From d0d0d9d7bb21f711459510226db2a0f28fc7f3bd Mon Sep 17 00:00:00 2001 From: Boshen Date: Tue, 20 Feb 2024 15:33:28 +0800 Subject: [PATCH] feat(diagnostics): implement json reporter (#2452) --- crates/oxc_cli/src/lint/mod.rs | 3 +- crates/oxc_diagnostics/src/reporter.rs | 75 +++++++++++++++++++++----- crates/oxc_diagnostics/src/service.rs | 35 ++++++------ 3 files changed, 79 insertions(+), 34 deletions(-) diff --git a/crates/oxc_cli/src/lint/mod.rs b/crates/oxc_cli/src/lint/mod.rs index 881995c84..c883eaa9a 100644 --- a/crates/oxc_cli/src/lint/mod.rs +++ b/crates/oxc_cli/src/lint/mod.rs @@ -111,7 +111,8 @@ impl Runner for LintRunner { }; let lint_service = LintService::new(cwd, &paths, linter); - let diagnostic_service = Self::get_diagnostic_service(&warning_options, &output_options); + let mut diagnostic_service = + Self::get_diagnostic_service(&warning_options, &output_options); // Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run. rayon::spawn({ diff --git a/crates/oxc_diagnostics/src/reporter.rs b/crates/oxc_diagnostics/src/reporter.rs index 804bd6597..bb61495fa 100644 --- a/crates/oxc_diagnostics/src/reporter.rs +++ b/crates/oxc_diagnostics/src/reporter.rs @@ -1,26 +1,75 @@ -use std::fmt; +use std::io::{BufWriter, Stdout, Write}; -use crate::{miette::JSONReportHandler, Diagnostic, GraphicalReportHandler}; +use crate::{ + miette::{Error, JSONReportHandler}, + GraphicalReportHandler, +}; #[allow(clippy::large_enum_variant)] // Lerge size is fine because this is a singleton #[derive(Debug)] #[non_exhaustive] pub enum DiagnosticReporter { - Graphical(GraphicalReportHandler), // 288 bytes - Json(JSONReportHandler), -} - -impl Default for DiagnosticReporter { - fn default() -> Self { - Self::Graphical(GraphicalReportHandler::new()) - } + // stdio is blocked by LineWriter, use a BufWriter to reduce syscalls. + // See `https://github.com/rust-lang/rust/issues/60673`. + Graphical { handler: GraphicalReportHandler, writer: BufWriter }, + Json { diagnostics: Vec }, } impl DiagnosticReporter { - pub fn render_report(&self, f: &mut T, diagnostic: &(dyn Diagnostic)) { + pub fn new_graphical() -> Self { + Self::Graphical { + handler: GraphicalReportHandler::new(), + writer: BufWriter::new(std::io::stdout()), + } + } + + pub fn new_json() -> Self { + Self::Json { diagnostics: vec![] } + } + + pub fn finish(&mut self) { match self { - Self::Graphical(handler) => handler.render_report(f, diagnostic).unwrap(), - Self::Json(handler) => handler.render_report(f, diagnostic).unwrap(), + Self::Graphical { writer, .. } => { + writer.flush().unwrap(); + } + // NOTE: this output does not conform to eslint json format yet + // https://eslint.org/docs/latest/use/formatters/#json + Self::Json { diagnostics } => { + let handler = JSONReportHandler::new(); + let messages = diagnostics + .drain(..) + .map(|error| { + let mut output = String::from("\t"); + handler.render_report(&mut output, error.as_ref()).unwrap(); + output + }) + .collect::>() + .join(",\n"); + println!("[\n{messages}\n]"); + } + } + } + + pub fn render_diagnostics(&mut self, s: &[u8]) { + match self { + Self::Graphical { writer, .. } => { + writer.write_all(s).unwrap(); + } + Self::Json { .. } => {} + } + } + + pub fn render_error(&mut self, error: Error) -> Option { + match self { + Self::Graphical { handler, .. } => { + let mut output = String::new(); + handler.render_report(&mut output, error.as_ref()).unwrap(); + Some(output) + } + Self::Json { diagnostics } => { + diagnostics.push(error); + None + } } } } diff --git a/crates/oxc_diagnostics/src/service.rs b/crates/oxc_diagnostics/src/service.rs index 0db87843a..06d5ebd3c 100644 --- a/crates/oxc_diagnostics/src/service.rs +++ b/crates/oxc_diagnostics/src/service.rs @@ -1,14 +1,11 @@ use std::{ cell::Cell, - io::{BufWriter, Write}, path::{Path, PathBuf}, sync::{mpsc, Arc}, }; use crate::{ - miette::{JSONReportHandler, NamedSource}, - reporter::DiagnosticReporter, - Error, MinifiedFileError, Severity, + miette::NamedSource, reporter::DiagnosticReporter, Error, MinifiedFileError, Severity, }; pub type DiagnosticTuple = (PathBuf, Vec); @@ -39,7 +36,7 @@ impl Default for DiagnosticService { fn default() -> Self { let (sender, receiver) = mpsc::channel(); Self { - reporter: DiagnosticReporter::default(), + reporter: DiagnosticReporter::new_graphical(), quiet: false, max_warnings: None, warnings_count: Cell::new(0), @@ -52,7 +49,7 @@ impl Default for DiagnosticService { impl DiagnosticService { pub fn set_json_reporter(&mut self) { - self.reporter = DiagnosticReporter::Json(JSONReportHandler::new()); + self.reporter = DiagnosticReporter::new_json(); } #[must_use] @@ -99,9 +96,7 @@ impl DiagnosticService { /// # Panics /// /// * When the writer fails to write - pub fn run(&self) { - let mut buf_writer = BufWriter::new(std::io::stdout()); - + pub fn run(&mut self) { while let Ok(Some((path, diagnostics))) = self.receiver.recv() { let mut output = String::new(); for diagnostic in diagnostics { @@ -124,20 +119,20 @@ impl DiagnosticService { } } - let mut err = String::new(); - self.reporter.render_report(&mut err, diagnostic.as_ref()); - // Skip large output and print only once - if err.lines().any(|line| line.len() >= 400) { - let minified_diagnostic = Error::new(MinifiedFileError(path.clone())); - err = format!("{minified_diagnostic:?}"); - output = err; - break; + if let Some(mut err_str) = self.reporter.render_error(diagnostic) { + // Skip large output and print only once + if err_str.lines().any(|line| line.len() >= 400) { + let minified_diagnostic = Error::new(MinifiedFileError(path.clone())); + err_str = format!("{minified_diagnostic:?}"); + output = err_str; + break; + } + output.push_str(&err_str); } - output.push_str(&err); } - buf_writer.write_all(output.as_bytes()).unwrap(); + self.reporter.render_diagnostics(output.as_bytes()); } - buf_writer.flush().unwrap(); + self.reporter.finish(); } }