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