Skip to content

Commit bd9dd88

Browse files
committed
feat(linter)!: add more info to json reporter (#11524)
fixes #4868
1 parent b5a6a6e commit bd9dd88

File tree

4 files changed

+86
-30
lines changed

4 files changed

+86
-30
lines changed
Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use std::cell::RefCell;
2+
use std::rc::Rc;
3+
14
use oxc_diagnostics::{
25
Error,
36
reporter::{DiagnosticReporter, DiagnosticResult},
@@ -9,7 +12,9 @@ use miette::JSONReportHandler;
912
use crate::output_formatter::InternalFormatter;
1013

1114
#[derive(Debug, Default)]
12-
pub struct JsonOutputFormatter;
15+
pub struct JsonOutputFormatter {
16+
reporter: JsonReporterWrapper,
17+
}
1318

1419
impl InternalFormatter for JsonOutputFormatter {
1520
fn all_rules(&self) -> Option<String> {
@@ -32,25 +37,58 @@ impl InternalFormatter for JsonOutputFormatter {
3237
)
3338
}
3439

40+
fn lint_command_info(&self, lint_command_info: &super::LintCommandInfo) -> Option<String> {
41+
let diagnostics = self.reporter.0.borrow_mut().render();
42+
let number_of_rules =
43+
lint_command_info.number_of_rules.map_or("null".to_string(), |x| x.to_string());
44+
let start_time = lint_command_info.start_time.as_secs_f64();
45+
46+
Some(format!(
47+
r#"{{ "diagnostics": {},
48+
"number_of_files": {},
49+
"number_of_rules": {},
50+
"threads_count": {},
51+
"start_time": {}
52+
}}
53+
"#,
54+
diagnostics,
55+
lint_command_info.number_of_files,
56+
number_of_rules,
57+
lint_command_info.threads_count,
58+
start_time,
59+
))
60+
}
61+
3562
fn get_diagnostic_reporter(&self) -> Box<dyn DiagnosticReporter> {
36-
Box::new(JsonReporter::default())
63+
Box::new(self.reporter.clone())
3764
}
3865
}
3966

4067
/// Renders reports as a JSON array of objects.
4168
///
4269
/// Note that, due to syntactic restrictions of JSON arrays, this reporter waits until all
4370
/// diagnostics have been reported before writing them to the output stream.
44-
#[derive(Default)]
71+
#[derive(Default, Debug)]
4572
struct JsonReporter {
4673
diagnostics: Vec<Error>,
4774
}
4875

