feat(diagnostics): implement json reporter (#2452)

This commit is contained in:
Boshen 2024-02-20 15:33:28 +08:00 committed by GitHub
parent 195d76e6a5
commit d0d0d9d7bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 34 deletions

View file

@ -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({

View file

@ -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<Stdout> },
Json { diagnostics: Vec<Error> },
}
impl DiagnosticReporter {
pub fn render_report<T: fmt::Write>(&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::<Vec<_>>()
.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<String> {
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
}
}
}
}

View file

@ -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<Error>);
@ -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();
}
}