Class: Shirobai::Cop::Lint::DuplicateMethods

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

Overview

Drop-in Rust reimplementation of Lint/DuplicateMethods.

Stock keeps @definitions / @scopes on the cop instance, which RuboCop reuses across every file sharing a config — duplicate detection is deliberately cross-file (Method ... is defined at both first.rb:2 and second.rb:2.). The Rust side therefore returns the per-file part only: the exact stream of stock found_method calls (key, message name, offense range, rescue/ensure scope) in callback order, and this wrapper replays stock's bookkeeping against its own cross-investigation state. The replay is a hash lookup per event; all AST work (scope resolution, parent_module_name, anonymous Class.new blocks, attr/alias/delegator matchers) happens in Rust on the shared walk.

Two event flavors need Ruby-side completion:

  • scope_line >= 0: the key gets an "@#{smart_path}:#{line}" suffix (stock's source_location-based anonymous-block scope id; Rust does not know the buffer name).
  • sexp_start >= 0: stock's lookup_constant failed and (through each_ancestor's block-form return value of self) the key embeds the parser-gem s-expression of the whole defs node. The wrapper finds that node in processed_source.ast and interpolates it with stock's own Node#to_s, staying byte-identical for arbitrary bodies.

No autocorrect (stock has none).

Constant Summary collapse

MSG =
"Method `%<method>s` is defined at both %<defined>s and %<current>s."

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil, options = nil) ⇒ DuplicateMethods

Returns a new instance of DuplicateMethods.



45
46
47
48
49
# File 'lib/shirobai/cop/lint/duplicate_methods.rb', line 45

def initialize(config = nil, options = nil)
  super
  @definitions = {}
  @scopes = { 1 => [], 2 => [] } # 1 = :rescue, 2 = :ensure
end

Class Method Details

.badgeObject



38
# File 'lib/shirobai/cop/lint/duplicate_methods.rb', line 38

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

.bundle_args(config) ⇒ Object

[active_support_extensions_enabled] (AllCops).



41
42
43
# File 'lib/shirobai/cop/lint/duplicate_methods.rb', line 41

def self.bundle_args(config)
  [!!config.active_support_extensions_enabled?]
end

.cop_nameObject



37
# File 'lib/shirobai/cop/lint/duplicate_methods.rb', line 37

def self.cop_name = "Lint/DuplicateMethods"

Instance Method Details

#on_new_investigationObject



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/shirobai/cop/lint/duplicate_methods.rb', line 51

def on_new_investigation
  events = resolved_events
  return if events.empty?

  buffer = processed_source.buffer
  path = smart_path(buffer.name)
  off = SourceOffsets.for(bundle_eligible? ? processed_source.raw_source : buffer.source)

  events.each do |name, key, sexp_start, _sexp_end, scope_line, off_start, off_end, line, scope|
    if sexp_start >= 0
      node = defs_node_at(off[sexp_start])
      next unless node

      name = "#{node}.#{name}"
      key = "#{key}#{name}"
    end
    key = "#{key}@#{path}:#{scope_line}" if scope_line >= 0
    current = "#{path}:#{line}"

    if @definitions.key?(key)
      if scope != 0 && !@scopes[scope].include?(key)
        @definitions[key] = current
        @scopes[scope] << key
      else
        range = Parser::Source::Range.new(buffer, off[off_start], off[off_end])
        message = format(MSG, method: name, defined: @definitions[key], current: current)
        add_offense(range, message: message)
      end
    else
      @definitions[key] = current
    end
  end
end