From 4425b961cdfa416bb8e6014b068b53b714a68935 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 21 Apr 2024 01:24:22 +0800 Subject: [PATCH] feat(cli): implement `--format unix` (#3039) closes #1462 --- crates/oxc_cli/src/command/lint.rs | 2 + crates/oxc_cli/src/lint/mod.rs | 1 + crates/oxc_diagnostics/src/reporter.rs | 93 ++++++++++++++++++++------ crates/oxc_diagnostics/src/service.rs | 4 ++ 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/crates/oxc_cli/src/command/lint.rs b/crates/oxc_cli/src/command/lint.rs index 2f1e2a53b..0bd39c07e 100644 --- a/crates/oxc_cli/src/command/lint.rs +++ b/crates/oxc_cli/src/command/lint.rs @@ -144,6 +144,7 @@ pub struct OutputOptions { pub enum OutputFormat { Default, Json, + Unix, } impl FromStr for OutputFormat { @@ -152,6 +153,7 @@ impl FromStr for OutputFormat { match s { "json" => Ok(Self::Json), "default" => Ok(Self::Default), + "unix" => Ok(Self::Unix), _ => Err(format!("'{s}' is not a known format")), } } diff --git a/crates/oxc_cli/src/lint/mod.rs b/crates/oxc_cli/src/lint/mod.rs index 01b62ccfc..f79f35dfd 100644 --- a/crates/oxc_cli/src/lint/mod.rs +++ b/crates/oxc_cli/src/lint/mod.rs @@ -163,6 +163,7 @@ impl LintRunner { match output_options.format { OutputFormat::Default => {} OutputFormat::Json => diagnostic_service.set_json_reporter(), + OutputFormat::Unix => diagnostic_service.set_unix_reporter(), } diagnostic_service diff --git a/crates/oxc_diagnostics/src/reporter.rs b/crates/oxc_diagnostics/src/reporter.rs index bb61495fa..a18061f7a 100644 --- a/crates/oxc_diagnostics/src/reporter.rs +++ b/crates/oxc_diagnostics/src/reporter.rs @@ -2,31 +2,37 @@ use std::io::{BufWriter, Stdout, Write}; use crate::{ miette::{Error, JSONReportHandler}, - GraphicalReportHandler, + GraphicalReportHandler, Severity, }; +/// stdio is blocked by LineWriter, use a BufWriter to reduce syscalls. +/// See `https://github.com/rust-lang/rust/issues/60673`. +fn writer() -> BufWriter { + BufWriter::new(std::io::stdout()) +} + #[allow(clippy::large_enum_variant)] // Lerge size is fine because this is a singleton #[derive(Debug)] #[non_exhaustive] pub enum DiagnosticReporter { - // 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 }, + Unix { total: usize, writer: BufWriter }, } impl DiagnosticReporter { pub fn new_graphical() -> Self { - Self::Graphical { - handler: GraphicalReportHandler::new(), - writer: BufWriter::new(std::io::stdout()), - } + Self::Graphical { handler: GraphicalReportHandler::new(), writer: writer() } } pub fn new_json() -> Self { Self::Json { diagnostics: vec![] } } + pub fn new_unix() -> Self { + Self::Unix { total: 0, writer: writer() } + } + pub fn finish(&mut self) { match self { Self::Graphical { writer, .. } => { @@ -35,24 +41,21 @@ impl DiagnosticReporter { // 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]"); + format_json(diagnostics); + } + Self::Unix { total, writer } => { + if *total > 0 { + let line = format!("\n{total} problem{}\n", if *total > 1 { "s" } else { "" }); + writer.write_all(line.as_bytes()).unwrap(); + } + writer.flush().unwrap(); } } } pub fn render_diagnostics(&mut self, s: &[u8]) { match self { - Self::Graphical { writer, .. } => { + Self::Graphical { writer, .. } | Self::Unix { writer, .. } => { writer.write_all(s).unwrap(); } Self::Json { .. } => {} @@ -70,6 +73,58 @@ impl DiagnosticReporter { diagnostics.push(error); None } + Self::Unix { total: count, .. } => { + *count += 1; + Some(format_unix(&error)) + } } } } + +/// +fn format_json(diagnostics: &mut Vec) { + 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]"); +} + +/// +fn format_unix(diagnostic: &Error) -> String { + let mut line = 0; + let mut column = 0; + let mut filename = String::new(); + let mut message = String::new(); + let mut severity = "Warning"; + let mut rule_id = String::new(); + if let Some(mut labels) = diagnostic.labels() { + if let Some(source) = diagnostic.source_code() { + if let Some(label) = labels.next() { + if let Ok(span_content) = source.read_span(label.inner(), 0, 0) { + line = span_content.line() + 1; + column = span_content.column() + 1; + if let Some(name) = span_content.name() { + filename = name.to_string(); + }; + if matches!(diagnostic.severity(), Some(Severity::Error)) { + severity = "Warning"; + } + let msg = diagnostic.to_string(); + // Our messages usually comes with `eslint(rule): message` + (rule_id, message) = msg.split_once(':').map_or_else( + || (String::new(), msg.to_string()), + |(id, msg)| (id.to_string(), msg.trim().to_string()), + ); + } + } + } + } + format!("{filename}:{line}:{column}: {message} [{severity}/{rule_id}]\n") +} diff --git a/crates/oxc_diagnostics/src/service.rs b/crates/oxc_diagnostics/src/service.rs index 0e9190be6..4fe96fdc1 100644 --- a/crates/oxc_diagnostics/src/service.rs +++ b/crates/oxc_diagnostics/src/service.rs @@ -52,6 +52,10 @@ impl DiagnosticService { self.reporter = DiagnosticReporter::new_json(); } + pub fn set_unix_reporter(&mut self) { + self.reporter = DiagnosticReporter::new_unix(); + } + pub fn is_graphical_output(&self) -> bool { matches!(self.reporter, DiagnosticReporter::Graphical { .. }) }