From 9c17259c303f1b20a5247e22d463477c8b9c0619 Mon Sep 17 00:00:00 2001 From: Sergei Gerasenko Date: Wed, 14 Jan 2026 16:49:02 +0000 Subject: [PATCH] Align arrows for all possible syntax with arrows --- .../check_whitespace/arrow_alignment.rb | 236 +++++++------ .../check_whitespace/arrow_alignment_spec.rb | 313 +++++++++++------- 2 files changed, 332 insertions(+), 217 deletions(-) diff --git a/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb b/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb index d993db7c..135402a2 100644 --- a/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb +++ b/lib/puppet-lint/plugins/check_whitespace/arrow_alignment.rb @@ -1,130 +1,162 @@ +# frozen_string_literal: true + # Public: Check the manifest tokens for any arrows (=>) in a grouping ({}) that # are not aligned with other arrows in that grouping. -# + # https://puppet.com/docs/puppet/latest/style_guide.html#spacing-indentation-and-whitespace + COMMENT_TYPES = Set[:COMMENT, :SLASH_COMMENT, :MLCOMMENT] +# rubocop:disable Metrics/BlockLength PuppetLint.new_check(:arrow_alignment) do def check - resource_indexes.each do |res_idx| - arrow_column = [0] - level_idx = 0 - level_tokens = [] - param_column = [nil] - resource_tokens = res_idx[:tokens] - resource_tokens.reject! do |token| - COMMENT_TYPES.include?(token.type) - end + initialize_state + + tokens_to_check = tokens.reject do |token| + COMMENT_TYPES.include?(token.type) + end - # If this is a single line resource, skip it - first_arrow = resource_tokens.index { |r| r.type == :FARROW } - last_arrow = resource_tokens.rindex { |r| r.type == :FARROW } - next if first_arrow.nil? - next if last_arrow.nil? - next if resource_tokens[first_arrow].line == resource_tokens[last_arrow].line - - resource_tokens.each do |token| - case token.type - when :FARROW - param_token = token.prev_code_token - - if param_token.type == :DQPOST - param_length = 0 - iter_token = param_token - while iter_token.type != :DQPRE - param_length += iter_token.to_manifest.length - iter_token = iter_token.prev_token - end - param_length += iter_token.to_manifest.length - else - param_length = param_token.to_manifest.length - end - - if param_column[level_idx].nil? - param_column[level_idx] = if param_token.type == :DQPOST - iter_token.column - else - param_token.column - end - end - - if (level_tokens[level_idx] ||= []).any? { |t| t.line == token.line } - this_arrow_column = param_column[level_idx] + param_length + 1 - else - this_arrow_column = param_token.column + param_token.to_manifest.length - this_arrow_column += 1 - end - - arrow_column[level_idx] = this_arrow_column if arrow_column[level_idx] < this_arrow_column - - (level_tokens[level_idx] ||= []) << token - when :LBRACE - level_idx += 1 - arrow_column << 0 - level_tokens[level_idx] ||= [] - param_column << nil - when :RBRACE, :SEMIC - if (level_tokens[level_idx] ||= []).map(&:line).uniq.length > 1 - level_tokens[level_idx].each do |arrow_tok| - next if arrow_tok.column == arrow_column[level_idx] || level_tokens[level_idx].size == 1 - - arrows_on_line = level_tokens[level_idx].select { |t| t.line == arrow_tok.line } - notify( - :warning, - message: "indentation of => is not properly aligned (expected in column #{arrow_column[level_idx]}, but found it in column #{arrow_tok.column})", - line: arrow_tok.line, - column: arrow_tok.column, - token: arrow_tok, - arrow_column: arrow_column[level_idx], - newline: arrows_on_line.index(arrow_tok) != 0, - newline_indent: param_column[level_idx] - 1, - description: 'Check the manifest tokens for any arrows (=>) in a grouping ({}) that are not aligned with other arrows in that grouping.', - help_uri: 'https://puppet.com/docs/puppet/latest/style_guide.html#spacing-indentation-and-whitespace', - ) - end - end - arrow_column[level_idx] = 0 - level_tokens[level_idx].clear - param_column[level_idx] = nil - level_idx -= 1 - end + tokens_to_check.each do |token| + case token.type + when :FARROW + handle_farrow(token) + when :LBRACE + handle_lbrace + when :RBRACE, :SEMIC + process_alignment(token) + handle_block_exit(token) end end end def fix(problem) if problem[:newline] - index = tokens.index(problem[:token].prev_code_token.prev_token) + fix_newline_alignment(problem) + else + fix_horizontal_alignment(problem) + end + end - # insert newline - tokens.insert(index, PuppetLint::Lexer::Token.new(:NEWLINE, "\n", 0, 0)) + private - # indent the parameter to the correct depth - problem[:token].prev_code_token.prev_token.type = :INDENT - problem[:token].prev_code_token.prev_token.value = ' ' * problem[:newline_indent] + def initialize_state + @arrow_column = [0] + @level_idx = 0 + @level_tokens = [] + @param_column = [nil] + end + + def handle_lbrace + @level_idx += 1 + @arrow_column[@level_idx] = 0 + @level_tokens[@level_idx] = [] + @param_column[@level_idx] = nil + end + + def handle_block_exit(token) + @arrow_column[@level_idx] = 0 + @level_tokens[@level_idx].clear + @param_column[@level_idx] = nil + @level_idx -= 1 if token.type == :RBRACE + end + + def handle_farrow(token) + param_token = token.prev_code_token + p_len, p_col = calculate_param_details(param_token) + + @param_column[@level_idx] ||= p_col + + # Determine where arrow should be relative to its own line or the group + current_tokens = (@level_tokens[@level_idx] ||= []) + this_arrow_column = if current_tokens.any? { |t| t.line == token.line } + @param_column[@level_idx] + p_len + 1 + else + param_token.column + param_token.to_manifest.length + 1 + end + + @arrow_column[@level_idx] = this_arrow_column if @arrow_column[@level_idx] < this_arrow_column + @level_tokens[@level_idx] << token + end - end_param_idx = tokens.index(problem[:token].prev_code_token) - start_param_idx = tokens.index(problem[:token].prev_token_of([:INDENT, :NEWLINE])) - param_length = tokens[start_param_idx..end_param_idx].sum { |r| r.to_manifest.length } + 1 - new_ws_len = problem[:arrow_column] - param_length + def calculate_param_details(param_token) + if param_token.type == :DQPOST + len = 0 + iter = param_token + until iter.type == :DQPRE + len += iter.to_manifest.length + iter = iter.prev_token + end + [len + iter.to_manifest.length, iter.column] else - new_ws_len = if problem[:token].prev_token.type == :WHITESPACE - problem[:token].prev_token.to_manifest.length - else - 0 - end - new_ws_len += (problem[:arrow_column] - problem[:token].column) + [param_token.to_manifest.length, param_token.column] end + end - raise PuppetLint::NoFix if new_ws_len.negative? + def process_alignment(_token) + current_tokens = @level_tokens[@level_idx] || [] + return unless current_tokens.map(&:line).uniq.length > 1 + return if current_tokens.size < 2 + + target_col = @arrow_column[@level_idx] + + current_tokens.each do |arrow_tok| + next if arrow_tok.column == target_col + + trigger_notification(arrow_tok, target_col) + end + end + + def trigger_notification(arrow_tok, target_col) + arrows_on_line = @level_tokens[@level_idx].select { |t| t.line == arrow_tok.line } + + notify( + :warning, + message: 'indentation of => is not properly aligned ' \ + "(expected in column #{target_col}, but found " \ + "it in column #{arrow_tok.column})", + line: arrow_tok.line, + column: arrow_tok.column, + token: arrow_tok, + arrow_column: target_col, + newline: arrows_on_line.index(arrow_tok) != 0, + newline_indent: @param_column[@level_idx] - 1, + ) + end - new_ws = ' ' * new_ws_len + def fix_newline_alignment(problem) + index = tokens.index(problem[:token].prev_code_token.prev_token) + tokens.insert(index, PuppetLint::Lexer::Token.new(:NEWLINE, "\n", 0, 0)) + + prev_token = problem[:token].prev_code_token.prev_token + prev_token.type = :INDENT + prev_token.value = ' ' * problem[:newline_indent] + + end_idx = tokens.index(problem[:token].prev_code_token) + start_token = problem[:token].prev_token_of([:INDENT, :NEWLINE]) + start_idx = tokens.index(start_token) + + param_length = tokens[start_idx..end_idx].sum { |r| r.to_manifest.length } + 1 + apply_whitespace_fix(problem, problem[:arrow_column] - param_length) + end + + def fix_horizontal_alignment(problem) + current_ws = 0 + current_ws = problem[:token].prev_token.to_manifest.length if problem[:token].prev_token.type == :WHITESPACE + + new_len = current_ws + (problem[:arrow_column] - problem[:token].column) + apply_whitespace_fix(problem, new_len) + end + + def apply_whitespace_fix(problem, new_ws_len) + raise PuppetLint::NoFix if new_ws_len.negative? + new_ws_value = ' ' * new_ws_len if problem[:token].prev_token.type == :WHITESPACE - problem[:token].prev_token.value = new_ws + problem[:token].prev_token.value = new_ws_value else index = tokens.index(problem[:token].prev_token) - tokens.insert(index + 1, PuppetLint::Lexer::Token.new(:WHITESPACE, new_ws, 0, 0)) + tokens.insert(index + 1, PuppetLint::Lexer::Token.new(:WHITESPACE, new_ws_value, 0, 0)) end end end +# rubocop:enable Metrics/BlockLength diff --git a/spec/unit/puppet-lint/plugins/check_whitespace/arrow_alignment_spec.rb b/spec/unit/puppet-lint/plugins/check_whitespace/arrow_alignment_spec.rb index e904c861..2d549b17 100644 --- a/spec/unit/puppet-lint/plugins/check_whitespace/arrow_alignment_spec.rb +++ b/spec/unit/puppet-lint/plugins/check_whitespace/arrow_alignment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'arrow_alignment' do @@ -6,7 +8,7 @@ context 'with fix disabled' do context 'selectors inside a resource' do let(:code) do - <<-END + <<-CODE file { 'foo': ensure => $ensure, require => $ensure ? { @@ -15,7 +17,7 @@ }, foo => bar, } - END + CODE end it 'does not detect any problems' do @@ -25,7 +27,7 @@ context 'selectors in the middle of a resource' do let(:code) do - <<-END + <<-CODE file { 'foo': ensure => $ensure ? { present => directory, @@ -33,7 +35,7 @@ }, owner => 'tomcat6', } - END + CODE end it 'does not detect any problems' do @@ -43,7 +45,7 @@ context 'selector inside a resource' do let(:code) do - <<-END + <<-CODE ensure => $ensure ? { present => directory, absent => undef, @@ -51,28 +53,7 @@ owner => 'foo4', group => 'foo4', mode => '0755', - END - end - - it 'does not detect any problems' do - expect(problems).to be_empty - end - end - - context 'selector inside a hash inside a resource' do - let(:code) do - <<-END - server => { - ensure => ensure => $ensure ? { - present => directory, - absent => undef, - }, - ip => '192.168.1.1' - }, - owner => 'foo4', - group => 'foo4', - mode => '0755', - END + CODE end it 'does not detect any problems' do @@ -82,7 +63,7 @@ context 'nested hashes with correct indentation' do let(:code) do - <<-END + <<-CODE class { 'lvs::base': virtualeservers => { '192.168.2.13' => { @@ -107,7 +88,7 @@ class { 'lvs::base': } } } - END + CODE end it 'does not detect any problems' do @@ -117,7 +98,7 @@ class { 'lvs::base': context 'single resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => 2, @@ -125,7 +106,7 @@ class { 'lvs::base': baz => 4, meh => 5, } - END + CODE end it 'detects four problems' do @@ -142,7 +123,7 @@ class { 'lvs::base': context 'single resource with a misaligned => and semicolon at the end' do let(:code) do - <<-END + <<-CODE file { '/tmp/bar': foo => 1, bar => 2, @@ -150,7 +131,7 @@ class { 'lvs::base': baz => 4, meh => 5; } - END + CODE end it 'detects four problems' do @@ -167,7 +148,7 @@ class { 'lvs::base': context 'complex resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => $baz ? { @@ -177,7 +158,7 @@ class { 'lvs::base': meep => 4, bah => 5, } - END + CODE end it 'detects three problems' do @@ -193,7 +174,7 @@ class { 'lvs::base': context 'multi-resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': ; '/tmp/bar': @@ -202,7 +183,7 @@ class { 'lvs::base': gronk => 'bah', meh => 'no' } - END + CODE end it 'only detects a single problem' do @@ -216,7 +197,7 @@ class { 'lvs::base': context 'multi-resource with a misaligned => and semicolons' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': ensure => 'directory', @@ -229,7 +210,7 @@ class { 'lvs::base': owner => 'root', mode => '0755'; } - END + CODE end it 'only detects a single problem' do @@ -246,10 +227,10 @@ class { 'lvs::base': context 'multiple single line resources' do let(:code) do - <<-END + <<-CODE file { 'foo': ensure => file } package { 'bar': ensure => present } - END + CODE end it 'does not detect any problems' do @@ -259,12 +240,12 @@ class { 'lvs::base': context 'resource with unaligned => in commented line' do let(:code) do - <<-END + <<-CODE file { 'foo': ensure => directory, # purge => true, } - END + CODE end it 'does not detect any problems' do @@ -274,11 +255,11 @@ class { 'lvs::base': context 'single line resource spread out on multiple lines' do let(:code) do - <<-END + <<-CODE file { 'foo': ensure => present, } - END + CODE end it 'does not detect any problems' do @@ -288,11 +269,11 @@ class { 'lvs::base': context 'multiline resource with a single line of params' do let(:code) do - <<-END + <<-CODE mymodule::do_thing { 'some thing': whatever => { foo => 'bar', one => 'two' }, } - END + CODE end it 'does not detect any problems' do @@ -302,12 +283,12 @@ class { 'lvs::base': context 'resource with aligned => too far out' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': ensure => file, mode => '0444', } - END + CODE end it 'detects 2 problems' do @@ -322,13 +303,13 @@ class { 'lvs::base': context 'resource with multiple params where one is an empty hash' do let(:code) do - <<-END + <<-CODE foo { 'foo': a => true, b => { } } - END + CODE end it 'does not detect any problems' do @@ -338,12 +319,12 @@ class { 'lvs::base': context 'multiline resource with multiple params on a line' do let(:code) do - <<-END + <<-CODE user { 'test': a => 'foo', bb => 'bar', ccc => 'baz', } - END + CODE end it 'detects 2 problems' do @@ -358,12 +339,12 @@ class { 'lvs::base': context 'resource param containing a single-element same-line hash' do let(:code) do - <<-END + <<-CODE foo { 'foo': a => true, b => { 'a' => 'b' } } - END + CODE end it 'does not detect any problems' do @@ -373,14 +354,14 @@ class { 'lvs::base': context 'multiline hash with opening brace on same line as first pair' do let(:code) do - <<-END + <<-CODE foo { 'foo': bar => [ { aa => bb, c => d}, ], } - END + CODE end it 'does not detect any problems' do @@ -390,14 +371,14 @@ class { 'lvs::base': context 'unaligned multiline hash with opening brace on the same line as the first pair' do let(:code) do - <<-END + <<-CODE foo { 'foo': bar => [ { aa => bb, c => d}, ], } - END + CODE end it 'detects one problem' do @@ -411,7 +392,7 @@ class { 'lvs::base': context 'hash with strings containing variables as keys properly aligned' do let(:code) do - <<-END + <<-CODE foo { foo: param => { a => 1 @@ -419,7 +400,7 @@ class { 'lvs::base': b => 3, }, } - END + CODE end it 'does not detect any problems' do @@ -429,7 +410,7 @@ class { 'lvs::base': context 'hash with strings containing variables as keys incorrectly aligned' do let(:code) do - <<-END + <<-CODE foo { foo: param => { a => 1 @@ -437,7 +418,7 @@ class { 'lvs::base': b => 3, }, } - END + CODE end it 'detects 2 problems' do @@ -452,7 +433,7 @@ class { 'lvs::base': context 'complex data structure with different indentation levels at the same depth' do let(:code) do - <<-END + <<-CODE class { 'some_class': config_hash => { 'a_hash' => { @@ -466,7 +447,7 @@ class { 'some_class': ], }, } - END + CODE end it 'does not detect any problems' do @@ -476,7 +457,7 @@ class { 'some_class': context 'where the top level of the block has no parameters' do let(:code) do - <<-END + <<-CODE case $facts['os']['family'] { 'RedHat': { $datadir = $::operatingsystem ? { @@ -485,13 +466,51 @@ class { 'some_class': } } } - END + CODE end it 'does not detect any problems' do expect(problems).to be_empty end end + + context 'with misaligned hash' do + let(:code) do + <<~CODE + $x = { + present => directory, + absent => undef, + }, + CODE + end + + it 'detects one problem' do + expect(problems.size).to eq(1) + end + + it 'creates 1 warning' do + expect(problems).to contain_warning(msg % [11, 12]).on_line(3).in_column(12) + end + end + + context 'with misaligned selector' do + let(:code) do + <<~CODE + $x = $y ? { + 'a' => 1, + default => 3, + } + CODE + end + + it 'detects one problem' do + expect(problems.size).to eq(1) + end + + it 'creates 1 warning' do + expect(problems).to contain_warning(msg % [11, 7]).on_line(2).in_column(7) + end + end end context 'with fix enabled' do @@ -505,7 +524,7 @@ class { 'some_class': context 'single resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => 2, @@ -513,11 +532,11 @@ class { 'some_class': baz => 4, meh => 5, } - END + CODE end let(:fixed) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => 2, @@ -525,7 +544,7 @@ class { 'some_class': baz => 4, meh => 5, } - END + CODE end it 'detects four problems' do @@ -546,7 +565,7 @@ class { 'some_class': context 'complex resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => $baz ? { @@ -556,11 +575,11 @@ class { 'some_class': meep => 4, bah => 5, } - END + CODE end let(:fixed) do - <<-END + <<-CODE file { '/tmp/foo': foo => 1, bar => $baz ? { @@ -570,7 +589,7 @@ class { 'some_class': meep => 4, bah => 5, } - END + CODE end it 'detects three problems' do @@ -590,7 +609,7 @@ class { 'some_class': context 'multi-resource with a misaligned =>' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': ; '/tmp/bar': @@ -599,11 +618,11 @@ class { 'some_class': gronk => 'bah', meh => 'no' } - END + CODE end let(:fixed) do - <<-END + <<-CODE file { '/tmp/foo': ; '/tmp/bar': @@ -612,7 +631,7 @@ class { 'some_class': gronk => 'bah', meh => 'no' } - END + CODE end it 'only detects a single problem' do @@ -630,21 +649,21 @@ class { 'some_class': context 'resource with aligned => too far out' do let(:code) do - <<-END + <<-CODE file { '/tmp/foo': ensure => file, mode => '0444', } - END + CODE end let(:fixed) do - <<-END + <<-CODE file { '/tmp/foo': ensure => file, mode => '0444', } - END + CODE end it 'detects 2 problems' do @@ -663,21 +682,21 @@ class { 'some_class': context 'resource with unaligned => and no whitespace between param and =>' do let(:code) do - <<-END + <<-CODE user { 'test': param1 => 'foo', param2=> 'bar', } - END + CODE end let(:fixed) do - <<-END + <<-CODE user { 'test': param1 => 'foo', param2 => 'bar', } - END + CODE end it 'detects 1 problem' do @@ -695,22 +714,22 @@ class { 'some_class': context 'multiline resource with multiple params on a line' do let(:code) do - <<-END + <<-CODE user { 'test': a => 'foo', bb => 'bar', ccc => 'baz', } - END + CODE end let(:fixed) do - <<-END + <<-CODE user { 'test': a => 'foo', bb => 'bar', ccc => 'baz', } - END + CODE end it 'detects 2 problems' do @@ -729,22 +748,22 @@ class { 'some_class': context 'multiline resource with multiple params on a line, extra one longer' do let(:code) do - <<-END + <<-CODE user { 'test': a => 'foo', bbccc => 'bar', ccc => 'baz', } - END + CODE end let(:fixed) do - <<-END + <<-CODE user { 'test': a => 'foo', bbccc => 'bar', ccc => 'baz', } - END + CODE end it 'detects 2 problems' do @@ -764,7 +783,7 @@ class { 'some_class': context 'hash with strings containing variables as keys incorrectly aligned' do let(:code) do - <<-END + <<-CODE foo { foo: param => { a => 1 @@ -772,11 +791,11 @@ class { 'some_class': b => 3, }, } - END + CODE end let(:fixed) do - <<-END + <<-CODE foo { foo: param => { a => 1 @@ -784,7 +803,7 @@ class { 'some_class': b => 3, }, } - END + CODE end it 'detects 2 problems' do @@ -803,7 +822,7 @@ class { 'some_class': context 'complex data structure with different indentation levels at the same depth' do let(:code) do - <<-END + <<-CODE class { 'some_class': config_hash => { 'a_hash' => { @@ -817,11 +836,11 @@ class { 'some_class': ], }, } - END + CODE end let(:fixed) do - <<-END + <<-CODE class { 'some_class': config_hash => { 'a_hash' => { @@ -835,7 +854,7 @@ class { 'some_class': ], }, } - END + CODE end it 'detects 1 problem' do @@ -853,7 +872,7 @@ class { 'some_class': context 'complex data structure with multiple token keys' do let(:code) do - <<-END.gsub(%r{^ {10}}, '') + <<-CODE.gsub(%r{^ {10}}, '') class example ( $external_ip_base, ) { @@ -871,11 +890,11 @@ class example ( }, } } - END + CODE end let(:fixed) do - <<-END.gsub(%r{^ {10}}, '') + <<-CODE.gsub(%r{^ {10}}, '') class example ( $external_ip_base, ) { @@ -893,7 +912,7 @@ class example ( }, } } - END + CODE end it 'detects 5 problems' do @@ -915,7 +934,7 @@ class example ( context 'realignment of resource with an inline single line hash' do let(:code) do - <<-END.gsub(%r{^ {10}}, '') + <<-CODE.gsub(%r{^ {10}}, '') class { 'puppetdb': database => 'embedded', #database => 'postgres', @@ -928,11 +947,11 @@ class { 'puppetdb': open_listen_port => false, open_ssl_listen_port => false; } - END + CODE end let(:fixed) do - <<-END.gsub(%r{^ {10}}, '') + <<-CODE.gsub(%r{^ {10}}, '') class { 'puppetdb': database => 'embedded', #database => 'postgres', @@ -945,7 +964,7 @@ class { 'puppetdb': open_listen_port => false, open_ssl_listen_port => false; } - END + CODE end it 'detects 8 problems' do @@ -970,26 +989,26 @@ class { 'puppetdb': context 'negative argument' do let(:code) do - <<-END + <<-CODE res { 'a': x => { 'a' => '', 'ab' => '', } } - END + CODE end # TODO: This is not the desired behaviour, but adjusting the check to # properly format the hashes will need to wait until a major version # bump. let(:fixed) do - <<-END + <<-CODE res { 'a': x => { 'a' => '', 'ab' => '', } } - END + CODE end it 'detects a problem' do @@ -1004,5 +1023,69 @@ class { 'puppetdb': expect(manifest).to eq(fixed) end end + + context 'with misaligned hash' do + let(:code) do + <<~CODE + $x = { + present => directory, + absent => undef, + }, + CODE + end + + let(:fixed) do + <<~CODE + $x = { + present => directory, + absent => undef, + }, + CODE + end + + it 'detects one problem' do + expect(problems.size).to eq(1) + end + + it 'fixes the problems' do + expect(problems).to contain_fixed(msg % [11, 12]).on_line(3).in_column(12) + end + + it 'realigns the arrows' do + expect(manifest).to eq(fixed) + end + end + + context 'with misaligned selector' do + let(:code) do + <<~CODE + $x = $y ? { + 'a' => 1, + default => 3, + } + CODE + end + + let(:fixed) do + <<~CODE + $x = $y ? { + 'a' => 1, + default => 3, + } + CODE + end + + it 'detects one problem' do + expect(problems.size).to eq(1) + end + + it 'fixes the problems' do + expect(problems).to contain_fixed(msg % [11, 7]).on_line(2).in_column(7) + end + + it 'realigns the arrows' do + expect(manifest).to eq(fixed) + end + end end end