From 56ee7a2d48435bd2ba9c7347597d7b756c80b6fa Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Jul 2024 03:19:02 +0800 Subject: [PATCH] feat: implement sarif format reporter --- Cargo.lock | 207 ++++++++++++++++--- crates/typos-cli/Cargo.toml | 1 + crates/typos-cli/src/bin/typos-cli/args.rs | 2 + crates/typos-cli/src/bin/typos-cli/main.rs | 7 + crates/typos-cli/src/bin/typos-cli/report.rs | 153 +++++++++++++- crates/typos-cli/src/report.rs | 4 + crates/typos-cli/tests/cmd/help.toml | 2 +- 7 files changed, 343 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b644f22e..0794a7bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -123,7 +133,7 @@ checksum = "edf3ee19dbc0a46d740f6f0926bde8c50f02bdbc7b536842da28f6ac56513a8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -208,7 +218,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim", "terminal_size", ] @@ -221,7 +231,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -347,9 +357,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -357,27 +367,58 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn", + "strsim", + "syn 2.0.66", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.66", +] + +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.66", ] [[package]] @@ -390,7 +431,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.66", ] [[package]] @@ -402,7 +443,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -449,7 +490,7 @@ checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -502,7 +543,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -729,6 +770,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.149" @@ -871,7 +918,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.66", "unicase", ] @@ -913,6 +960,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.66", +] + [[package]] name = "proc-exit" version = "2.0.1" @@ -921,9 +978,9 @@ checksum = "dbd05acff31c8b626109e8e1db042b9bddb2b3ffba01ead93d15b78517a282ca" [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1044,6 +1101,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.15" @@ -1059,6 +1122,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemafy_core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bec29dddcfe60f92f3c0d422707b8b56473983ef0481df8d5236ed3ab8fdf24" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "schemafy_lib" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3d87f1df246a9b7e2bfd1f4ee5f88e48b11ef9cfc62e63f0dead255b1a6f5f" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "schemafy_core", + "serde", + "serde_derive", + "serde_json", + "syn 1.0.109", + "uriparse", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1080,6 +1170,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-sarif" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38c5e5bbaa10cc256774ea394ad62968c31c0e3c3265f65221e02c87dd1a914" +dependencies = [ + "anyhow", + "derive_builder", + "prettyplease", + "proc-macro2", + "quote", + "schemafy_lib", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn 2.0.66", + "thiserror", +] + [[package]] name = "serde_derive" version = "1.0.203" @@ -1088,14 +1198,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -1147,9 +1257,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "snapbox" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb" +checksum = "699c824ef8c2061c39efb3af4f334310b3acbfb2a50c6d1f867e4d95dcff94be" dependencies = [ "anstream", "anstyle", @@ -1184,15 +1294,39 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "strsim" -version = "0.11.0" +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.66", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" @@ -1251,7 +1385,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] [[package]] @@ -1367,6 +1501,7 @@ dependencies = [ "proc-exit", "regex", "serde", + "serde-sarif", "serde_json", "serde_regex", "snapbox", @@ -1487,6 +1622,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "utf8parse" version = "0.2.1" @@ -1750,5 +1895,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.66", ] diff --git a/crates/typos-cli/Cargo.toml b/crates/typos-cli/Cargo.toml index e0936ec5e..0f92f555f 100644 --- a/crates/typos-cli/Cargo.toml +++ b/crates/typos-cli/Cargo.toml @@ -77,6 +77,7 @@ colorchoice-clap = "1.0.3" serde_regex = "1.1.0" regex = "1.10.4" encoding_rs = "0.8.34" +serde-sarif = "0.5.0" [dev-dependencies] assert_fs = "1.1" diff --git a/crates/typos-cli/src/bin/typos-cli/args.rs b/crates/typos-cli/src/bin/typos-cli/args.rs index 3541533cf..a1a7f02d9 100644 --- a/crates/typos-cli/src/bin/typos-cli/args.rs +++ b/crates/typos-cli/src/bin/typos-cli/args.rs @@ -10,6 +10,7 @@ pub(crate) enum Format { #[default] Long, Json, + Sarif, } impl Format { @@ -19,6 +20,7 @@ impl Format { Format::Brief => Box::new(crate::report::PrintBrief), Format::Long => Box::new(crate::report::PrintLong), Format::Json => Box::new(crate::report::PrintJson), + Format::Sarif => Box::new(crate::report::PrintSarif::default()), } } } diff --git a/crates/typos-cli/src/bin/typos-cli/main.rs b/crates/typos-cli/src/bin/typos-cli/main.rs index a63af8046..3ae6dbac4 100644 --- a/crates/typos-cli/src/bin/typos-cli/main.rs +++ b/crates/typos-cli/src/bin/typos-cli/main.rs @@ -8,6 +8,8 @@ mod report; use proc_exit::prelude::*; +use typos_cli::report::Report; + fn main() { human_panic::setup_panic!(); let result = run(); @@ -316,6 +318,11 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { if status_reporter.errors_found() { errors_found = true; } + + if let Err(err) = status_reporter.generate_final_result() { + log::error!("generate final result: {}", err); + return proc_exit::Code::FAILURE.ok(); + } } if errors_found { diff --git a/crates/typos-cli/src/bin/typos-cli/report.rs b/crates/typos-cli/src/bin/typos-cli/report.rs index 45557226b..0302fe4b6 100644 --- a/crates/typos-cli/src/bin/typos-cli/report.rs +++ b/crates/typos-cli/src/bin/typos-cli/report.rs @@ -1,9 +1,10 @@ #![allow(clippy::needless_update)] use std::io::Write as _; -use std::sync::atomic; +use std::sync::{atomic, Mutex}; use anstream::stdout; +use serde_sarif::sarif; use unicode_width::UnicodeWidthStr; use typos_cli::report::{Context, Message, Report, Typo}; @@ -46,6 +47,10 @@ impl<'r> Report for MessageStatus<'r> { } self.reporter.report(msg) } + + fn generate_final_result(&self) -> Result<(), std::io::Error> { + self.reporter.generate_final_result() + } } #[derive(Debug, Default)] @@ -280,6 +285,152 @@ impl Report for PrintJson { } } +#[derive(Debug)] +pub(crate) struct PrintSarif { + results: Mutex>, + error: Mutex>, +} + +impl Default for PrintSarif { + fn default() -> Self { + Self { + results: Mutex::new(Vec::new()), + error: Mutex::new(Vec::new()), + } + } +} + +impl Report for PrintSarif { + fn report(&self, msg: Message<'_>) -> Result<(), std::io::Error> { + self.report_sarif(msg).map_err(sarif_error_mapper) + } + + fn generate_final_result(&self) -> Result<(), std::io::Error> { + self.generate_final_result().map_err(sarif_error_mapper) + } +} + +impl PrintSarif { + fn report_sarif(&self, msg: Message<'_>) -> Result<(), Box> { + match &msg { + Message::Typo(msg) => { + let message = match &msg.corrections { + typos::Status::Valid => { + return Ok(()); + } + typos::Status::Invalid => { + format!("`{}` is disallowed", msg.typo) + } + typos::Status::Corrections(corrections) => { + format!("`{}` should be {}", msg.typo, itertools::join( + corrections.iter().map(|s| format!("`{}`", s)), + ", ", + )) + } + }; + + let start = String::from_utf8_lossy(&msg.buffer[0..msg.byte_offset]); + let column_start = + unicode_segmentation::UnicodeSegmentation::graphemes(start.as_ref(), true).count() + 1; + let column_end = + unicode_segmentation::UnicodeSegmentation::graphemes(msg.typo, true).count() + column_start; + let line_num = msg.context.as_ref().map(|context| match context { + Context::File(context) => context.line_num, + _ => 1, + }).unwrap_or(1); + + let location = sarif::LocationBuilder::default() + .physical_location(sarif::PhysicalLocationBuilder::default() + .artifact_location( + sarif::ArtifactLocationBuilder::default() + .uri(msg.context.as_ref().map(|context| match context { + Context::File(context) => context.path.to_str().unwrap_or(""), + _ => "", + }).unwrap_or("")) + .build()? + ) + .region( + sarif::RegionBuilder::default() + .start_line(line_num as i64) + .end_line(line_num as i64) + .start_column(column_start as i64) + .end_column(column_end as i64) + .build()? + ) + .build()?) + .build()?; + + + let result = sarif::ResultBuilder::default() + .level(sarif::ResultLevel::Error.to_string()) + .message(sarif::MessageBuilder::default() + .markdown(message) + .build()?) + .locations(vec![location]) + .build()?; + + self.results.lock().unwrap().push(result); + } + Message::Parse(msg) => { + self.error.lock().unwrap().push(msg.data.to_owned()); + } + Message::Error(msg) => { + self.error.lock().unwrap().push(msg.msg.clone()); + } + Message::BinaryFile(_) => {} + Message::FileType(_) => {} + Message::File(_) => {} + _ => unimplemented!("New message {:?}", msg), + } + + Ok(()) + } + + fn generate_final_result(&self) -> Result<(), Box> { + let mut sarif_builder = sarif::SarifBuilder::default(); + sarif_builder.version(sarif::Version::V2_1_0.to_string()) + .schema(sarif::SCHEMA_URL); + + + let tool = sarif::ToolBuilder::default() + .driver(sarif::ToolComponentBuilder::default() + .name("typos") + .information_uri("https://github.com/crate-ci/typos") + .build()?) + .build()?; + + let mut run_builder = sarif::RunBuilder::default(); + run_builder + .tool(tool) + .column_kind(sarif::ResultColumnKind::UnicodeCodePoints.to_string()) + .results(self.results.lock().unwrap().clone()); + + if !self.error.lock().unwrap().is_empty() { + let invocations = self.error.lock().unwrap().iter().map(|x| { + sarif::InvocationBuilder::default() + .process_start_failure_message(x.clone()) + .build() + }).collect::, _>>(); + + if let Err(e) = invocations { + return Err(e.into()); + } + + run_builder.invocations(invocations.unwrap()); + } + + let sarif = sarif_builder.build()?; + + serde_json::to_writer_pretty(stdout().lock(), &sarif)?; + + Ok(()) + } +} + +fn sarif_error_mapper(error: impl std::fmt::Display) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::Other, format!("failed to generate SARIF output: {error}")) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/typos-cli/src/report.rs b/crates/typos-cli/src/report.rs index c6f50fa0f..a805ccdf1 100644 --- a/crates/typos-cli/src/report.rs +++ b/crates/typos-cli/src/report.rs @@ -4,6 +4,10 @@ use std::borrow::Cow; pub trait Report: Send + Sync { fn report(&self, msg: Message<'_>) -> Result<(), std::io::Error>; + + fn generate_final_result(&self) -> Result<(), std::io::Error> { + Ok(()) + } } #[derive(Clone, Debug, serde::Serialize, derive_more::From)] diff --git a/crates/typos-cli/tests/cmd/help.toml b/crates/typos-cli/tests/cmd/help.toml index 772f09f48..c02facdbb 100644 --- a/crates/typos-cli/tests/cmd/help.toml +++ b/crates/typos-cli/tests/cmd/help.toml @@ -45,7 +45,7 @@ Mode: Output: --format Render style for messages [default: long] [possible values: silent, brief, - long, json] + long, json, sarif] --color Controls when to use color [default: auto] [possible values: auto, always, never] -v, --verbose... Increase logging verbosity