Class: Shirobai::Cop::Layout::HashAlignment

Inherits:
RuboCop::Cop::Base
  • Object
show all
Extended by:
RuboCop::Cop::AutoCorrector
Includes:
RuboCop::Cop::RangeHelp
Defined in:
lib/shirobai/cop/layout/hash_alignment.rb

Overview

Drop-in Rust reimplementation of ‘Layout/HashAlignment`.

Rust parses the source, walks every multi-line hash literal (replicating the ‘EnforcedLastArgumentHashStyle` `ignore_node` of a call’s last hash argument and the ‘Layout/ArgumentAlignment: with_fixed_indentation` incompatibility skip), and for each pair computes the column-delta triple `separator, value` under each configured alignment (`EnforcedHashRocketStyle` / `EnforcedColonStyle`, possibly multi-style). A non-zero delta is an offense; for the colon/rocket flavours the least-offending permitted style wins, and keyword-splat offenses are always reported. Rust returns, per offending pair / kwsplat, the offense range, a message selector, the delta triple and the byte ranges of the key / operator / value (parser geometry). Ruby applies the realignment via `corrector.insert_before` / `corrector.remove`, exactly like stock’s ‘adjust` (including the `key_delta` clamp to `-key.column`). Offenses come from the per-file bundled run (`Shirobai::Dispatch`); the config derivation is purely config-driven, so this cop is always bundle-eligible.

Constant Summary collapse

MESSAGES =
[
  "Align the keys of a hash literal if they span more than one line.",
  "Align the separators of a hash literal if they span more than one line.",
  "Align the keys and values of a hash literal if they span more than one line.",
  "Align keyword splats with the rest of the hash if it spans more than one line."
].freeze
STYLE_CODES =
{ "key" => "key", "separator" => "separator", "table" => "table" }.freeze
LAST_ARG_CODES =
{
  "always_inspect" => 0,
  "always_ignore" => 1,
  "ignore_explicit" => 2,
  "ignore_implicit" => 3
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.badgeObject



44
# File 'lib/shirobai/cop/layout/hash_alignment.rb', line 44

def self.badge = RuboCop::Cop::Badge.parse("Layout/HashAlignment")

.bundle_args(config) ⇒ Object

Packed args for the bundled run: ‘[rocket_styles, colon_styles, last_argument_style_code, enforce_fixed]`. The style lists carry the `EnforcedHashRocketStyle` / `EnforcedColonStyle` values in config order (deduplicated downstream, matching stock’s ‘formats.uniq`). The enforce flag replicates `enforce_first_argument_with_fixed_indentation?` (`Layout/ArgumentAlignment` `with_fixed_indentation`), driving the `autocorrect_incompatible_with_other_cops?` skip.



53
54
55
56
57
58
59
60
61
62
# File 'lib/shirobai/cop/layout/hash_alignment.rb', line 53

def self.bundle_args(config)
  cop_config = config.for_badge(badge)
  arg_alignment_config = config.for_enabled_cop("Layout/ArgumentAlignment")
  [
    normalize_styles(cop_config["EnforcedHashRocketStyle"]),
    normalize_styles(cop_config["EnforcedColonStyle"]),
    LAST_ARG_CODES.fetch(cop_config["EnforcedLastArgumentHashStyle"], 0),
    arg_alignment_config["EnforcedStyle"] == "with_fixed_indentation"
  ]
end

.cop_nameObject



43
# File 'lib/shirobai/cop/layout/hash_alignment.rb', line 43

def self.cop_name = "Layout/HashAlignment"

.normalize_styles(value) ⇒ Object



64
65
66
67
68
# File 'lib/shirobai/cop/layout/hash_alignment.rb', line 64

def self.normalize_styles(value)
  formats = value.is_a?(String) ? [value] : Array(value)
  formats = ["key"] if formats.empty?
  formats.map { |f| STYLE_CODES.fetch(f, "key") }
end

Instance Method Details

#on_new_investigationObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/shirobai/cop/layout/hash_alignment.rb', line 70

def on_new_investigation
  buffer = processed_source.buffer

  offenses = Dispatch.offenses_for(processed_source, config, :hash_alignment)
  off = SourceOffsets.for(processed_source.raw_source)
  # Stock registers a hash's offenses inside one `on_hash` callback. If
  # one offense's corrector raises `Parser::ClobberingError` (adjacent
  # key/separator/value adjustments that collide), it aborts THAT
  # callback — keeping the offenses already added but dropping the
  # clobbering one and the rest of that hash — while other hashes
  # (separate callbacks) are unaffected. We add all offenses in one
  # `on_new_investigation`, so we reproduce that confinement explicitly:
  # the Rust `group` id marks each source hash, and a clobber skips only
  # the remaining offenses of its own group.
  aborted_group = nil
  offenses.each do |group, start, fin, message_idx, has_value, deltas, key, op, value|
    next if group == aborted_group

    range = Parser::Source::Range.new(buffer, off[start], off[fin])
    begin
      add_offense(range, message: MESSAGES[message_idx]) do |corrector|
        if has_value
          correct_key_value(corrector, buffer, off, deltas, key, op, value)
        else
          # `correct_no_value`: adjust the whole node by the key delta.
          adjust(corrector, deltas[0], range)
        end
      end
    rescue Parser::ClobberingError
      aborted_group = group
    end
  end
end