Class: Shirobai::Cop::Style::PercentLiteralDelimiters

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

Overview

Drop-in Rust reimplementation of Style/PercentLiteralDelimiters.

Rust walks every percent literal once and decides on a per-type PreferredDelimiters lookup (resolved Ruby-side from default + per-type overrides) whether the literal:

  • already uses the preferred opener, OR
  • contains the preferred opener/closer in any str/sym child source (stock's contains_preferred_delimiter?), OR
  • (for %w / %i only) contains the begin delimiter's matchpair character — (/) / [/] / {`/`} / </> or the mirror single-byte forms — in a child source (stock's include_same_character_as_used_for_delimiter?).

When none of the skips apply, Rust returns the offense range plus the opening_loc and the single-byte closer (so regex options like the trailing i in %r(.*)i survive the autocorrect, matching stock's node.loc.end byte width). The wrapper builds the offense + corrector, formatting both with the resolved preferred pair for the literal's type.

Constant Summary collapse

MSG =
"`%<type>s`-literals should be delimited by `%<open>s` and `%<close>s`."
TYPES =

Canonical type order shared with crates/.../percent_literal_delimiters.rs and stock's PreferredDelimiters::PERCENT_LITERAL_TYPES.

["%", "%i", "%I", "%q", "%Q", "%r", "%s", "%w", "%W", "%x"].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.badgeObject



35
# File 'lib/shirobai/cop/style/percent_literal_delimiters.rb', line 35

def self.badge = RuboCop::Cop::Badge.parse("Style/PercentLiteralDelimiters")

.bundle_args(config) ⇒ Object

Returns [pairs] — a 10-entry list of two-byte strings packed for the bundle path's lists slot. We resolve default + per-type overrides exactly like stock's PreferredDelimiters#preferred_delimiters: when default is set, every type defaults to it (then overridden by its own key); without default the listed types still resolve.



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

def self.bundle_args(config)
  cfg = config.for_badge(badge)
  configured = cfg.fetch("PreferredDelimiters", {})
  # Stock raises ArgumentError on an unknown key. We mirror that here
  # so the `from_packed` side never sees a missing-type fallback. The
  # raise propagates through `Dispatch.bundle_token`, which surfaces
  # it just like stock would.
  invalid = configured.keys - (TYPES + ["default"])
  unless invalid.empty?
    raise ArgumentError, "Invalid preferred delimiter config key: #{invalid.join(', ')}"
  end

  default = configured["default"]
  pairs = TYPES.map do |type|
    configured[type] || default
  end
  if pairs.any?(&:nil?)
    # Missing without `default` => the type has no preferred pair; we
    # use `()` as an inert placeholder. The cop never reaches the
    # `process` arm for this type (stock would also bail out), but
    # `from_packed` insists on 10 entries.
    pairs = pairs.map { |p| p || "()" }
  end
  [pairs]
end

.cop_nameObject



34
# File 'lib/shirobai/cop/style/percent_literal_delimiters.rb', line 34

def self.cop_name = "Style/PercentLiteralDelimiters"

Instance Method Details

#bundle_eligible?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/shirobai/cop/style/percent_literal_delimiters.rb', line 68

def bundle_eligible?
  true
end

#on_new_investigationObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/shirobai/cop/style/percent_literal_delimiters.rb', line 72

def on_new_investigation
  buffer = processed_source.buffer
  off = SourceOffsets.for(processed_source.raw_source)

  resolved_result.each do |start_offset, end_offset, begin_start, begin_end,
                           end_start, end_end, type_index|
    type = TYPES[type_index]
    open_delim, close_delim = preferred_pair_for(type).chars

    range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
    message = format(MSG, type: type, open: open_delim, close: close_delim)
    add_offense(range, message: message) do |corrector|
      begin_range = Parser::Source::Range.new(
        buffer, off[begin_start], off[begin_end]
      )
      end_range = Parser::Source::Range.new(
        buffer, off[end_start], off[end_end]
      )
      corrector.replace(begin_range, "#{type}#{open_delim}")
      corrector.replace(end_range, close_delim)
    end
  end
end