From c98c20f766e7ad6b03c80aec60cae60d1d2895fe Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 1 Aug 2023 16:47:59 +0800 Subject: [PATCH] Add rdjson foramt support for lint result, then we can use reviewdog. https://github.com/reviewdog/reviewdog/tree/master/proto/rdf --- autocorrect-cli/src/cli.rs | 5 +- autocorrect-cli/src/lib.rs | 36 +++-- autocorrect/src/lib.rs | 2 +- autocorrect/src/result/json.rs | 53 +++++++ autocorrect/src/{result.rs => result/mod.rs} | 2 + autocorrect/src/result/rdjson.rs | 152 +++++++++++++++++++ 6 files changed, 229 insertions(+), 21 deletions(-) create mode 100644 autocorrect/src/result/json.rs rename autocorrect/src/{result.rs => result/mod.rs} (99%) create mode 100644 autocorrect/src/result/rdjson.rs diff --git a/autocorrect-cli/src/cli.rs b/autocorrect-cli/src/cli.rs index edaa6300..06dc9aee 100644 --- a/autocorrect-cli/src/cli.rs +++ b/autocorrect-cli/src/cli.rs @@ -5,13 +5,10 @@ use clap::{Parser, Subcommand, ValueEnum}; pub(crate) enum OutputFormatter { Diff, Json, + Rdjson, } impl OutputFormatter { - pub fn is_json(&self) -> bool { - *self == OutputFormatter::Json - } - pub fn is_diff(&self) -> bool { *self == OutputFormatter::Diff } diff --git a/autocorrect-cli/src/lib.rs b/autocorrect-cli/src/lib.rs index 5c0bd1b4..5fca3495 100644 --- a/autocorrect-cli/src/lib.rs +++ b/autocorrect-cli/src/lib.rs @@ -1,3 +1,4 @@ +use autocorrect::LintResult; // autocorrect: false use clap::Parser; use std::ffi::OsString; @@ -97,7 +98,7 @@ where // calc run time let start_t = SystemTime::now(); - let mut lint_results: Vec = Vec::new(); + let mut lint_results: Vec = Vec::new(); let lint_errors_count = std::sync::Arc::new(std::sync::Mutex::new(0)); let lint_warnings_count = std::sync::Arc::new(std::sync::Mutex::new(0)); @@ -184,7 +185,7 @@ where Ok(raw) => { bench!(format!("Done {filepath}"), { if cli.lint { - let mut lint_results: Vec = Vec::new(); + let mut lint_results: Vec = Vec::new(); let mut _err_count = 0; let mut _warn_count = 0; @@ -226,18 +227,14 @@ where log::debug!("Lint result found: {} issues.", lint_results.len()); if cli.lint { - if cli.formatter.is_json() { - log::info!( - r#"{{"count": {},"messages": [{}]}}"#, - lint_results.len(), - lint_results.join(",") - ); - } else { + if cli.formatter.is_diff() { let _err_count = *lint_errors_count.lock().unwrap(); let _warn_count = *lint_warnings_count.lock().unwrap(); log::info!("\n"); - log::info!("{}", lint_results.join("")); + lint_results.iter().for_each(|lint_result| { + log::info!("{}", lint_result.to_diff(cli.no_diff_bg_color)) + }); log::info!( "{}, {}\n", @@ -252,6 +249,15 @@ where // Exit with code = 1 std::process::exit(1); } + } else { + if cli.formatter == cli::OutputFormatter::Json { + log::info!("{}", autocorrect::json::to_lint_results_json(lint_results)); + } else { + log::info!( + "{}", + autocorrect::rdjson::to_lint_results_rdjson(lint_results) + ) + } } } else if cli.fix { log::info!("\n"); @@ -327,7 +333,7 @@ fn lint_and_output( filetype: &str, raw: &str, cli: &Cli, - results: &mut Vec, + results: &mut Vec, errors_count: &mut usize, warings_count: &mut usize, ) { @@ -352,14 +358,12 @@ fn lint_and_output( } } - if diff_mode { + if cli.formatter.is_diff() { if result.has_error() { log::debug!("{}\n{}", filepath, result.error); return; } - - results.push(result.to_diff(cli.no_diff_bg_color)); - } else { - results.push(result.to_json()); } + + results.push(result.clone()); } diff --git a/autocorrect/src/lib.rs b/autocorrect/src/lib.rs index ffdf88a8..3e48f6c3 100644 --- a/autocorrect/src/lib.rs +++ b/autocorrect/src/lib.rs @@ -135,7 +135,7 @@ pub mod ignorer; pub use code::{format_for, get_file_extension, is_support_type, lint_for}; pub use config::Config; pub use format::*; -pub use result::{FormatResult, LineResult, LintResult}; +pub use result::{json, rdjson, FormatResult, LineResult, LintResult}; pub use rule::{halfwidth, spellcheck}; #[cfg(test)] diff --git a/autocorrect/src/result/json.rs b/autocorrect/src/result/json.rs new file mode 100644 index 00000000..a9a47511 --- /dev/null +++ b/autocorrect/src/result/json.rs @@ -0,0 +1,53 @@ +//! AutoCorrect Lint JSON +use crate::LintResult; + +#[doc(hidden)] +pub fn to_lint_results_json(lint_results: Vec) -> String { + format!( + r#"{{"count": {},"messages": [{}]}}"#, + lint_results.len(), + lint_results + .iter() + .map(|r| r.to_json()) + .collect::>() + .join(",") + ) +} + +#[cfg(test)] +pub(crate) fn crate_test_lint_results() -> Vec { + use crate::result::{LineResult, Results, Severity}; + + let mut lint_result = LintResult::new("hello你好.\n这是第2行"); + lint_result.filepath = "test/foo/bar.rs".to_owned(); + lint_result.push(LineResult { + line: 1, + col: 1, + new: "hello 你好。".to_owned(), + old: "hello你好.".to_owned(), + severity: Severity::Error, + }); + lint_result.push(LineResult { + line: 2, + col: 1, + new: "这是第 2 行".to_owned(), + old: "这是第2行".to_owned(), + severity: Severity::Error, + }); + + vec![lint_result] +} + +#[cfg(test)] +mod tests { + #[test] + fn test_to_lint_results_json() { + let json = super::to_lint_results_json(crate::result::json::crate_test_lint_results()); + + let expected = r#"{"count": 1,"messages": [{"filepath":"test/foo/bar.rs","lines":[{"l":1,"c":1,"new":"hello 你好。","old":"hello你好.","severity":1},{"l":2,"c":1,"new":"这是第 2 行","old":"这是第2行","severity":1}],"error":""}]}"#; + if expected != json { + println!("--------------- json:\n{}", json); + } + assert_json_eq!(expected, json); + } +} diff --git a/autocorrect/src/result.rs b/autocorrect/src/result/mod.rs similarity index 99% rename from autocorrect/src/result.rs rename to autocorrect/src/result/mod.rs index c85a2ef4..6087bb47 100644 --- a/autocorrect/src/result.rs +++ b/autocorrect/src/result/mod.rs @@ -1,3 +1,5 @@ +pub mod json; +pub mod rdjson; use serde::{Deserialize, Serialize}; use serde_repr::*; diff --git a/autocorrect/src/result/rdjson.rs b/autocorrect/src/result/rdjson.rs new file mode 100644 index 00000000..55c7b20f --- /dev/null +++ b/autocorrect/src/result/rdjson.rs @@ -0,0 +1,152 @@ +//! Rdjson format for reviewdog +//! https://github.com/reviewdog/reviewdog/tree/master/proto/rdf +use super::LintResult; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +struct RdfJson { + source: RdfSource, + severity: String, + diagnostics: String, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfSource { + name: String, + url: String, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfDiagnostic { + message: String, + severity: String, + code: RdfCode, + location: RdfLocation, + suggestions: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfLocation { + path: String, + range: RdfRange, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfRange { + start: Option, + end: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfLineColumn { + line: usize, + column: usize, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfSuggetion { + text: String, + range: RdfRange, +} + +#[derive(Serialize, Deserialize, Clone)] +struct RdfCode { + value: Option, + url: String, +} + +fn to_severity_str(severity: super::Severity) -> String { + match severity { + super::Severity::Error => "ERROR".to_owned(), + super::Severity::Warning => "WARNING".to_owned(), + super::Severity::Pass => "PASS".to_owned(), + } +} + +/// RDF JSONSchema +/// https://github.com/reviewdog/reviewdog/blob/master/proto/rdf/jsonschema/Diagnostic.jsonschema +#[doc(hidden)] +pub(crate) fn to_rdjson_diagnostic(lint_result: &LintResult, pretty: bool) -> String { + let range = RdfRange { + start: Some(RdfLineColumn { + line: lint_result.line, + column: lint_result.col, + }), + end: None, + }; + + let mut rdf_diagnostic: RdfDiagnostic = RdfDiagnostic { + message: "".to_owned(), + location: RdfLocation { + path: lint_result.filepath.clone(), + range: range.clone(), + }, + severity: "UNKNOWN_SEVERITY".to_owned(), + code: RdfCode { + value: Some("AutoCorrect".to_owned()), + url: "https://github.com/huacnlee/autocorrect".to_owned(), + }, + suggestions: vec![], + }; + + lint_result.lines.iter().for_each(|line_result| { + if rdf_diagnostic.severity == "UNKNOWN_SEVERITY" { + rdf_diagnostic.severity = to_severity_str(line_result.severity); + } + + let suggestion = RdfSuggetion { + text: line_result.new.clone(), + range: RdfRange { + start: Some(RdfLineColumn { + line: line_result.line, + column: line_result.col, + }), + end: Some(RdfLineColumn { + line: line_result.line + line_result.old.split("\n").count() - 1, + column: line_result.col + + line_result + .old + .split("\n") + .last() + .unwrap_or("") + .chars() + .count(), + }), + }, + }; + + rdf_diagnostic.suggestions.push(suggestion); + }); + + if pretty { + serde_json::to_string_pretty(&rdf_diagnostic).unwrap() + } else { + serde_json::to_string(&rdf_diagnostic).unwrap() + } +} + +#[doc(hidden)] +pub fn to_lint_results_rdjson(lint_results: Vec) -> String { + let diagnostics = lint_results + .iter() + .map(|r| to_rdjson_diagnostic(r, false)) + .collect::>() + .join(","); + format!( + r#"{{"source":{{"name":"AutoCorrect Lint","url": "https://github.com/huacnlee/autocorrect"}},"diagnostics": [{diagnostics}]}}"#, + ) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_to_lint_results_rdjson() { + let rdjson = super::to_lint_results_rdjson(crate::result::json::crate_test_lint_results()); + + let expected = r#"{"source":{"name":"AutoCorrect Lint","url": "https://github.com/huacnlee/autocorrect"},"diagnostics": [{"message":"","severity":"ERROR","code":{"value":"autocorrect","url":"https://github.com/huacnlee/autocorrect"},"location":{"path":"test/foo/bar.rs","range":{"start":{"line":1,"column":1},"end":{"line":1,"column":25}}},"suggestions":[{"text":"hello 你好。","range":{"start":{"line":1,"column":1},"end":{"line":0,"column":11}}},{"text":"这是第 2 行","range":{"start":{"line":2,"column":1},"end":{"line":0,"column":12}}}]}]}"#; + if expected != rdjson { + println!("--------------- rdjson:\n{}", rdjson); + } + assert_json_eq!(expected, rdjson); + } +}