Skip to content

Commit 466c24a

Browse files
authored
feat(linter): add gitlab reporter output format (#10927)
closes #10352 Adds support for outputting a gitlab code quality report. Modeled mostly on the JSON and Github reporters. e.g `oxlint --format gitlab > codequality.json`
1 parent 4733b52 commit 466c24a

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use oxc_diagnostics::{
2+
Error, Severity,
3+
reporter::{DiagnosticReporter, DiagnosticResult, Info},
4+
};
5+
6+
use std::hash::{DefaultHasher, Hash, Hasher};
7+
8+
use crate::output_formatter::InternalFormatter;
9+
10+
#[derive(Debug, Default)]
11+
pub struct GitlabOutputFormatter;
12+
13+
#[derive(Debug, serde::Serialize)]
14+
struct GitlabErrorLocationLinesJson {
15+
begin: usize,
16+
end: usize,
17+
}
18+
19+
#[derive(Debug, serde::Serialize)]
20+
struct GitlabErrorLocationJson {
21+
path: String,
22+
lines: GitlabErrorLocationLinesJson,
23+
}
24+
25+
#[derive(Debug, serde::Serialize)]
26+
struct GitlabErrorJson {
27+
description: String,
28+
check_name: String,
29+
fingerprint: String,
30+
severity: String,
31+
location: GitlabErrorLocationJson,
32+
}
33+
34+
impl InternalFormatter for GitlabOutputFormatter {
35+
fn get_diagnostic_reporter(&self) -> Box<dyn DiagnosticReporter> {
36+
Box::new(GitlabReporter::default())
37+
}
38+
}
39+
40+
/// Renders reports as a Gitlab Code Quality Report
41+
///
42+
/// <https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format>
43+
///
44+
/// Note that, due to syntactic restrictions of JSON arrays, this reporter waits until all
45+
/// diagnostics have been reported before writing them to the output stream.
46+
#[derive(Default)]
47+
struct GitlabReporter {
48+
diagnostics: Vec<Error>,
49+
}
50+
51+
impl DiagnosticReporter for GitlabReporter {
52+
fn finish(&mut self, _: &DiagnosticResult) -> Option<String> {
53+
Some(format_gitlab(&mut self.diagnostics))
54+
}
55+
56+
fn render_error(&mut self, error: Error) -> Option<String> {
57+
self.diagnostics.push(error);
58+
None
59+
}
60+
}
61+
62+
fn format_gitlab(diagnostics: &mut Vec<Error>) -> String {
63+
let errors = diagnostics.drain(..).map(|error| {
64+
let Info { start, end, filename, message, severity, rule_id } = Info::new(&error);
65+
let severity = match severity {
66+
Severity::Error => "critical".to_string(),
67+
Severity::Warning => "major".to_string(),
68+
Severity::Advice => "minor".to_string(),
69+
};
70+
71+
let fingerprint = {
72+
let mut hasher = DefaultHasher::new();
73+
start.line.hash(&mut hasher);
74+
end.line.hash(&mut hasher);
75+
filename.hash(&mut hasher);
76+
message.hash(&mut hasher);
77+
severity.hash(&mut hasher);
78+
79+
format!("{:x}", hasher.finish())
80+
};
81+
82+
GitlabErrorJson {
83+
description: message,
84+
check_name: rule_id.unwrap_or_default(),
85+
location: GitlabErrorLocationJson {
86+
path: filename,
87+
lines: GitlabErrorLocationLinesJson { begin: start.line, end: end.line },
88+
},
89+
fingerprint,
90+
severity,
91+
}
92+
});
93+
94+
serde_json::to_string_pretty(&errors.collect::<Vec<_>>()).expect("Failed to serialize")
95+
}
96+
97+
#[cfg(test)]
98+
mod test {
99+
use oxc_diagnostics::{
100+
NamedSource, OxcDiagnostic,
101+
reporter::{DiagnosticReporter, DiagnosticResult},
102+
};
103+
use oxc_span::Span;
104+
105+
use super::GitlabReporter;
106+
107+
#[test]
108+
fn reporter() {
109+
let mut reporter = GitlabReporter::default();
110+
111+
let error = OxcDiagnostic::warn("error message")
112+
.with_label(Span::new(0, 8))
113+
.with_source_code(NamedSource::new("file://test.ts", "debugger;"));
114+
115+
let first_result = reporter.render_error(error);
116+
117+
// reporter keeps it in memory
118+
assert!(first_result.is_none());
119+
120+
// reporter gives results when finishing
121+
let second_result = reporter.finish(&DiagnosticResult::default());
122+
123+
assert!(second_result.is_some());
124+
assert_eq!(
125+
second_result.unwrap(),
126+
"[\n {\n \"description\": \"error message\",\n \"check_name\": \"\",\n \"fingerprint\": \"8b23bd85b148d3\",\n \"severity\": \"major\",\n \"location\": {\n \"path\": \"file://test.ts\",\n \"lines\": {\n \"begin\": 1,\n \"end\": 1\n }\n }\n }\n]"
127+
);
128+
}
129+
}

apps/oxlint/src/output_formatter/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod checkstyle;
22
mod default;
33
mod github;
4+
mod gitlab;
45
mod json;
56
mod junit;
67
mod stylish;
@@ -12,6 +13,7 @@ use std::time::Duration;
1213

1314
use checkstyle::CheckStyleOutputFormatter;
1415
use github::GithubOutputFormatter;
16+
use gitlab::GitlabOutputFormatter;
1517
use junit::JUnitOutputFormatter;
1618
use stylish::StylishOutputFormatter;
1719
use unix::UnixOutputFormatter;
@@ -26,6 +28,7 @@ pub enum OutputFormat {
2628
/// GitHub Check Annotation
2729
/// <https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message>
2830
Github,
31+
Gitlab,
2932
Json,
3033
Unix,
3134
Checkstyle,
@@ -43,6 +46,7 @@ impl FromStr for OutputFormat {
4346
"unix" => Ok(Self::Unix),
4447
"checkstyle" => Ok(Self::Checkstyle),
4548
"github" => Ok(Self::Github),
49+
"gitlab" => Ok(Self::Gitlab),
4650
"stylish" => Ok(Self::Stylish),
4751
"junit" => Ok(Self::JUnit),
4852
_ => Err(format!("'{s}' is not a known format")),
@@ -96,6 +100,7 @@ impl OutputFormatter {
96100
OutputFormat::Json => Box::<JsonOutputFormatter>::default(),
97101
OutputFormat::Checkstyle => Box::<CheckStyleOutputFormatter>::default(),
98102
OutputFormat::Github => Box::new(GithubOutputFormatter),
103+
OutputFormat::Gitlab => Box::<GitlabOutputFormatter>::default(),
99104
OutputFormat::Unix => Box::<UnixOutputFormatter>::default(),
100105
OutputFormat::Default => Box::new(DefaultOutputFormatter),
101106
OutputFormat::Stylish => Box::<StylishOutputFormatter>::default(),

0 commit comments

Comments
 (0)