///|
/// Product version embedded in the SARIF driver metadata. Keep in step with
/// package.json; the release script checks the match.
const PRODUCT_VERSION : String = "0.3.0"
///|
/// The URI code space for artifact locations: ASCII letters, digits and
/// - . _ ~ stay literal; / is a separator; everything else is percent-encoded
/// from its UTF-8 bytes. This is deliberately conservative (queries and
/// fragments are encoded) so the URI is safe in any SARIF consumer.
fn uri_unreserved(code : Int) -> Bool {
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code == 45 ||
code == 46 ||
code == 95 ||
code == 126 ||
code == 47
}
///|
/// The hex digit Char for a 4-bit nibble. This toolchain's String indexing
/// returns a UTF-16 code unit, so the digit is converted explicitly.
fn hex_char(value : Int) -> Char {
HEX_DIGITS[value].to_int().unsafe_to_char()
}
///|
const HEX_DIGITS : String = "0123456789ABCDEF"
///|
/// Append the percent-encoded UTF-8 form of one byte to the char buffer.
fn push_pct(chars : Array[Char], byte : Int) -> Unit {
chars.push('%')
chars.push(hex_char(byte >> 4))
chars.push(hex_char(byte & 0xF))
}
///|
/// Percent-encode a relative path as a URI reference, preserving / separators.
pub fn uri_escape_path(value : String) -> String {
let chars : Array[Char] = []
for char in value {
let code = char.to_int()
if code >= 0xD800 && code <= 0xDFFF {
// Lone surrogates are not valid UTF-8; substitute the replacement glyph.
push_pct(chars, 0xEF)
push_pct(chars, 0xBF)
push_pct(chars, 0xBD)
} else if uri_unreserved(code) {
chars.push(char)
} else if code <= 0x7F {
push_pct(chars, code)
} else if code <= 0x7FF {
push_pct(chars, 0xC0 | (code >> 6))
push_pct(chars, 0x80 | (code & 0x3F))
} else if code <= 0xFFFF {
push_pct(chars, 0xE0 | (code >> 12))
push_pct(chars, 0x80 | ((code >> 6) & 0x3F))
push_pct(chars, 0x80 | (code & 0x3F))
} else {
push_pct(chars, 0xF0 | (code >> 18))
push_pct(chars, 0x80 | ((code >> 12) & 0x3F))
push_pct(chars, 0x80 | ((code >> 6) & 0x3F))
push_pct(chars, 0x80 | (code & 0x3F))
}
}
String::from_iter(chars.iter())
}
///|
/// SARIF 2.1.0 level for a diagnostic severity: error/warning pass through and
/// anything else (including the scanning phase) becomes a note.
fn sarif_level(severity : String) -> String {
if severity == "error" {
"error"
} else if severity == "warning" {
"warning"
} else {
"note"
}
}
///|
/// SARIF result for one report group. Locations carry only the artifact URI;
/// the model has no source line numbers, so no region is ever fabricated.
fn sarif_result(
code : String,
level : String,
message : String,
uri_path : String,
members : Array[SourceMember],
) -> String {
let locations = if check_path(uri_path, 1, []) is Some(_) {
",\"locations\":[{\"physicalLocation\":{\"artifactLocation\":{\"uri\":" +
quote(uri_escape_path(uri_path)) +
"}}}]"
} else {
""
}
let navigable = members.filter(item => check_path(item.path, 1, []) is Some(_))
let related = if navigable.is_empty() {
""
} else {
",\"relatedLocations\":[" +
navigable
.map(item => {
"{\"physicalLocation\":{\"artifactLocation\":{\"uri\":" +
quote(uri_escape_path(item.path)) +
"}}}"
})
.join(",") +
"]"
}
"{\"ruleId\":" +
quote(code) +
",\"level\":" +
quote(level) +
",\"message\":{\"text\":" +
quote(message) +
"}" +
locations +
related +
",\"properties\":{\"path\":" +
quote(uri_path) +
",\"members\":" +
render_source_members(members) +
"}" +
"}"
}
///|
fn sarif_results(report : DetailedReport) -> String {
let parts : Array[String] = []
for diagnostic in report.diagnostics {
parts.push(
sarif_result(
diagnostic.code,
sarif_level(diagnostic.severity),
diagnostic.message,
diagnostic.anchor,
diagnostic.members,
),
)
}
for issue in report.scan_issues {
parts.push(
sarif_result(
issue.code,
"note",
issue.message,
if issue.path.is_empty() {
"."
} else {
issue.path
},
[],
),
)
}
parts.join(",")
}
///|
fn sarif_rule_descriptors() -> String {
rules()
.map(rule => {
"{\"id\":" +
quote(rule.code) +
",\"name\":" +
quote(rule.title) +
",\"shortDescription\":{\"text\":" +
quote(rule.title) +
"},\"fullDescription\":{\"text\":" +
quote(rule.reason) +
"},\"help\":{\"text\":" +
quote(rule.suggestion) +
"},\"defaultConfiguration\":{\"level\":\"error\"}}"
})
.join(",")
}
///|
fn sarif_invocation(report : DetailedReport) -> String {
let notifications = report.limitations.map(limitation => {
"{\"descriptor\":{\"id\":\"MOONPORT_RUN_LIMIT\"},\"message\":{\"text\":" +
quote(limitation) +
"}}"
})
let notification_json = if notifications.is_empty() {
""
} else {
",\"toolExecutionNotifications\":[" + notifications.join(",") + "]"
}
"{\"executionSuccessful\":" +
report.complete.to_json().stringify() +
notification_json +
"}"
}
///|
/// A complete SARIF 2.1.0 document for a schema 2 report. The document never
/// encodes timestamps, absolute paths or source line numbers.
pub fn render_check_sarif(report : DetailedReport) -> String {
"{\"version\":\"2.1.0\",\"$schema\":\"https://json.schemastore.org/sarif-2.1.0.json\",\"runs\":[" +
"{\"tool\":{\"driver\":{" +
"\"name\":\"MoonPortCheck\"," +
"\"informationUri\":\"https://github.com/AlexenderSokolov/moonportcheck\"," +
"\"version\":\"" +
PRODUCT_VERSION +
"\",\"rules\":[" +
sarif_rule_descriptors() +
"]}},\"invocations\":[" +
sarif_invocation(report) +
"],\"results\":[" +
sarif_results(report) +
"]}]}"
}