Class: Rigor::Analysis::Baseline
- Inherits:
-
Object
- Object
- Rigor::Analysis::Baseline
- Defined in:
- lib/rigor/analysis/baseline.rb,
sig/rigor/analysis/baseline.rbs
Overview
ADR-22 Slice 1 — per-project baseline filter. See lib/rigor/analysis/baseline.rb for the full contract.
Defined Under Namespace
Classes: Bucket, DriftRow, LoadError
Constant Summary collapse
- CURRENT_VERSION =
1
Instance Attribute Summary collapse
-
#buckets ⇒ Object
readonly
Returns the value of attribute buckets.
Class Method Summary collapse
-
.from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd) ⇒ Baseline
Build a baseline from a current run's diagnostic stream.
-
.load(path, project_root: Dir.pwd) ⇒ Baseline?
Load a baseline file from disk.
Instance Method Summary collapse
-
#audit(diagnostics) ⇒ Object
Report bucket-level drift for each recorded baseline bucket.
- #empty? ⇒ Boolean
-
#filter(diagnostics) ⇒ [untyped, ::Integer]
Apply the baseline filter.
-
#initialize(buckets, project_root: Dir.pwd) ⇒ Baseline
constructor
A new instance of Baseline.
-
#size ⇒ ::Integer
Number of recorded buckets.
-
#to_yaml ⇒ ::String
Serialise to a YAML string.
-
#without(buckets_to_drop) ⇒ Baseline
Return a new Baseline with the given buckets removed.
Constructor Details
#initialize(buckets, project_root: Dir.pwd) ⇒ Baseline
Returns a new instance of Baseline.
181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/rigor/analysis/baseline.rb', line 181 def initialize(buckets, project_root: Dir.pwd) @project_root = Pathname.new(project_root) @buckets = buckets.freeze # For each (file, qualified_rule) pair, two arrays: # - rule-ID rows (message_regex == nil) # - message-pattern rows (message_regex != nil) # The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that # don't match any message row fall through to the rule-ID row if one exists. @by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze freeze end |
Instance Attribute Details
#buckets ⇒ Object (readonly)
Returns the value of attribute buckets.
179 180 181 |
# File 'lib/rigor/analysis/baseline.rb', line 179 def buckets @buckets end |
Class Method Details
.from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd) ⇒ Baseline
Build a baseline from a current run's diagnostic stream.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/rigor/analysis/baseline.rb', line 85 def from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd) raise ArgumentError, "match_mode must be :rule or :message" unless %i[rule message].include?(match_mode) grouped = group_for_baseline(diagnostics, match_mode, project_root) buckets = grouped.map do |key, entries| Bucket.new( file: key[0], rule: key[1], message_regex: key[2], count: entries.size ) end new(buckets, project_root: project_root) end |
.load(path, project_root: Dir.pwd) ⇒ Baseline?
Load a baseline file from disk.
Returns nil when path is nil (no baseline configured).
Raises LoadError on malformed or unsupported content.
71 72 73 74 75 76 77 |
# File 'lib/rigor/analysis/baseline.rb', line 71 def load(path, project_root: Dir.pwd) return nil if path.nil? return new([], project_root: project_root) unless File.exist?(path) raw = YAML.safe_load_file(path, permitted_classes: [Symbol]) parse_loaded(raw, path: path, project_root: project_root) end |
Instance Method Details
#audit(diagnostics) ⇒ Object
Report bucket-level drift for each recorded baseline bucket.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/rigor/analysis/baseline.rb', line 242 def audit(diagnostics) counts = Hash.new(0) diagnostics.each do |diag| next if diag.qualified_rule.nil? || diag.path.nil? bucket = claim_bucket_for(diag) counts[bucket_key(bucket)] += 1 if bucket end buckets.map do |bucket| actual = counts[bucket_key(bucket)] DriftRow.new(bucket: bucket, actual_count: actual, status: status_for(actual, bucket.count)) end end |
#empty? ⇒ Boolean
283 284 285 |
# File 'lib/rigor/analysis/baseline.rb', line 283 def empty? buckets.empty? end |
#filter(diagnostics) ⇒ [untyped, ::Integer]
Apply the baseline filter. Returns [surfaced_diagnostics, silenced_count].
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/rigor/analysis/baseline.rb', line 198 def filter(diagnostics) return [diagnostics, 0] if buckets.empty? grouped = group_diagnostics_for_filtering(diagnostics) surfaced = [] silenced_count = 0 grouped.each_value do |entries| bucket = entries[:bucket] diags = entries[:diagnostics] # No matching bucket → all surface as new findings. `actual <= count` → all silenced (within # threshold, WD4). `actual > count` → all surface (over threshold, WD4). if bucket && diags.size <= bucket.count silenced_count += diags.size else surfaced.concat(diags) end end # Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address # them). unkeyable = diagnostics.reject { |d| d.qualified_rule && d.path } [surfaced + unkeyable, silenced_count] end |
#size ⇒ ::Integer
Number of recorded buckets.
279 280 281 |
# File 'lib/rigor/analysis/baseline.rb', line 279 def size buckets.size end |
#to_yaml ⇒ ::String
Serialise to a YAML string.
266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/rigor/analysis/baseline.rb', line 266 def to_yaml rows = buckets.map do |bucket| row = { "file" => bucket.file, "rule" => bucket.rule } row["message"] = bucket..source if bucket. row["count"] = bucket.count row end document = { "version" => CURRENT_VERSION, "ignored" => rows } YAML.dump(document) end |
#without(buckets_to_drop) ⇒ Baseline
Return a new Baseline with the given buckets removed.
259 260 261 262 |
# File 'lib/rigor/analysis/baseline.rb', line 259 def without(buckets_to_drop) dropset = buckets_to_drop.to_set self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root) end |