Class: Shirobai::Cop::RSpec::MultipleMemoizedHelpers

Inherits:
RuboCop::Cop::Base
  • Object
show all
Includes:
BundleEligible
Defined in:
lib/shirobai/cop/rspec/multiple_memoized_helpers.rb

Overview

Drop-in Rust reimplementation of RSpec/MultipleMemoizedHelpers (rubocop-rspec 3.10.2).

Rust does the scope-tree work on the shared walk: for every plain-block spec group it unions the memoized helpers visible from the group's own frame and every parser-block ancestor frame (stock's all_helpers), dedups them by node identity, and maps each let (plus subject when AllowSubject: false) to its variable_definition? name. Names that are decidable bytewise (sym value / str value / nil) are counted as rust_distinct; dsym/dstr names cannot be (two interpolations that differ only in whitespace are structurally EQUAL but not byte-equal), so their source ranges are handed here. Rust emits a group only when the safe upper bound rust_distinct + dsym_dstr_count > Max.

The wrapper locates the dsym/dstr nodes in the parser AST, dedups them with stock's structural node equality (Array#uniq), computes count = rust_distinct + located_uniq, and on a real count > Max reports the group and records self.max = count through the same exclude_limit 'Max' DSL stock uses (drives --auto-gen-config).

Constant Summary collapse

MSG =
"Example group has too many memoized helpers [%<count>d/%<max>d]"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.badgeObject



33
# File 'lib/shirobai/cop/rspec/multiple_memoized_helpers.rb', line 33

def self.badge = RuboCop::Cop::Badge.parse(cop_name)

.bundle_args(config) ⇒ Object

Packed args for the bundled run: [Max, AllowSubject].



36
37
38
39
# File 'lib/shirobai/cop/rspec/multiple_memoized_helpers.rb', line 36

def self.bundle_args(config)
  cop_config = config.for_badge(badge)
  [cop_config.fetch("Max", 5), cop_config.fetch("AllowSubject", true) ? 1 : 0]
end

.cop_nameObject



32
# File 'lib/shirobai/cop/rspec/multiple_memoized_helpers.rb', line 32

def self.cop_name = "RSpec/MultipleMemoizedHelpers"

Instance Method Details

#on_new_investigationObject



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/shirobai/cop/rspec/multiple_memoized_helpers.rb', line 41

def on_new_investigation
  groups = resolved_groups
  return if groups.empty?

  max = cop_config["Max"]
  buffer = processed_source.buffer
  source = bundle_eligible? ? processed_source.raw_source : buffer.source
  off = SourceOffsets.for(source)
  located = locate_dynamic_names(groups, off)

  groups.each do |(gstart, gend, rust_distinct, dyn_ranges)|
    # A locate miss (a prism/parser range disagreement we have not
    # met yet) falls back to the range itself as the identity, so it
    # degrades to per-occurrence distinctness instead of silently
    # merging every missed node into one `nil`.
    nodes = dyn_ranges.map { |(s, e)| located[[off[s], off[e]]] || [off[s], off[e]] }
    count = rust_distinct + nodes.uniq.length
    next if count <= max

    self.max = count
    range = Parser::Source::Range.new(buffer, off[gstart], off[gend])
    add_offense(range, message: format(MSG, count: count, max: max))
  end
end