diff --git a/changelog/fix_false_negatives_for_layout_multiline_method_call_indentation.md b/changelog/fix_false_negatives_for_layout_multiline_method_call_indentation.md new file mode 100644 index 000000000000..562ca5757bd0 --- /dev/null +++ b/changelog/fix_false_negatives_for_layout_multiline_method_call_indentation.md @@ -0,0 +1 @@ +* [#12199](https://github.com/rubocop/rubocop/issues/12199): Fix false negatives for `Layout/MultilineMethodCallIndentation` when using safe navigation operator. ([@koic][]) diff --git a/lib/rubocop/cop/layout/multiline_method_call_indentation.rb b/lib/rubocop/cop/layout/multiline_method_call_indentation.rb index a82640fae8d8..5e6a825316d2 100644 --- a/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +++ b/lib/rubocop/cop/layout/multiline_method_call_indentation.rb @@ -75,7 +75,7 @@ def relevant_node?(send_node) def right_hand_side(send_node) dot = send_node.loc.dot selector = send_node.loc.selector - if send_node.dot? && selector && same_line?(dot, selector) + if (send_node.dot? || send_node.safe_navigation?) && selector && same_line?(dot, selector) dot.join(selector) elsif selector selector @@ -179,7 +179,7 @@ def syntactic_alignment_base(lhs, rhs) # a.b # .c def semantic_alignment_base(node, rhs) - return unless rhs.source.start_with?('.') + return unless rhs.source.start_with?('.', '&.') node = semantic_alignment_node(node) return unless node&.loc&.selector diff --git a/lib/rubocop/cop/mixin/multiline_expression_indentation.rb b/lib/rubocop/cop/mixin/multiline_expression_indentation.rb index fee6dd7fcf62..cd33bdbf9275 100644 --- a/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +++ b/lib/rubocop/cop/mixin/multiline_expression_indentation.rb @@ -20,16 +20,17 @@ def on_send(node) range = offending_range(node, lhs, rhs, style) check(range, node, lhs, rhs) end + alias on_csend on_send private - # In a chain of method calls, we regard the top send node as the base + # In a chain of method calls, we regard the top call node as the base # for indentation of all lines following the first. For example: # a. # b c { block }. <-- b is indented relative to a # d <-- d is indented relative to a def left_hand_side(lhs) - while lhs.parent&.send_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method? + while lhs.parent&.call_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method? lhs = lhs.parent end lhs diff --git a/spec/rubocop/cop/layout/multiline_method_call_indentation_spec.rb b/spec/rubocop/cop/layout/multiline_method_call_indentation_spec.rb index 2def04bffd1d..a4f646796917 100644 --- a/spec/rubocop/cop/layout/multiline_method_call_indentation_spec.rb +++ b/spec/rubocop/cop/layout/multiline_method_call_indentation_spec.rb @@ -219,6 +219,113 @@ .b RUBY end + + context 'when using safe navigation operator' do + it 'registers an offense and corrects no indentation of second line' do + expect_offense(<<~RUBY) + a&. + b + ^ Use 2 (not 0) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + a&. + b + RUBY + end + + it 'registers an offense and corrects 3 spaces indentation of 2nd line' do + expect_offense(<<~RUBY) + a&. + b + ^ Use 2 (not 3) spaces for indenting an expression spanning multiple lines. + c&. + d + ^ Use 2 (not 3) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + a&. + b + c&. + d + RUBY + end + + it 'registers an offense and corrects extra indentation of third line' do + expect_offense(<<~RUBY) + a&. + b&. + c + ^ Use 2 (not 4) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + a&. + b&. + c + RUBY + end + + it 'registers an offense and corrects the emacs ruby-mode 1.1 ' \ + 'indentation of an expression in an array' do + expect_offense(<<~RUBY) + [ + a&. + b + ^ Use 2 (not 0) spaces for indenting an expression spanning multiple lines. + ] + RUBY + + expect_correction(<<~RUBY) + [ + a&. + b + ] + RUBY + end + + it 'registers an offense and corrects extra indentation of 3rd line in typical RSpec code' do + expect_offense(<<~RUBY) + expect { Foo.new }&. + to change { Bar.count }&. + from(1)&.to(2) + ^^^^ Use 2 (not 6) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + expect { Foo.new }&. + to change { Bar.count }&. + from(1)&.to(2) + RUBY + end + + it 'registers an offense and corrects proc call without a selector' do + expect_offense(<<~RUBY) + a + &.(args) + ^^^ Use 2 (not 1) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + a + &.(args) + RUBY + end + + it 'registers an offense and corrects one space indentation of 2nd line' do + expect_offense(<<~RUBY) + a + &.b + ^^^ Use 2 (not 1) spaces for indenting an expression spanning multiple lines. + RUBY + + expect_correction(<<~RUBY) + a + &.b + RUBY + end + end end context 'when EnforcedStyle is aligned' do @@ -255,6 +362,13 @@ def foo RUBY end + it 'accepts methods being aligned with safe navigation method call that is an argument' do + expect_no_offenses(<<~RUBY) + do_something obj.foo(key: value) + &.bar(arg) + RUBY + end + context '>= Ruby 2.7', :ruby27 do it 'accepts methods being aligned with method that is an argument' \ 'when using numbered parameter' do