feat(cli): implement --format unix (#3039)

closes #1462
This commit is contained in:
Boshen 2024-04-21 01:24:22 +08:00 committed by GitHub
parent ae1f15ac1e
commit 4425b961cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 81 additions and 19 deletions

View file

@ -144,6 +144,7 @@ pub struct OutputOptions {
pub enum OutputFormat { pub enum OutputFormat {
Default, Default,
Json, Json,
Unix,
} }
impl FromStr for OutputFormat { impl FromStr for OutputFormat {
@ -152,6 +153,7 @@ impl FromStr for OutputFormat {
match s { match s {
"json" => Ok(Self::Json), "json" => Ok(Self::Json),
"default" => Ok(Self::Default), "default" => Ok(Self::Default),
"unix" => Ok(Self::Unix),
_ => Err(format!("'{s}' is not a known format")), _ => Err(format!("'{s}' is not a known format")),
} }
} }

View file

@ -163,6 +163,7 @@ impl LintRunner {
match output_options.format { match output_options.format {
OutputFormat::Default => {} OutputFormat::Default => {}
OutputFormat::Json => diagnostic_service.set_json_reporter(), OutputFormat::Json => diagnostic_service.set_json_reporter(),
OutputFormat::Unix => diagnostic_service.set_unix_reporter(),
} }
diagnostic_service diagnostic_service

View file

@ -2,31 +2,37 @@ use std::io::{BufWriter, Stdout, Write};
use crate::{ use crate::{
miette::{Error, JSONReportHandler}, 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<Stdout> {
BufWriter::new(std::io::stdout())
}
#[allow(clippy::large_enum_variant)] // Lerge size is fine because this is a singleton #[allow(clippy::large_enum_variant)] // Lerge size is fine because this is a singleton
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum DiagnosticReporter { 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<Stdout> }, Graphical { handler: GraphicalReportHandler, writer: BufWriter<Stdout> },
Json { diagnostics: Vec<Error> }, Json { diagnostics: Vec<Error> },
Unix { total: usize, writer: BufWriter<Stdout> },
} }
impl DiagnosticReporter { impl DiagnosticReporter {
pub fn new_graphical() -> Self { pub fn new_graphical() -> Self {
Self::Graphical { Self::Graphical { handler: GraphicalReportHandler::new(), writer: writer() }
handler: GraphicalReportHandler::new(),
writer: BufWriter::new(std::io::stdout()),
}
} }
pub fn new_json() -> Self { pub fn new_json() -> Self {
Self::Json { diagnostics: vec![] } Self::Json { diagnostics: vec![] }
} }
pub fn new_unix() -> Self {
Self::Unix { total: 0, writer: writer() }
}
pub fn finish(&mut self) { pub fn finish(&mut self) {
match self { match self {
Self::Graphical { writer, .. } => { Self::Graphical { writer, .. } => {
@ -35,24 +41,21 @@ impl DiagnosticReporter {
// NOTE: this output does not conform to eslint json format yet // NOTE: this output does not conform to eslint json format yet
// https://eslint.org/docs/latest/use/formatters/#json // https://eslint.org/docs/latest/use/formatters/#json
Self::Json { diagnostics } => { Self::Json { diagnostics } => {
let handler = JSONReportHandler::new(); format_json(diagnostics);
let messages = diagnostics }
.drain(..) Self::Unix { total, writer } => {
.map(|error| { if *total > 0 {
let mut output = String::from("\t"); let line = format!("\n{total} problem{}\n", if *total > 1 { "s" } else { "" });
handler.render_report(&mut output, error.as_ref()).unwrap(); writer.write_all(line.as_bytes()).unwrap();
output }
}) writer.flush().unwrap();
.collect::<Vec<_>>()
.join(",\n");
println!("[\n{messages}\n]");
} }
} }
} }
pub fn render_diagnostics(&mut self, s: &[u8]) { pub fn render_diagnostics(&mut self, s: &[u8]) {
match self { match self {
Self::Graphical { writer, .. } => { Self::Graphical { writer, .. } | Self::Unix { writer, .. } => {
writer.write_all(s).unwrap(); writer.write_all(s).unwrap();
} }
Self::Json { .. } => {} Self::Json { .. } => {}
@ -70,6 +73,58 @@ impl DiagnosticReporter {
diagnostics.push(error); diagnostics.push(error);
None None
} }
Self::Unix { total: count, .. } => {
*count += 1;
Some(format_unix(&error))
}
} }
} }
} }
/// <https://github.com/fregante/eslint-formatters/tree/main/packages/eslint-formatter-json>
fn format_json(diagnostics: &mut Vec<Error>) {
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]");
}
/// <https://github.com/fregante/eslint-formatters/tree/main/packages/eslint-formatter-unix>
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")
}

View file

@ -52,6 +52,10 @@ impl DiagnosticService {
self.reporter = DiagnosticReporter::new_json(); self.reporter = DiagnosticReporter::new_json();
} }
pub fn set_unix_reporter(&mut self) {
self.reporter = DiagnosticReporter::new_unix();
}
pub fn is_graphical_output(&self) -> bool { pub fn is_graphical_output(&self) -> bool {
matches!(self.reporter, DiagnosticReporter::Graphical { .. }) matches!(self.reporter, DiagnosticReporter::Graphical { .. })
} }