Builder
The Sarif::Builder provides a fluent API for constructing SARIF documents. It handles object wiring (tool, rules, results, ruleIndex linking) so you can focus on your data.
Basic Usage
log = Sarif::Builder.build do |b|
b.run("MyTool", "1.0.0") do |r|
r.result("Issue found", level: Sarif::Level::Warning)
end
end
Adding Rules
Define rules before results. The builder auto-links ruleIndex when a result references a rule_id:
log = Sarif::Builder.build do |b|
b.run("Linter") do |r|
r.rule("R001", name: "NoUnused",
short_description: "No unused variables",
level: Sarif::Level::Warning)
r.rule("R002", name: "NoShadow",
short_description: "No shadowed variables",
level: Sarif::Level::Error)
r.result("Variable 'x' is unused",
rule_id: "R001", level: Sarif::Level::Warning,
uri: "src/app.cr", start_line: 15)
r.result("Variable 'i' shadows outer 'i'",
rule_id: "R002", level: Sarif::Level::Error,
uri: "src/loop.cr", start_line: 22, start_column: 5)
end
end
The resulting JSON will have ruleIndex: 0 for the first result and ruleIndex: 1 for the second.
Result Builder Block
For more control, use the block form:
log = Sarif::Builder.build do |b|
b.run("Tool") do |r|
r.result do |rb|
rb.message("Complex issue", markdown: "**Complex** issue")
rb.rule_id("R1")
rb.level(Sarif::Level::Error)
rb.location(uri: "file.cr", start_line: 5, end_line: 10)
rb.related_location(uri: "other.cr", start_line: 20,
message_text: "Related code", id: 1)
rb.fingerprint("primary", "hash123")
end
end
end
Adding Artifacts
Register analyzed files:
log = Sarif::Builder.build do |b|
b.run("Scanner") do |r|
r.artifact("src/main.cr", mime_type: "text/x-crystal")
r.artifact("src/utils.cr", mime_type: "text/x-crystal")
r.result("Issue in main", uri: "src/main.cr", start_line: 1)
end
end
Adding Invocations
Record how the tool was invoked:
log = Sarif::Builder.build do |b|
b.run("Scanner") do |r|
r.invocation(true, "scanner --check src/")
r.result("Found issue", level: Sarif::Level::Warning)
end
end
Multiple Runs
A single SARIF log can contain results from multiple tools:
log = Sarif::Builder.build do |b|
b.run("Linter", "1.0") do |r|
r.result("Style issue", level: Sarif::Level::Note)
end
b.run("SecurityScanner", "2.0") do |r|
r.result("Vulnerability found", level: Sarif::Level::Error)
end
end
Code Flows
Build code flows to describe data or control flow paths:
log = Sarif::Builder.build do |b|
b.run("TaintAnalyzer") do |r|
r.result do |rb|
rb.message("Tainted data flows to SQL query")
rb.rule_id("SEC001")
rb.level(Sarif::Level::Error)
rb.code_flow("Taint flow") do |cf|
cf.thread_flow("main") do |tf|
tf.location("src/input.cr", 5, message: "User input received")
tf.location("src/transform.cr", 12, message: "Data transformed")
tf.location("src/query.cr", 20,
message: "Used in SQL query",
importance: Sarif::Importance::Essential)
end
end
end
end
end
Fixes
Describe suggested fixes with artifact changes and replacements:
log = Sarif::Builder.build do |b|
b.run("Linter") do |r|
r.result do |rb|
rb.message("Unused variable 'x'")
rb.rule_id("LINT001")
rb.fix("Remove unused variable") do |f|
f.artifact_change("src/app.cr") do |ac|
ac.replacement(10, 1, 10, 20, inserted_text: "")
end
end
end
end
end
Suppressions
Mark results as suppressed:
log = Sarif::Builder.build do |b|
b.run("Scanner") do |r|
r.result do |rb|
rb.message("Known false positive")
rb.rule_id("FP001")
rb.suppression(
Sarif::SuppressionKind::InSource,
justification: "Verified false positive",
status: Sarif::SuppressionStatus::Accepted
)
end
end
end
Complete Example
log = Sarif::Builder.build do |b|
b.run("CrystalLint", "2.0.0") do |r|
r.rule("CL001", name: "UnusedVar",
short_description: "Unused variable detected",
help_uri: "https://example.com/rules/CL001")
r.rule("CL002", name: "ShadowVar",
short_description: "Variable shadows outer scope")
r.artifact("src/app.cr", mime_type: "text/x-crystal")
r.artifact("src/loop.cr", mime_type: "text/x-crystal")
r.invocation(true, "crystal-lint src/")
r.result("Variable 'x' is never used",
rule_id: "CL001", level: Sarif::Level::Warning,
uri: "src/app.cr", start_line: 15)
r.result("Variable 'i' shadows outer 'i'",
rule_id: "CL002", level: Sarif::Level::Note,
uri: "src/loop.cr", start_line: 22, start_column: 5)
end
end
puts log.to_pretty_json