GitHub

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