use super::support::*;
use crate::analysis::findings::Severity;
use crate::languages::runner::*;
fn checkstyle_sarif() -> &'static str {
r#"{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": { "driver": { "name": "Checkstyle", "version": "14.1.0" } },
"results": [
{
"level": "error",
"locations": [
{ "physicalLocation": {
"artifactLocation": { "uri": "file:/private/tmp/jvmfix/Sample.java" },
"region": { "startColumn": 8, "startLine": 2 } } }
],
"message": { "id": "import.unused", "text": "Unused import - java.util.List." },
"ruleId": "com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck"
},
{
"level": "warning",
"locations": [
{ "physicalLocation": {
"artifactLocation": { "uri": "file:/private/tmp/jvmfix/Sample.java" },
"region": { "startColumn": 14, "startLine": 5 } } }
],
"message": { "id": "ws.notFollowed", "text": "'=' is not followed by whitespace." },
"ruleId": "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck"
}
]
}
]
}"#
}
#[test]
fn sarif_parser_reads_rule_location_and_message() {
let spec = checkstyle_like_spec();
let findings = parse_output(&spec, checkstyle_sarif(), "root").expect("sarif parses");
assert_eq!(findings.len(), 2);
assert_eq!(
findings[0].kind,
"com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck"
);
assert_eq!(findings[0].line, 2);
assert_eq!(findings[0].column, Some(8));
assert_eq!(findings[0].message, "Unused import - java.util.List.");
}
fn sarif_for_uri(uri: &str) -> String {
format!(
r#"{{ "version": "2.1.0", "runs": [ {{ "results": [
{{ "level": "warning",
"locations": [ {{ "physicalLocation": {{
"artifactLocation": {{ "uri": "{uri}" }},
"region": {{ "startLine": 1 }} }} }} ],
"message": {{ "text": "m" }}, "ruleId": "r" }}
] }} ] }}"#
)
}
#[test]
fn sarif_parser_strips_the_file_uri_scheme() {
let spec = checkstyle_like_spec();
let findings = parse_output(&spec, checkstyle_sarif(), "root").unwrap();
assert_eq!(findings[0].file_path, "/private/tmp/jvmfix/Sample.java");
}
#[test]
fn sarif_parser_percent_decodes_the_uri_path() {
let spec = checkstyle_like_spec();
let sarif = sarif_for_uri("file:/repo/My%20Sources/Has%22Quote%22.java");
let findings = parse_output(&spec, &sarif, "root").unwrap();
assert_eq!(findings[0].file_path, "/repo/My Sources/Has\"Quote\".java");
}
#[test]
fn sarif_parser_drops_the_root_slash_before_a_windows_drive() {
let spec = checkstyle_like_spec();
let findings =
parse_output(&spec, &sarif_for_uri("file:/C:/repo/Sample.java"), "root").unwrap();
assert_eq!(findings[0].file_path, "C:/repo/Sample.java");
}
#[test]
fn sarif_parser_keeps_paths_that_only_look_like_a_drive() {
let spec = checkstyle_like_spec();
for path in [
"/repo/Sample.java",
"/C/repo/Sample.java",
"/repo:/Sample.java",
"/C:x/Sample.java",
"C:/repo/Sample.java",
] {
let sarif = sarif_for_uri(&format!("file:{path}"));
let findings = parse_output(&spec, &sarif, "root").unwrap();
assert_eq!(findings[0].file_path, path, "{path} should pass through");
}
}
#[test]
fn sarif_parser_leaves_malformed_percent_sequences_alone() {
let spec = checkstyle_like_spec();
for uri in [
"file:/repo/100%.java",
"file:/repo/100%2x.java",
"file:/repo/ends%2.java",
] {
let sarif = sarif_for_uri(uri);
let findings = parse_output(&spec, &sarif, "root").unwrap();
assert_eq!(
findings[0].file_path,
uri.strip_prefix("file:").unwrap(),
"{uri} should pass through undecoded"
);
}
}
#[test]
fn sarif_parser_maps_the_sarif_level_to_severity() {
let spec = checkstyle_like_spec();
let findings = parse_output(&spec, checkstyle_sarif(), "root").unwrap();
assert_eq!(findings[0].severity, Severity::Error);
assert_eq!(findings[1].severity, Severity::Warning);
}
#[test]
fn sarif_levels_note_and_none_map_to_info_not_warning() {
let spec = checkstyle_like_spec();
let sarif = r#"{
"version": "2.1.0",
"runs": [ { "results": [
{ "level": "note",
"locations": [ { "physicalLocation": {
"artifactLocation": { "uri": "file:/repo/A.java" },
"region": { "startLine": 1 } } } ],
"message": { "text": "informational" },
"ruleId": "com.puppycrawl.tools.checkstyle.checks.SomeInfoCheck" },
{ "level": "none",
"locations": [ { "physicalLocation": {
"artifactLocation": { "uri": "file:/repo/A.java" },
"region": { "startLine": 2 } } } ],
"message": { "text": "evaluated, nothing to say" },
"ruleId": "com.puppycrawl.tools.checkstyle.checks.SomeQuietCheck" }
] } ]
}"#;
let findings = parse_output(&spec, sarif, "root").unwrap();
assert_eq!(findings.len(), 2);
assert_eq!(
findings[0].severity,
Severity::Info,
"note is informational"
);
assert_eq!(
findings[1].severity,
Severity::Info,
"none must not become a warning"
);
}
#[test]
fn sarif_parser_treats_no_output_as_clean() {
let spec = checkstyle_like_spec();
let findings = parse_output(&spec, "", "root").expect("empty output is a clean run");
assert!(findings.is_empty());
}
#[test]
fn sarif_parser_errors_on_unparseable_input() {
let spec = checkstyle_like_spec();
let err = parse_output(&spec, "not json at all", "root")
.expect_err("garbage must not be reported as clean");
assert!(err.0.contains("checkstyle"), "message was {:?}", err.0);
}
#[test]
fn sarif_parser_rejects_a_locationless_result() {
let spec = tflint_like_spec();
let tflint_errors = r#"{
"version": "2.1.0",
"runs": [
{ "tool": { "driver": { "name": "tflint" } }, "results": [] },
{ "tool": { "driver": { "name": "tflint-errors" } },
"results": [
{ "ruleId": "application_error", "ruleIndex": 0, "level": "error",
"message": { "text": "Failed to initialize plugins; Plugin \"aws\" not found. Did you run `tflint --init`?" } }
] }
]
}"#;
let err = parse_output(&spec, tflint_errors, "root")
.expect_err("a locationless result is the tool failing, not a finding");
assert!(
err.0.contains("tflint") && err.0.contains("Plugin \"aws\" not found"),
"the tool's own error text must reach the message: {}",
err.0
);
}
#[test]
fn sarif_parser_accepts_an_empty_error_run() {
let spec = tflint_like_spec();
let sarif = r#"{"runs":[
{"tool":{"driver":{"name":"tflint"}},"results":[]},
{"tool":{"driver":{"name":"tflint-errors"}},"results":[]}
]}"#;
let findings = parse_output(&spec, sarif, "root").expect("an empty run is clean");
assert!(findings.is_empty());
}
#[test]
fn sarif_parser_rejects_a_location_without_a_uri() {
let spec = checkstyle_like_spec();
for locations in [
r#""locations": []"#,
r#""locations": [{"logicalLocations": []}]"#,
r#""locations": [{"physicalLocation": {"region": {"startLine": 3}}}]"#,
] {
let sarif = format!(
r#"{{"runs":[{{"results":[{{"level":"error","message":{{"text":"m"}},"ruleId":"r",{locations}}}]}}]}}"#
);
let err = parse_output(&spec, &sarif, "root")
.expect_err("a result that cannot be placed is not a finding");
assert!(
err.0.contains("checkstyle"),
"message was {:?} for {locations}",
err.0
);
}
}
#[test]
fn sarif_parser_defaults_a_regionless_location_to_line_one() {
let spec = tflint_like_spec();
let sarif = r#"{"runs":[{"results":[
{ "ruleId": "terraform_required_version", "level": "warning",
"message": { "text": "terraform \"required_version\" attribute is required" },
"locations": [ { "physicalLocation": { "artifactLocation": { "uri": "main.tf" } } } ] }
]}]}"#;
let findings = parse_output(&spec, sarif, "root").expect("a region is optional");
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].file_path, "main.tf");
assert_eq!(findings[0].line, 1);
assert_eq!(findings[0].column, None);
}