Class: Shirobai::Cop::Lint::AmbiguousBlockAssociation

Inherits:
RuboCop::Cop::Base
  • Object
show all
Extended by:
RuboCop::Cop::AutoCorrector
Defined in:
lib/shirobai/cop/lint/ambiguous_block_association.rb

Overview

Drop-in Rust reimplementation of Lint/AmbiguousBlockAssociation.

Detection and autocorrect both happen in Rust; Ruby turns the byte offsets handed back into offenses and a remove + insert_before + insert_after corrector trio (matching stock's wrap_in_parentheses byte-for-byte).

AllowedMethods is taken from the cop's own config (verbatim string entries; Regexp entries are filtered out and force the standalone fallback). AllowedPatterns (regexp) is matched in Ruby: the wrapper walks every block-bearing call candidate's INNER sender source and collects the strings that match any pattern, then hands the list to the standalone Rust entry as allowed_inner_sources so the Rust path can drop them by exact-bytes lookup. Without AllowedPatterns the bundle path is taken (no regexp work).

Constant Summary collapse

MSG =
"Parenthesize the param `%<param>s` to make sure that the " \
"block will be associated with the `%<method>s` method " \
"call."
MSG_DO_END_BLOCK =
"`%<inner_method>s` is called without a block because the " \
"`do` block binds to `%<outer_method>s`. " \
"Use braces or extract to a variable."

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.badgeObject



32
# File 'lib/shirobai/cop/lint/ambiguous_block_association.rb', line 32

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

.bundle_args(config) ⇒ Object

Returns [allowed_methods]. allowed_methods mirrors the stock AllowedMethods mixin: regexp entries filtered out (any regexp forces the wrapper into the standalone path via bundle_eligible?).



37
38
39
40
41
42
# File 'lib/shirobai/cop/lint/ambiguous_block_association.rb', line 37

def self.bundle_args(config)
  cfg = config.for_badge(badge)
  allowed = Array(cfg.fetch("AllowedMethods", []))
  names = allowed.reject { |e| e.is_a?(Regexp) }.map(&:to_s)
  [names]
end

.cop_nameObject



31
# File 'lib/shirobai/cop/lint/ambiguous_block_association.rb', line 31

def self.cop_name = "Lint/AmbiguousBlockAssociation"

Instance Method Details

#bundle_eligible?Boolean

Bundle path is OK only when AllowedMethods has no regexp AND AllowedPatterns is empty (regexp matching against inner-sender source is done in Ruby; the bundle path does not carry the pre-applied list).

Returns:

  • (Boolean)


48
49
50
51
52
53
# File 'lib/shirobai/cop/lint/ambiguous_block_association.rb', line 48

def bundle_eligible?
  cfg = cop_config
  allowed = Array(cfg.fetch("AllowedMethods", []))
  patterns = Array(cfg.fetch("AllowedPatterns", []))
  allowed.none? { |e| e.is_a?(Regexp) } && patterns.empty?
end

#on_new_investigationObject



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
# File 'lib/shirobai/cop/lint/ambiguous_block_association.rb', line 55

def on_new_investigation
  buffer = processed_source.buffer
  off = SourceOffsets.for(processed_source.raw_source)
  fetch_offenses.each do |kind, start_offset, end_offset, param_start, param_end,
                          inner_send_start, inner_send_end,
                          ac_open_start, ac_open_end, ac_close_pos|
    range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
    param_range = Parser::Source::Range.new(buffer, off[param_start], off[param_end])
    inner_range = Parser::Source::Range.new(buffer, off[inner_send_start], off[inner_send_end])
    if kind == 1
      # `do...end` binding (stock `on_block`, 1.89): offense on the
      # inner call, message names both methods, no autocorrect.
      message = format(MSG_DO_END_BLOCK,
                       inner_method: param_range.source,
                       outer_method: inner_range.source)
      add_offense(range, message: message)
    else
      message = format(MSG, param: param_range.source, method: inner_range.source)
      add_offense(range, message: message) do |corrector|
        open_range = Parser::Source::Range.new(buffer, off[ac_open_start], off[ac_open_end])
        close_range = Parser::Source::Range.new(buffer, off[ac_close_pos], off[ac_close_pos])
        corrector.remove(open_range)
        corrector.insert_before(open_range, "(")
        corrector.insert_after(close_range, ")")
      end
    end
  end
end