///|
/// Return the text that identifies a result for human-facing reports.
fn result_message(result : SarifResult) -> String {
match result.message.text {
Some(text) => text
None =>
match result.message.markdown {
Some(markdown) => markdown
None => ""
}
}
}
///|
/// Return a normalized key for the first physical location of a result.
fn result_location_key(result : SarifResult) -> String {
match result.locations {
Some(locations) => {
for location in locations {
match location.physicalLocation {
Some(physical) => {
let path = match physical.artifactLocation {
Some(artifact) =>
match artifact.uri {
Some(uri) => normalize_path(uri)
None => ""
}
None => ""
}
let line = match physical.region {
Some(region) =>
match region.startLine {
Some(value) => value.to_string()
None => ""
}
None => ""
}
let column = match physical.region {
Some(region) =>
match region.startColumn {
Some(value) => value.to_string()
None => ""
}
None => ""
}
return path + ":" + line + ":" + column
}
None => ()
}
}
""
}
None => ""
}
}
///|
/// Build a deterministic identity for a SARIF result.
///
/// The identity intentionally avoids object addresses, array indexes and
/// timestamps. It combines the rule, normalized first location and message;
/// this makes it stable across separate tool runs and useful for de-duplication
/// and baseline comparisons.
pub fn fingerprint(result : SarifResult) -> String {
let rule = match result.ruleId {
Some(value) => value
None => ""
}
let location = result_location_key(result)
let message = result_message(result)
rule.length().to_string() +
":" +
rule +
"|" +
location.length().to_string() +
":" +
location +
"|" +
message.length().to_string() +
":" +
message
}
///|
/// Remove duplicate findings from every run while retaining the first result.
pub fn deduplicate(log : SarifLog) -> SarifLog {
let runs = log.runs.map(fn(run) {
let seen : Map[String, Unit] = Map([])
let results = match run.results {
Some(items) =>
Some(
items.filter(fn(result) {
let key = fingerprint(result)
if seen.contains(key) {
false
} else {
seen[key] = ()
true
}
}),
)
None => None
}
{ tool: run.tool, results, automationDetails: run.automationDetails }
})
{ version: log.version, schema: log.schema, runs }
}
///|
/// State of a result when compared with a previous SARIF baseline.
pub enum BaselineState {
New
Unchanged
Absent
} derive(Debug, Eq, ToJson)
///|
/// Aggregate counts produced by a baseline comparison.
pub struct BaselineReport {
new_results : Int
unchanged_results : Int
absent_results : Int
} derive(Debug, Eq, ToJson)
///|
/// Compare unique result identities in `current` with `baseline`.
///
/// Results present only in `current` are new. Results present in both logs are
/// unchanged. Results present only in `baseline` are absent.
pub fn compare_baseline(
current : SarifLog,
baseline : SarifLog,
) -> BaselineReport {
let baseline_keys : Map[String, Unit] = Map([])
for run in baseline.runs {
match run.results {
Some(results) =>
for result in results {
baseline_keys[fingerprint(result)] = ()
}
None => ()
}
}
let current_keys : Map[String, Unit] = Map([])
let mut new_results = 0
let mut unchanged_results = 0
for run in current.runs {
match run.results {
Some(results) =>
for result in results {
let key = fingerprint(result)
if !current_keys.contains(key) {
current_keys[key] = ()
let state = if baseline_keys.contains(key) {
Unchanged
} else {
New
}
match state {
New => new_results += 1
Unchanged => unchanged_results += 1
Absent => ()
}
}
}
None => ()
}
}
let mut absent_results = 0
for key, _ in baseline_keys {
if !current_keys.contains(key) {
let state = Absent
match state {
Absent => absent_results += 1
New | Unchanged => ()
}
}
}
{ new_results, unchanged_results, absent_results }
}
///|
/// Add SARIF baselineState values to results in the current log.
///
/// Results found in the baseline are marked `unchanged`; other current
/// results are marked `new`. Baseline-only results are represented by the
/// counts returned from `compare_baseline` and are not added to the current
/// log.
pub fn annotate_baseline(current : SarifLog, baseline : SarifLog) -> SarifLog {
let baseline_keys : Map[String, Unit] = Map([])
for run in baseline.runs {
match run.results {
Some(results) =>
for result in results {
baseline_keys[fingerprint(result)] = ()
}
None => ()
}
}
let runs = current.runs.map(fn(run) {
let results = match run.results {
Some(items) =>
Some(
items.map(fn(result) {
let state = if baseline_keys.contains(fingerprint(result)) {
"unchanged"
} else {
"new"
}
{
ruleId: result.ruleId,
ruleIndex: result.ruleIndex,
level: result.level,
message: result.message,
locations: result.locations,
relatedLocations: result.relatedLocations,
partialFingerprints: result.partialFingerprints,
fingerprints: result.fingerprints,
suppressions: result.suppressions,
fixes: result.fixes,
properties: result.properties,
baselineState: Some(state),
}
}),
)
None => None
}
{ tool: run.tool, results, automationDetails: run.automationDetails }
})
{ version: current.version, schema: current.schema, runs }
}