Class: Audition::Report

Inherits:
Object
  • Object
show all
Defined in:
lib/audition/report.rb

Overview

Aggregates static findings and dynamic results into a verdict and renders them as npm-CLI-style text or JSON.

Defined Under Namespace

Classes: Style, Text

Constant Summary collapse

VERDICTS =
{
  not_ready: "not ractor-ready",
  blocked: "own code is ractor-ready; blocked by dependencies",
  risky: "risky: warnings only, no hard errors",
  ready: "ractor-ready as far as audition can tell"
}.freeze
GITHUB_LEVELS =
{
  error: "error", warning: "warning", info: "notice"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target_type:, target_root:, findings:, dynamic_results: [], unsafe_fixes: 0, baselined: 0) ⇒ Report

Returns a new instance of Report.

Parameters:

  • target_type (Symbol)
  • target_root (String)
  • findings (Array<Finding>)

    static plus dynamic findings

  • dynamic_results (Array<Dynamic::Result>) (defaults to: [])
  • unsafe_fixes (Integer) (defaults to: 0)

    shown as the --fix-unsafe hint

  • baselined (Integer) (defaults to: 0)

    findings hidden by the baseline



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/audition/report.rb', line 93

def initialize(target_type:, target_root:, findings:,
  dynamic_results: [], unsafe_fixes: 0,
  baselined: 0)
  @target_type = target_type
  @target_root = target_root
  @findings = findings.sort_by do |f|
    [f.path, f.line || 0, -f.severity_rank]
  end
  @dynamic_results = dynamic_results
  @unsafe_fixes = unsafe_fixes
  @baselined = baselined
end

Instance Attribute Details

#baselinedObject (readonly)

Returns the value of attribute baselined.



84
85
86
# File 'lib/audition/report.rb', line 84

def baselined
  @baselined
end

#dynamic_resultsObject (readonly)

Returns the value of attribute dynamic_results.



84
85
86
# File 'lib/audition/report.rb', line 84

def dynamic_results
  @dynamic_results
end

#findingsObject (readonly)

Returns the value of attribute findings.



84
85
86
# File 'lib/audition/report.rb', line 84

def findings
  @findings
end

#target_rootObject (readonly)

Returns the value of attribute target_root.



84
85
86
# File 'lib/audition/report.rb', line 84

def target_root
  @target_root
end

#target_typeObject (readonly)

Returns the value of attribute target_type.



84
85
86
# File 'lib/audition/report.rb', line 84

def target_type
  @target_type
end

#unsafe_fixesObject (readonly)

Returns the value of attribute unsafe_fixes.



84
85
86
# File 'lib/audition/report.rb', line 84

def unsafe_fixes
  @unsafe_fixes
end

Instance Method Details

#countsObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/audition/report.rb', line 132

def counts
  @counts ||= begin
    base = {error: 0, dep_error: 0, warning: 0, info: 0,
            fixable: 0}
    findings.each_with_object(base) do |f, acc|
      if f.error? && f.dependency?
        acc[:dep_error] += 1
      else
        acc[f.severity] += 1
      end
      # Only safe autofixes count: `--fix` alone would not
      # touch an unsafe-only finding, so advertising it as
      # fixable would send users in circles.
      if f.autofix && !f.autofix.unsafe?
        acc[:fixable] += 1
      end
    end
  end
end

#dependency_errors?Boolean

Returns:

  • (Boolean)


128
129
130
# File 'lib/audition/report.rb', line 128

def dependency_errors?
  counts[:dep_error].positive?
end

#own_errors?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/audition/report.rb', line 124

def own_errors?
  counts[:error].positive?
end

#to_githubString

GitHub Actions workflow commands: findings become inline PR annotations when this runs in CI.

Returns:

  • (String)

    one ::error/::warning/::notice line per finding plus a verdict line



167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/audition/report.rb', line 167

def to_github
  lines = findings.map do |f|
    level = GITHUB_LEVELS.fetch(f.severity)
    location = f.line ? ",line=#{f.line}" : ""
    body = workflow_escape("#{f.message}. #{f.why}")
    file = property_escape(f.path)
    title = property_escape("audition #{f.check}")
    "::#{level} file=#{file}#{location}," \
    "title=#{title}::#{body}"
  end
  lines << "audition verdict: #{VERDICTS.fetch(verdict)}"
  lines.join("\n")
end

#to_jsonObject



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/audition/report.rb', line 181

def to_json(*)
  JSON.pretty_generate(
    "audition" => VERSION,
    "ruby" => RUBY_VERSION,
    "target" => {"type" => target_type.to_s,
                 "root" => target_root},
    "verdict" => verdict.to_s,
    "summary" => {
      "errors" => counts[:error],
      "dependency_errors" => counts[:dep_error],
      "warnings" => counts[:warning],
      "infos" => counts[:info],
      "fixable" => counts[:fixable]
    },
    "findings" => findings.map do |f|
      {
        "check" => f.check,
        "severity" => f.severity.to_s,
        "message" => f.message,
        "why" => f.why,
        "fix" => f.fix,
        "path" => f.path,
        "line" => f.line,
        "source" => f.source,
        "fixable" => f.fixable?,
        "dependency" => f.dependency?
      }
    end,
    "dynamic" => dynamic_results.map do |r|
      {"mode" => r.mode.to_s, "passed" => r.passed,
       "raw" => r.raw}
    end
  )
end

#to_text(style: Style.detect) ⇒ String

Returns the human-facing terminal report.

Parameters:

  • style (Style) (defaults to: Style.detect)

    rendering style (auto-detected default)

Returns:

  • (String)

    the human-facing terminal report



154
155
156
# File 'lib/audition/report.rb', line 154

def to_text(style: Style.detect)
  Text.new(self, style).render
end

#verdictSymbol

Policy: own errors condemn the target outright; dependency errors (or a failed probe with clean own findings) mean the target is fine but cannot run here yet; anything softer is merely risky.

Returns:

  • (Symbol)

    :not_ready, :blocked, :risky, or :ready



113
114
115
116
117
118
119
120
121
122
# File 'lib/audition/report.rb', line 113

def verdict
  return :not_ready if own_errors?
  return :blocked if dependency_errors? ||
    dynamic_results.any? { |r| !r.passed }
  return :risky if counts[:warning].positive?

  # Info notes describe things that work on Ruby 4.0 and are
  # only worth knowing; they do not taint the verdict.
  :ready
end