feat: SARIF 2.1.0 and Checkstyle XML reports for check/fix
Some checks are pending
CI / lint (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
Some checks are pending
CI / lint (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
--report FILE on check and fix writes the findings a run already computed into CI-consumable formats chosen by extension: .sarif (single run, %SRCROOT%-relative URIs, properties.autoFixable, rule registry with descriptions) and .xml (Checkstyle, source=jmove.<rule>). Unknown suffixes fail fast with INVALID_ARGUMENT; stdout and exit codes are untouched, and clean runs write valid empty documents. The check handler moved into cli::report (its format siblings); fix emits the candidate list in dry-run and apply modes. Marketing angle from the plan: auto-fix for what Checkstyle only reports.
This commit is contained in:
parent
47395069cb
commit
ad1ad4f999
10 changed files with 726 additions and 56 deletions
122
tests/cli_report.rs
Normal file
122
tests/cli_report.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//! `--report <FILE>` interop: SARIF 2.1.0 and Checkstyle XML for CI/IDE
|
||||
//! consumers. The reports mirror the findings of `check`/`fix` without
|
||||
//! changing stdout or exit codes.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{copy_fixture, in_root, jmove, read};
|
||||
use predicates::prelude::*;
|
||||
|
||||
fn report_path(tmp: &tempfile::TempDir, name: &str) -> String {
|
||||
tmp.path()
|
||||
.join(name)
|
||||
.to_str()
|
||||
.expect("utf-8 path")
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn sarif(tmp: &tempfile::TempDir, name: &str) -> serde_json::Value {
|
||||
serde_json::from_str(&read(&tmp.path().join(name))).expect("valid sarif json")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_writes_sarif_and_keeps_the_findings_exit_code() {
|
||||
let tmp = copy_fixture("java", "mismatch");
|
||||
let report = report_path(&tmp, "out.sarif");
|
||||
jmove(&tmp, &["check", "--report", &report]).code(2);
|
||||
let doc = sarif(&tmp, "out.sarif");
|
||||
let result = &doc["runs"][0]["results"][0];
|
||||
assert_eq!(result["ruleId"], "java/class-name-mismatch");
|
||||
assert_eq!(result["level"], "error");
|
||||
assert!(
|
||||
result["message"]["text"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("jmove mv")
|
||||
);
|
||||
let location = &result["locations"][0]["physicalLocation"];
|
||||
assert_eq!(
|
||||
location["artifactLocation"]["uri"],
|
||||
"src/main/java/com/example/Bad.java"
|
||||
);
|
||||
assert_eq!(location["region"]["startLine"], 3);
|
||||
assert_eq!(result["properties"]["autoFixable"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_writes_checkstyle_for_broken_imports() {
|
||||
let tmp = copy_fixture("typescript", "complex");
|
||||
let report = report_path(&tmp, "checkstyle.xml");
|
||||
jmove(&tmp, &["check", "--report", &report]).code(2);
|
||||
let xml = read(&in_root(tmp.path(), "checkstyle.xml"));
|
||||
assert!(xml.starts_with("<?xml version=\"1.0\""), "{xml}");
|
||||
assert!(xml.contains("<file name=\"src/broken.ts\">"), "{xml}");
|
||||
assert!(
|
||||
xml.contains(
|
||||
"<error line=\"4\" severity=\"error\" message=\"cannot resolve './gone'\""
|
||||
) || xml.contains("message=\"cannot resolve './gone'\""),
|
||||
"{xml}"
|
||||
);
|
||||
assert!(xml.contains("source=\"jmove.broken-import\""), "{xml}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_projects_still_get_valid_empty_reports() {
|
||||
let tmp = copy_fixture("typescript", "basic");
|
||||
let sarif_name = report_path(&tmp, "empty.sarif");
|
||||
let xml_name = report_path(&tmp, "empty.xml");
|
||||
jmove(&tmp, &["check", "--report", &sarif_name]).success();
|
||||
jmove(&tmp, &["check", "--report", &xml_name]).success();
|
||||
let doc = sarif(&tmp, "empty.sarif");
|
||||
assert_eq!(doc["version"], "2.1.0");
|
||||
assert!(doc["runs"][0]["results"].as_array().unwrap().is_empty());
|
||||
assert!(
|
||||
doc["runs"][0]["tool"]["driver"]["rules"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
let xml = read(&in_root(tmp.path(), "empty.xml"));
|
||||
assert!(!xml.contains("<file"), "{xml}");
|
||||
assert!(xml.ends_with("</checkstyle>\n"), "{xml}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_report_extension_fails_fast() {
|
||||
let tmp = copy_fixture("typescript", "basic");
|
||||
let report = report_path(&tmp, "out.txt");
|
||||
jmove(&tmp, &["check", "--report", &report])
|
||||
.code(1)
|
||||
.stderr(predicate::str::contains("unsupported file name"));
|
||||
assert!(!tmp.path().join("out.txt").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_reports_candidates_in_both_formats_without_touching_stdout() {
|
||||
let tmp = copy_fixture("typescript", "unused");
|
||||
let sarif_name = report_path(&tmp, "fix.sarif");
|
||||
let xml_name = report_path(&tmp, "fix.xml");
|
||||
jmove(&tmp, &["fix", "--dry-run", "--report", &sarif_name])
|
||||
.success()
|
||||
.stdout(predicate::str::contains("-import type { Ghost }"));
|
||||
jmove(&tmp, &["fix", "--dry-run", "--report", &xml_name]).success();
|
||||
let doc = sarif(&tmp, "fix.sarif");
|
||||
let result = &doc["runs"][0]["results"][0];
|
||||
assert_eq!(result["ruleId"], "ts/unused-import");
|
||||
assert_eq!(result["level"], "warning");
|
||||
assert_eq!(result["properties"]["autoFixable"], true);
|
||||
let xml = read(&in_root(tmp.path(), "fix.xml"));
|
||||
assert!(xml.contains("source=\"jmove.ts.unused-import\""), "{xml}");
|
||||
assert!(xml.contains("severity=\"warning\""), "{xml}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_fix_writes_report_before_stdout_summary() {
|
||||
let tmp = copy_fixture("typescript", "unused");
|
||||
let report = report_path(&tmp, "applied.sarif");
|
||||
jmove(&tmp, &["fix", "--report", &report])
|
||||
.success()
|
||||
.stdout(predicate::str::contains("fixed"));
|
||||
let doc = sarif(&tmp, "applied.sarif");
|
||||
assert!(!doc["runs"][0]["results"].as_array().unwrap().is_empty());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue