From c87fc0dfd2450a0576a1a67caf12552ce77d0486 Mon Sep 17 00:00:00 2001 From: Tapan Prakash Date: Sun, 26 Jan 2025 23:11:14 +0530 Subject: [PATCH 1/7] Basic setup for Junit formatter --- apps/oxlint/src/output_formatter/junit.rs | 26 +++++++++++++++++++++++ apps/oxlint/src/output_formatter/mod.rs | 5 +++++ 2 files changed, 31 insertions(+) create mode 100644 apps/oxlint/src/output_formatter/junit.rs diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs new file mode 100644 index 0000000000000..571c7cc295f07 --- /dev/null +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -0,0 +1,26 @@ +use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult}, Error}; + +use super::InternalFormatter; + +#[derive(Default)] +pub struct JUnitOutputFormatter; + +impl InternalFormatter for JUnitOutputFormatter { + fn get_diagnostic_reporter(&self) -> Box { + Box::new(JUnitReporter::default()) + } +} + +#[derive(Default)] +struct JUnitReporter { +} + +impl DiagnosticReporter for JUnitReporter { + fn finish(&mut self, result: &DiagnosticResult) -> Option { + todo!() + } + + fn render_error(&mut self, error: Error) -> Option { + todo!() + } +} \ No newline at end of file diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index e01e58369a6d9..b2e702c02d6d3 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -4,6 +4,7 @@ mod github; mod json; mod stylish; mod unix; +mod junit; use std::str::FromStr; use std::time::Duration; @@ -12,6 +13,7 @@ use checkstyle::CheckStyleOutputFormatter; use github::GithubOutputFormatter; use stylish::StylishOutputFormatter; use unix::UnixOutputFormatter; +use junit::JUnitOutputFormatter; use oxc_diagnostics::reporter::DiagnosticReporter; @@ -27,6 +29,7 @@ pub enum OutputFormat { Unix, Checkstyle, Stylish, + JUnit, } impl FromStr for OutputFormat { @@ -40,6 +43,7 @@ impl FromStr for OutputFormat { "checkstyle" => Ok(Self::Checkstyle), "github" => Ok(Self::Github), "stylish" => Ok(Self::Stylish), + "junit" => Ok(Self::JUnit), _ => Err(format!("'{s}' is not a known format")), } } @@ -93,6 +97,7 @@ impl OutputFormatter { OutputFormat::Unix => Box::::default(), OutputFormat::Default => Box::new(DefaultOutputFormatter), OutputFormat::Stylish => Box::::default(), + OutputFormat::JUnit => Box::::default(), } } From bfe6372b0d5788ce04e0800fb53e4b7a6c2d98de Mon Sep 17 00:00:00 2001 From: Tapan Prakash Date: Mon, 27 Jan 2025 23:28:58 +0530 Subject: [PATCH 2/7] feat(linter): junit reporter support --- Cargo.lock | 67 +++++++++++++++++++++++ Cargo.toml | 1 + apps/oxlint/Cargo.toml | 3 +- apps/oxlint/src/output_formatter/junit.rs | 28 ++++++++-- 4 files changed, 94 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8ccdda96a7ea..8dd5d477f2551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "num-traits", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -1402,6 +1411,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "newtype-uuid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056" +dependencies = [ + "uuid", +] + [[package]] name = "nom" version = "8.0.0" @@ -2280,6 +2298,7 @@ dependencies = [ "oxc_diagnostics", "oxc_linter", "oxc_span", + "quick-junit", "rayon", "regex", "rustc-hash", @@ -2494,6 +2513,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" +[[package]] +name = "quick-junit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" +dependencies = [ + "chrono", + "indexmap", + "newtype-uuid", + "quick-xml", + "strip-ansi-escapes", + "thiserror 2.0.10", + "uuid", +] + +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.38" @@ -3023,6 +3066,15 @@ dependencies = [ "serde", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3497,6 +3549,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" + [[package]] name = "valuable" version = "0.1.0" @@ -3515,6 +3573,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 69278de0111a2..636ad77b82879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -202,6 +202,7 @@ ureq = { version = "3.0.0", default-features = false } url = "2.5.4" walkdir = "2.5.0" wasm-bindgen = "0.2.99" +quick-junit = "0.5.1" [workspace.metadata.cargo-shear] ignored = ["napi", "oxc_transform_napi", "oxc_parser_napi", "prettyplease"] diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index fbe2544ec89bb..05f9458bca3a0 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -44,7 +44,8 @@ rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } -tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature +tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature +quick-junit = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs index 571c7cc295f07..75852d3719676 100644 --- a/apps/oxlint/src/output_formatter/junit.rs +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -1,6 +1,8 @@ -use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult}, Error}; + +use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult, Info}, Error}; use super::InternalFormatter; +use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; #[derive(Default)] pub struct JUnitOutputFormatter; @@ -13,14 +15,32 @@ impl InternalFormatter for JUnitOutputFormatter { #[derive(Default)] struct JUnitReporter { + diagnostics: Vec, } impl DiagnosticReporter for JUnitReporter { - fn finish(&mut self, result: &DiagnosticResult) -> Option { - todo!() + fn finish(&mut self, _: &DiagnosticResult) -> Option { + Some(format_junit(&self.diagnostics)) } fn render_error(&mut self, error: Error) -> Option { - todo!() + self.diagnostics.push(error); + None + } +} + +fn format_junit(diagnostics: &[Error]) -> String { + let mut report = Report::new("Oxlint"); + for diagnostic in diagnostics { + let info = Info::new(diagnostic); + let mut test_suite = TestSuite::new(info.filename); + let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure); + status.set_message(info.message); + let rule = diagnostic.code().map_or_else(String::new, |code| code.to_string()); + let test_case = TestCase::new(rule, status); + test_suite.add_test_case(test_case); + + report.add_test_suite(test_suite); } + report.to_string().unwrap() } \ No newline at end of file From a716d78d65485c72a27b2f8c4472062628257188 Mon Sep 17 00:00:00 2001 From: Tapan Prakash Date: Fri, 31 Jan 2025 11:19:31 +0530 Subject: [PATCH 3/7] Improved junit reporting --- apps/oxlint/src/output_formatter/junit.rs | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs index 75852d3719676..d14d9a93a650f 100644 --- a/apps/oxlint/src/output_formatter/junit.rs +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -1,5 +1,5 @@ - -use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult, Info}, Error}; +use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult, Info}, Error, Severity}; +use rustc_hash::FxHashMap; use super::InternalFormatter; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; @@ -30,16 +30,33 @@ impl DiagnosticReporter for JUnitReporter { } fn format_junit(diagnostics: &[Error]) -> String { - let mut report = Report::new("Oxlint"); + let mut grouped: FxHashMap> = FxHashMap::default(); + for diagnostic in diagnostics { let info = Info::new(diagnostic); - let mut test_suite = TestSuite::new(info.filename); - let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure); - status.set_message(info.message); - let rule = diagnostic.code().map_or_else(String::new, |code| code.to_string()); - let test_case = TestCase::new(rule, status); - test_suite.add_test_case(test_case); + grouped.entry(info.filename).or_default().push(diagnostic); + } + let mut report = Report::new("Oxlint"); + for diagnostics in grouped.values() { + let diagnostic = diagnostics[0]; + let filename = Info::new(diagnostic).filename; + + let mut test_suite = TestSuite::new(filename); + + for diagnostic in diagnostics { + let rule = diagnostic.code().map_or_else(String::new, |code| code.to_string()); + let Info { message, start, .. } = Info::new(diagnostic); + + let mut status = match diagnostic.severity() { + Some(Severity::Error) => TestCaseStatus::non_success(NonSuccessKind::Error), + _ => TestCaseStatus::non_success(NonSuccessKind::Failure) + }; + status.set_message(message.clone()); + status.set_description(format!("line {}, column {}, {}", start.line, start.column, message.clone())); + let test_case = TestCase::new(rule, status); + test_suite.add_test_case(test_case); + } report.add_test_suite(test_suite); } report.to_string().unwrap() From 26b5c06b15b5ade56b1ffc9d9b2d511f808865c0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 05:54:44 +0000 Subject: [PATCH 4/7] [autofix.ci] apply automated fixes --- Cargo.toml | 2 +- apps/oxlint/Cargo.toml | 4 ++-- apps/oxlint/src/output_formatter/junit.rs | 16 ++++++++++++---- apps/oxlint/src/output_formatter/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 636ad77b82879..af60bf84716e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,6 +178,7 @@ phf = "0.11.2" pico-args = "0.5.0" prettyplease = "0.2.25" project-root = "0.2.2" +quick-junit = "0.5.1" rayon = "1.10.0" regex = "1.11.1" ropey = "1.6.1" @@ -202,7 +203,6 @@ ureq = { version = "3.0.0", default-features = false } url = "2.5.4" walkdir = "2.5.0" wasm-bindgen = "0.2.99" -quick-junit = "0.5.1" [workspace.metadata.cargo-shear] ignored = ["napi", "oxc_transform_napi", "oxc_parser_napi", "prettyplease"] diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index 05f9458bca3a0..d1b6f7ded2b60 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -39,13 +39,13 @@ bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] cow-utils = { workspace = true } ignore = { workspace = true, features = ["simd-accel"] } miette = { workspace = true } +quick-junit = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } -tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature -quick-junit = { workspace = true } +tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature [dev-dependencies] insta = { workspace = true } diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs index d14d9a93a650f..4ae6ca0ee1c1b 100644 --- a/apps/oxlint/src/output_formatter/junit.rs +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -1,4 +1,7 @@ -use oxc_diagnostics::{reporter::{DiagnosticReporter, DiagnosticResult, Info}, Error, Severity}; +use oxc_diagnostics::{ + reporter::{DiagnosticReporter, DiagnosticResult, Info}, + Error, Severity, +}; use rustc_hash::FxHashMap; use super::InternalFormatter; @@ -50,14 +53,19 @@ fn format_junit(diagnostics: &[Error]) -> String { let mut status = match diagnostic.severity() { Some(Severity::Error) => TestCaseStatus::non_success(NonSuccessKind::Error), - _ => TestCaseStatus::non_success(NonSuccessKind::Failure) + _ => TestCaseStatus::non_success(NonSuccessKind::Failure), }; status.set_message(message.clone()); - status.set_description(format!("line {}, column {}, {}", start.line, start.column, message.clone())); + status.set_description(format!( + "line {}, column {}, {}", + start.line, + start.column, + message.clone() + )); let test_case = TestCase::new(rule, status); test_suite.add_test_case(test_case); } report.add_test_suite(test_suite); } report.to_string().unwrap() -} \ No newline at end of file +} diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index b2e702c02d6d3..11b3d86c83fe6 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -2,18 +2,18 @@ mod checkstyle; mod default; mod github; mod json; +mod junit; mod stylish; mod unix; -mod junit; use std::str::FromStr; use std::time::Duration; use checkstyle::CheckStyleOutputFormatter; use github::GithubOutputFormatter; +use junit::JUnitOutputFormatter; use stylish::StylishOutputFormatter; use unix::UnixOutputFormatter; -use junit::JUnitOutputFormatter; use oxc_diagnostics::reporter::DiagnosticReporter; From 7b09eebe6739050ee3370b67631cc53f7fcbc304 Mon Sep 17 00:00:00 2001 From: Tapan Prakash Date: Fri, 31 Jan 2025 15:39:06 +0530 Subject: [PATCH 5/7] Added test cases --- apps/oxlint/src/output_formatter/junit.rs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs index 4ae6ca0ee1c1b..34b610764e50b 100644 --- a/apps/oxlint/src/output_formatter/junit.rs +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -69,3 +69,41 @@ fn format_junit(diagnostics: &[Error]) -> String { } report.to_string().unwrap() } + +#[cfg(test)] +mod test { + use super::*; + use oxc_diagnostics::{reporter::DiagnosticResult, NamedSource, OxcDiagnostic}; + use oxc_span::Span; + + #[test] + fn test_junit_reporter() { + const EXPECTED_REPORT: &str = r#" + + + + line 1, column 1, error message + + + line 1, column 1, warning message + + + +"#; + let mut reporter = JUnitReporter::default(); + + let error = OxcDiagnostic::error("error message") + .with_label(Span::new(0, 8)) + .with_source_code(NamedSource::new("file.js", "let a = ;")); + + let warning = OxcDiagnostic::warn("warning message") + .with_label(Span::new(0, 9)) + .with_source_code(NamedSource::new("file.js", "debugger;")); + + reporter.render_error(error); + reporter.render_error(warning); + + let output = reporter.finish(&DiagnosticResult::default()).unwrap(); + assert_eq!(output.to_string(), EXPECTED_REPORT); + } +} From 8c7111e296ef6f05134ac8537d088fc59178612d Mon Sep 17 00:00:00 2001 From: Tapan Prakash Date: Sat, 1 Feb 2025 20:55:47 +0530 Subject: [PATCH 6/7] removed quick-junit deps --- Cargo.lock | 67 ------------------- Cargo.toml | 1 - apps/oxlint/Cargo.toml | 1 - .../oxlint/src/output_formatter/checkstyle.rs | 53 +-------------- apps/oxlint/src/output_formatter/junit.rs | 51 ++++++++------ apps/oxlint/src/output_formatter/mod.rs | 8 +++ apps/oxlint/src/output_formatter/xml_utils.rs | 52 ++++++++++++++ ...gnostic_--format=junit test.js@oxlint.snap | 25 +++++++ 8 files changed, 118 insertions(+), 140 deletions(-) create mode 100644 apps/oxlint/src/output_formatter/xml_utils.rs create mode 100644 apps/oxlint/src/snapshots/fixtures__output_formatter_diagnostic_--format=junit test.js@oxlint.snap diff --git a/Cargo.lock b/Cargo.lock index c8f0107bdeda5..8696678dfd662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,15 +229,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "num-traits", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -1411,15 +1402,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "newtype-uuid" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056" -dependencies = [ - "uuid", -] - [[package]] name = "nom" version = "8.0.0" @@ -2299,7 +2281,6 @@ dependencies = [ "oxc_diagnostics", "oxc_linter", "oxc_span", - "quick-junit", "rayon", "regex", "rustc-hash", @@ -2514,30 +2495,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bccbff07d5ed689c4087d20d7307a52ab6141edeedf487c3876a55b86cf63df" -[[package]] -name = "quick-junit" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" -dependencies = [ - "chrono", - "indexmap", - "newtype-uuid", - "quick-xml", - "strip-ansi-escapes", - "thiserror 2.0.10", - "uuid", -] - -[[package]] -name = "quick-xml" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.38" @@ -3067,15 +3024,6 @@ dependencies = [ "serde", ] -[[package]] -name = "strip-ansi-escapes" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" -dependencies = [ - "vte", -] - [[package]] name = "strsim" version = "0.11.1" @@ -3550,12 +3498,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" - [[package]] name = "valuable" version = "0.1.0" @@ -3574,15 +3516,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" -[[package]] -name = "vte" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" -dependencies = [ - "memchr", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index af60bf84716e1..69278de0111a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,7 +178,6 @@ phf = "0.11.2" pico-args = "0.5.0" prettyplease = "0.2.25" project-root = "0.2.2" -quick-junit = "0.5.1" rayon = "1.10.0" regex = "1.11.1" ropey = "1.6.1" diff --git a/apps/oxlint/Cargo.toml b/apps/oxlint/Cargo.toml index d1b6f7ded2b60..fbe2544ec89bb 100644 --- a/apps/oxlint/Cargo.toml +++ b/apps/oxlint/Cargo.toml @@ -39,7 +39,6 @@ bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] cow-utils = { workspace = true } ignore = { workspace = true, features = ["simd-accel"] } miette = { workspace = true } -quick-junit = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } diff --git a/apps/oxlint/src/output_formatter/checkstyle.rs b/apps/oxlint/src/output_formatter/checkstyle.rs index 871e9f659755d..5f529f3928893 100644 --- a/apps/oxlint/src/output_formatter/checkstyle.rs +++ b/apps/oxlint/src/output_formatter/checkstyle.rs @@ -7,7 +7,7 @@ use oxc_diagnostics::{ Error, Severity, }; -use crate::output_formatter::InternalFormatter; +use crate::output_formatter::{xml_utils::xml_escape, InternalFormatter}; #[derive(Debug, Default)] pub struct CheckStyleOutputFormatter; @@ -66,57 +66,6 @@ fn format_checkstyle(diagnostics: &[Error]) -> String { ) } -/// -fn xml_escape(raw: &str) -> Cow { - xml_escape_impl(raw, |ch| matches!(ch, b'<' | b'>' | b'&' | b'\'' | b'\"')) -} - -fn xml_escape_impl bool>(raw: &str, escape_chars: F) -> Cow { - let bytes = raw.as_bytes(); - let mut escaped = None; - let mut iter = bytes.iter(); - let mut pos = 0; - while let Some(i) = iter.position(|&b| escape_chars(b)) { - if escaped.is_none() { - escaped = Some(Vec::with_capacity(raw.len())); - } - let escaped = escaped.as_mut().expect("initialized"); - let new_pos = pos + i; - escaped.extend_from_slice(&bytes[pos..new_pos]); - match bytes[new_pos] { - b'<' => escaped.extend_from_slice(b"<"), - b'>' => escaped.extend_from_slice(b">"), - b'\'' => escaped.extend_from_slice(b"'"), - b'&' => escaped.extend_from_slice(b"&"), - b'"' => escaped.extend_from_slice(b"""), - - // This set of escapes handles characters that should be escaped - // in elements of xs:lists, because those characters works as - // delimiters of list elements - b'\t' => escaped.extend_from_slice(b" "), - b'\n' => escaped.extend_from_slice(b" "), - b'\r' => escaped.extend_from_slice(b" "), - b' ' => escaped.extend_from_slice(b" "), - _ => unreachable!( - "Only '<', '>','\', '&', '\"', '\\t', '\\r', '\\n', and ' ' are escaped" - ), - } - pos = new_pos + 1; - } - - if let Some(mut escaped) = escaped { - if let Some(raw) = bytes.get(pos..) { - escaped.extend_from_slice(raw); - } - - // SAFETY: we operate on UTF-8 input and search for an one byte chars only, - // so all slices that was put to the `escaped` is a valid UTF-8 encoded strings - Cow::Owned(unsafe { String::from_utf8_unchecked(escaped) }) - } else { - Cow::Borrowed(raw) - } -} - #[cfg(test)] mod test { use oxc_diagnostics::{ diff --git a/apps/oxlint/src/output_formatter/junit.rs b/apps/oxlint/src/output_formatter/junit.rs index 34b610764e50b..9e1faebcd5805 100644 --- a/apps/oxlint/src/output_formatter/junit.rs +++ b/apps/oxlint/src/output_formatter/junit.rs @@ -4,8 +4,7 @@ use oxc_diagnostics::{ }; use rustc_hash::FxHashMap; -use super::InternalFormatter; -use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; +use super::{xml_utils::xml_escape, InternalFormatter}; #[derive(Default)] pub struct JUnitOutputFormatter; @@ -34,40 +33,54 @@ impl DiagnosticReporter for JUnitReporter { fn format_junit(diagnostics: &[Error]) -> String { let mut grouped: FxHashMap> = FxHashMap::default(); + let mut total_errors = 0; + let mut total_warnings = 0; for diagnostic in diagnostics { let info = Info::new(diagnostic); grouped.entry(info.filename).or_default().push(diagnostic); } - let mut report = Report::new("Oxlint"); + let mut test_suite = String::new(); for diagnostics in grouped.values() { let diagnostic = diagnostics[0]; let filename = Info::new(diagnostic).filename; - - let mut test_suite = TestSuite::new(filename); + let mut test_cases = String::new(); + let mut error = 0; + let mut warning = 0; for diagnostic in diagnostics { let rule = diagnostic.code().map_or_else(String::new, |code| code.to_string()); let Info { message, start, .. } = Info::new(diagnostic); - let mut status = match diagnostic.severity() { - Some(Severity::Error) => TestCaseStatus::non_success(NonSuccessKind::Error), - _ => TestCaseStatus::non_success(NonSuccessKind::Failure), + let severity = if let Some(Severity::Error) = diagnostic.severity() { + total_errors += 1; + error += 1; + "error" + } else { + total_warnings += 1; + warning += 1; + "failure" }; - status.set_message(message.clone()); - status.set_description(format!( - "line {}, column {}, {}", - start.line, - start.column, - message.clone() - )); - let test_case = TestCase::new(rule, status); - test_suite.add_test_case(test_case); + let description = + format!("line {}, column {}, {}", start.line, start.column, xml_escape(&message)); + + let status = format!( + " <{} message=\"{}\">{}", + severity, + xml_escape(&message), + description, + severity + ); + let test_case = + format!("\n \n{status}\n "); + test_cases = format!("{test_cases}{test_case}"); } - report.add_test_suite(test_suite); + test_suite = format!(" {}\n ", filename, diagnostics.len(), error, warning, test_cases); } - report.to_string().unwrap() + let test_suites = format!("\n{}\n\n", total_errors + total_warnings, total_warnings, total_errors, test_suite); + + format!("\n{test_suites}") } #[cfg(test)] diff --git a/apps/oxlint/src/output_formatter/mod.rs b/apps/oxlint/src/output_formatter/mod.rs index 11b3d86c83fe6..03af3d09f3ea1 100644 --- a/apps/oxlint/src/output_formatter/mod.rs +++ b/apps/oxlint/src/output_formatter/mod.rs @@ -5,6 +5,7 @@ mod json; mod junit; mod stylish; mod unix; +mod xml_utils; use std::str::FromStr; use std::time::Duration; @@ -167,4 +168,11 @@ mod test { Tester::new().with_cwd(TEST_CWD.into()).test_and_snapshot(args); } + + #[test] + fn test_output_formatter_diagnostic_junit() { + let args = &["--format=junit", "test.js"]; + + Tester::new().with_cwd(TEST_CWD.into()).test_and_snapshot(args); + } } diff --git a/apps/oxlint/src/output_formatter/xml_utils.rs b/apps/oxlint/src/output_formatter/xml_utils.rs new file mode 100644 index 0000000000000..e40d61ac805e8 --- /dev/null +++ b/apps/oxlint/src/output_formatter/xml_utils.rs @@ -0,0 +1,52 @@ +use std::borrow::Cow; + +/// +pub fn xml_escape(raw: &str) -> Cow { + xml_escape_impl(raw, |ch| matches!(ch, b'<' | b'>' | b'&' | b'\'' | b'\"')) +} + +fn xml_escape_impl bool>(raw: &str, escape_chars: F) -> Cow { + let bytes = raw.as_bytes(); + let mut escaped = None; + let mut iter = bytes.iter(); + let mut pos = 0; + while let Some(i) = iter.position(|&b| escape_chars(b)) { + if escaped.is_none() { + escaped = Some(Vec::with_capacity(raw.len())); + } + let escaped = escaped.as_mut().expect("initialized"); + let new_pos = pos + i; + escaped.extend_from_slice(&bytes[pos..new_pos]); + match bytes[new_pos] { + b'<' => escaped.extend_from_slice(b"<"), + b'>' => escaped.extend_from_slice(b">"), + b'\'' => escaped.extend_from_slice(b"'"), + b'&' => escaped.extend_from_slice(b"&"), + b'"' => escaped.extend_from_slice(b"""), + + // This set of escapes handles characters that should be escaped + // in elements of xs:lists, because those characters works as + // delimiters of list elements + b'\t' => escaped.extend_from_slice(b" "), + b'\n' => escaped.extend_from_slice(b" "), + b'\r' => escaped.extend_from_slice(b" "), + b' ' => escaped.extend_from_slice(b" "), + _ => unreachable!( + "Only '<', '>','\', '&', '\"', '\\t', '\\r', '\\n', and ' ' are escaped" + ), + } + pos = new_pos + 1; + } + + if let Some(mut escaped) = escaped { + if let Some(raw) = bytes.get(pos..) { + escaped.extend_from_slice(raw); + } + + // SAFETY: we operate on UTF-8 input and search for an one byte chars only, + // so all slices that was put to the `escaped` is a valid UTF-8 encoded strings + Cow::Owned(unsafe { String::from_utf8_unchecked(escaped) }) + } else { + Cow::Borrowed(raw) + } +} \ No newline at end of file diff --git a/apps/oxlint/src/snapshots/fixtures__output_formatter_diagnostic_--format=junit test.js@oxlint.snap b/apps/oxlint/src/snapshots/fixtures__output_formatter_diagnostic_--format=junit test.js@oxlint.snap new file mode 100644 index 0000000000000..81221705dc542 --- /dev/null +++ b/apps/oxlint/src/snapshots/fixtures__output_formatter_diagnostic_--format=junit test.js@oxlint.snap @@ -0,0 +1,25 @@ +--- +source: apps/oxlint/src/tester.rs +assertion_line: 95 +--- +########## +arguments: --format=junit test.js +working directory: fixtures/output_formatter_diagnostic +---------- + + + + + line 5, column 1, `debugger` statement is not allowed + + + line 1, column 10, Function 'foo' is declared but never used. + + + line 1, column 17, Parameter 'b' is declared but never used. Unused parameters should start with a '_'. + + + +---------- +CLI result: LintFoundErrors +---------- From 2033f882653192fda7cfdf555c4d5903538df97c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 15:27:57 +0000 Subject: [PATCH 7/7] [autofix.ci] apply automated fixes --- apps/oxlint/src/output_formatter/xml_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/oxlint/src/output_formatter/xml_utils.rs b/apps/oxlint/src/output_formatter/xml_utils.rs index e40d61ac805e8..6acde7e359636 100644 --- a/apps/oxlint/src/output_formatter/xml_utils.rs +++ b/apps/oxlint/src/output_formatter/xml_utils.rs @@ -49,4 +49,4 @@ fn xml_escape_impl bool>(raw: &str, escape_chars: F) -> Cow { } else { Cow::Borrowed(raw) } -} \ No newline at end of file +}