76+
#[derive(Clone, Debug, Default)]
77+
pub struct JsonReporterWrapper(Rc<RefCell<JsonReporter>>);
78+
79+
impl DiagnosticReporter for JsonReporterWrapper {
80+
fn finish(&mut self, _result: &DiagnosticResult) -> Option<String> {
81+
None
82+
}
83+
84+
fn render_error(&mut self, error: Error) -> Option<String> {
85+
self.0.borrow_mut().render_error(error)
86+
}
87+
}
88+
4989
impl DiagnosticReporter for JsonReporter {
50-
// NOTE: this output does not conform to eslint json format yet
51-
// https://eslint.org/docs/latest/use/formatters/#json
5290
fn finish(&mut self, _: &DiagnosticResult) -> Option<String> {
53-
Some(format_json(&mut self.diagnostics))
91+
None
5492
}
5593

5694
fn render_error(&mut self, error: Error) -> Option<String> {
@@ -59,51 +97,65 @@ impl DiagnosticReporter for JsonReporter {
5997
}
6098
}
6199

100+
impl JsonReporter {
101+
pub(super) fn render(&mut self) -> String {
102+
format_json(&mut self.diagnostics)
103+
}
104+
}
105+
62106
/// <https://github.com/fregante/eslint-formatters/tree/ae1fd9748596447d1fd09625c33d9e7ba9a3d06d/packages/eslint-formatter-json>
63107
fn format_json(diagnostics: &mut Vec<Error>) -> String {
64108
let handler = JSONReportHandler::new();
65109
let messages = diagnostics
66110
.drain(..)
67111
.map(|error| {
68-
let mut output = String::from("\t");
112+
let mut output = String::new();
69113
handler.render_report(&mut output, error.as_ref()).unwrap();
70114
output
71115
})
72116
.collect::<Vec<_>>()
73117
.join(",\n");
74-
format!("[\n{messages}\n]\n")
118+
format!("[{messages}]")
75119
}
76120

77121
#[cfg(test)]
78122
mod test {
79-
use oxc_diagnostics::{
80-
NamedSource, OxcDiagnostic,
81-
reporter::{DiagnosticReporter, DiagnosticResult},
82-
};
123+
use std::time::Duration;
124+
125+
use oxc_diagnostics::{NamedSource, OxcDiagnostic, reporter::DiagnosticResult};
83126
use oxc_span::Span;
84127

85-
use super::JsonReporter;
128+
use crate::output_formatter::{InternalFormatter, LintCommandInfo, json::JsonOutputFormatter};
86129

87130
#[test]
88131
fn reporter() {
89-
let mut reporter = JsonReporter::default();
132+
let formatter = JsonOutputFormatter::default();
90133

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

95-
let first_result = reporter.render_error(error);
138+
let mut diagnostic_reporter = formatter.get_diagnostic_reporter();
139+
let first_result = diagnostic_reporter.render_error(error);
96140

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

100144
// report not gives us all diagnostics at ones
101-
let second_result = reporter.finish(&DiagnosticResult::default());
102-
103-
assert!(second_result.is_some());
145+
let second_result = diagnostic_reporter.finish(&DiagnosticResult::default());
146+
147+
assert!(second_result.is_none());
148+
let output = formatter
149+
.lint_command_info(&LintCommandInfo {
150+
number_of_files: 0,
151+
number_of_rules: Some(0),
152+
start_time: Duration::new(0, 0),
153+
threads_count: 1,
154+
})
155+
.unwrap();
104156
assert_eq!(
105-
second_result.unwrap(),
106-
"[\n\t{\"message\": \"error message\",\"severity\": \"warning\",\"causes\": [],\"filename\": \"file://test.ts\",\"labels\": [{\"span\": {\"offset\": 0,\"length\": 8,\"line\": 1,\"column\": 1}}],\"related\": []}\n]\n"
157+
&output,
158+
"{ \"diagnostics\": [{\"message\": \"error message\",\"severity\": \"warning\",\"causes\": [],\"filename\": \"file://test.ts\",\"labels\": [{\"span\": {\"offset\": 0,\"length\": 8,\"line\": 1,\"column\": 1}}],\"related\": []}],\n \"number_of_files\": 0,\n \"number_of_rules\": 0,\n \"threads_count\": 1,\n \"start_time\": 0\n }\n "
107159
);
108160
}
109161
}

apps/oxlint/src/snapshots/fixtures__output_formatter_diagnostic_--format=json [email protected]

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ source: apps/oxlint/src/tester.rs
55
arguments: --format=json test.js
66
working directory: fixtures/output_formatter_diagnostic
77
----------
8-
[
9-
{"message": "`debugger` statement is not allowed","code": "eslint(no-debugger)","severity": "error","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html","help": "Remove the debugger statement","filename": "test.js","labels": [{"span": {"offset": 38,"length": 9,"line": 5,"column": 1}}],"related": []},
10-
{"message": "Function 'foo' is declared but never used.","code": "eslint(no-unused-vars)","severity": "warning","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html","help": "Consider removing this declaration.","filename": "test.js","labels": [{"label": "'foo' is declared here","span": {"offset": 9,"length": 3,"line": 1,"column": 10}}],"related": []},
11-
{"message": "Parameter 'b' is declared but never used. Unused parameters should start with a '_'.","code": "eslint(no-unused-vars)","severity": "warning","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html","help": "Consider removing this parameter.","filename": "test.js","labels": [{"label": "'b' is declared here","span": {"offset": 16,"length": 1,"line": 1,"column": 17}}],"related": []}
12-
]
13-
----------
8+
{ "diagnostics": [{"message": "`debugger` statement is not allowed","code": "eslint(no-debugger)","severity": "error","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger.html","help": "Remove the debugger statement","filename": "test.js","labels": [{"span": {"offset": 38,"length": 9,"line": 5,"column": 1}}],"related": []},
9+
{"message": "Function 'foo' is declared but never used.","code": "eslint(no-unused-vars)","severity": "warning","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html","help": "Consider removing this declaration.","filename": "test.js","labels": [{"label": "'foo' is declared here","span": {"offset": 9,"length": 3,"line": 1,"column": 10}}],"related": []},
10+
{"message": "Parameter 'b' is declared but never used. Unused parameters should start with a '_'.","code": "eslint(no-unused-vars)","severity": "warning","causes": [],"url": "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unused-vars.html","help": "Consider removing this parameter.","filename": "test.js","labels": [{"label": "'b' is declared here","span": {"offset": 16,"length": 1,"line": 1,"column": 17}}],"related": []}],
11+
"number_of_files": 1,
12+
"number_of_rules": null,
13+
"threads_count": 1,
14+
"start_time": <variable>
15+
}
16+
----------
1417
CLI result: LintFoundErrors
1518
----------

apps/oxlint/src/tester.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ impl Tester {
7373
settings.set_omit_expression(true);
7474
settings.set_snapshot_suffix("oxlint");
7575

76-
let regex = Regex::new(r"\d+ms").unwrap();
77-
7876
let output_string = &String::from_utf8(output).unwrap();
79-
let output_string = regex.replace_all(output_string, "<variable>ms");
77+
let regex = Regex::new(r"\d+ms").unwrap();
78+
let output_string = regex.replace_all(output_string, "<variable>ms").into_owned();
79+
let regex = Regex::new(r#""start_time": \d+\.\d+"#).unwrap();
80+
let output_string = regex.replace_all(&output_string, r#""start_time": <variable>"#);
8081

8182
// do not output the current working directory, each machine has a different one
8283
let cwd_string = current_cwd.to_str().unwrap();

crates/oxc_diagnostics/src/reporter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub trait DiagnosticReporter {
5757

5858
/// DiagnosticResult will be submitted to the Reporter when the [`DiagnosticService`](crate::service::DiagnosticService)
5959
/// is finished receiving all files
60-
#[derive(Default)]
60+
#[derive(Default, Debug)]
6161
pub struct DiagnosticResult {
6262
/// Total number of warnings received
6363
warnings_count: usize,

0 commit comments

Comments
 (0)