Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions apps/oxlint/src/output_formatter/gitlab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use oxc_diagnostics::{
Error, Severity,
reporter::{DiagnosticReporter, DiagnosticResult, Info},
};

use std::hash::{DefaultHasher, Hash, Hasher};

use crate::output_formatter::InternalFormatter;

#[derive(Debug, Default)]
pub struct GitlabOutputFormatter;

#[derive(Debug, serde::Serialize)]
struct GitlabErrorLocationLinesJson {
begin: usize,
end: usize,
}

#[derive(Debug, serde::Serialize)]
struct GitlabErrorLocationJson {
path: String,
lines: GitlabErrorLocationLinesJson,
}

#[derive(Debug, serde::Serialize)]
struct GitlabErrorJson {
description: String,
check_name: String,
fingerprint: String,
severity: String,
location: GitlabErrorLocationJson,
}

impl InternalFormatter for GitlabOutputFormatter {
fn get_diagnostic_reporter(&self) -> Box<dyn DiagnosticReporter> {
Box::new(GitlabReporter::default())
}
}

/// Renders reports as a Gitlab Code Quality Report
///
/// <https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format>
///
/// Note that, due to syntactic restrictions of JSON arrays, this reporter waits until all
/// diagnostics have been reported before writing them to the output stream.
#[derive(Default)]
struct GitlabReporter {
diagnostics: Vec<Error>,
}

impl DiagnosticReporter for GitlabReporter {
fn finish(&mut self, _: &DiagnosticResult) -> Option<String> {
Some(format_gitlab(&mut self.diagnostics))
}

fn render_error(&mut self, error: Error) -> Option<String> {
self.diagnostics.push(error);
None
}
}

fn format_gitlab(diagnostics: &mut Vec<Error>) -> String {
let errors = diagnostics.drain(..).map(|error| {
let Info { start, end, filename, message, severity, rule_id } = Info::new(&error);
let severity = match severity {
Severity::Error => "critical".to_string(),
Severity::Warning => "major".to_string(),
Severity::Advice => "minor".to_string(),
};

let fingerprint = {
let mut hasher = DefaultHasher::new();
start.line.hash(&mut hasher);
end.line.hash(&mut hasher);
filename.hash(&mut hasher);
message.hash(&mut hasher);
severity.hash(&mut hasher);

format!("{:x}", hasher.finish())
};

GitlabErrorJson {
description: message,
check_name: rule_id.unwrap_or_default(),
location: GitlabErrorLocationJson {
path: filename,
lines: GitlabErrorLocationLinesJson { begin: start.line, end: end.line },
},
fingerprint,
severity,
}
});

serde_json::to_string_pretty(&errors.collect::<Vec<_>>()).expect("Failed to serialize")
}

#[cfg(test)]
mod test {
use oxc_diagnostics::{
NamedSource, OxcDiagnostic,
reporter::{DiagnosticReporter, DiagnosticResult},
};
use oxc_span::Span;

use super::GitlabReporter;

#[test]
fn reporter() {
let mut reporter = GitlabReporter::default();

let error = OxcDiagnostic::warn("error message")
.with_label(Span::new(0, 8))
.with_source_code(NamedSource::new("file://test.ts", "debugger;"));

let first_result = reporter.render_error(error);

// reporter keeps it in memory
assert!(first_result.is_none());

// reporter gives results when finishing
let second_result = reporter.finish(&DiagnosticResult::default());

assert!(second_result.is_some());
assert_eq!(
second_result.unwrap(),
"[\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]"
);
}
}
5 changes: 5 additions & 0 deletions apps/oxlint/src/output_formatter/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod checkstyle;
mod default;
mod github;
mod gitlab;
mod json;
mod junit;
mod stylish;
Expand All @@ -12,6 +13,7 @@ use std::time::Duration;

use checkstyle::CheckStyleOutputFormatter;
use github::GithubOutputFormatter;
use gitlab::GitlabOutputFormatter;
use junit::JUnitOutputFormatter;
use stylish::StylishOutputFormatter;
use unix::UnixOutputFormatter;
Expand All @@ -26,6 +28,7 @@ pub enum OutputFormat {
/// GitHub Check Annotation
/// <https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message>
Github,
Gitlab,
Json,
Unix,
Checkstyle,
Expand All @@ -43,6 +46,7 @@ impl FromStr for OutputFormat {
"unix" => Ok(Self::Unix),
"checkstyle" => Ok(Self::Checkstyle),
"github" => Ok(Self::Github),
"gitlab" => Ok(Self::Gitlab),
"stylish" => Ok(Self::Stylish),
"junit" => Ok(Self::JUnit),
_ => Err(format!("'{s}' is not a known format")),
Expand Down Expand Up @@ -96,6 +100,7 @@ impl OutputFormatter {
OutputFormat::Json => Box::<JsonOutputFormatter>::default(),
OutputFormat::Checkstyle => Box::<CheckStyleOutputFormatter>::default(),
OutputFormat::Github => Box::new(GithubOutputFormatter),
OutputFormat::Gitlab => Box::<GitlabOutputFormatter>::default(),
OutputFormat::Unix => Box::<UnixOutputFormatter>::default(),
OutputFormat::Default => Box::new(DefaultOutputFormatter),
OutputFormat::Stylish => Box::<StylishOutputFormatter>::default(),
Expand Down
Loading