diff --git a/changelog/new_make_layout_argument_alignment_aware_of_kwargs.md b/changelog/new_make_layout_argument_alignment_aware_of_kwargs.md new file mode 100644 index 00000000000..82551694977 --- /dev/null +++ b/changelog/new_make_layout_argument_alignment_aware_of_kwargs.md @@ -0,0 +1 @@ +* [#9798](https://github.com/rubocop/rubocop/pull/9798): Make `Layout/ArgumentAlignment` aware of kwargs. ([@koic][]) diff --git a/lib/rubocop/cop/layout/argument_alignment.rb b/lib/rubocop/cop/layout/argument_alignment.rb index c60c2305f64..b29d17cf56d 100644 --- a/lib/rubocop/cop/layout/argument_alignment.rb +++ b/lib/rubocop/cop/layout/argument_alignment.rb @@ -10,33 +10,39 @@ module Layout # # good # # foo :bar, - # :baz + # :baz, + # key: value # # foo( # :bar, - # :baz + # :baz, + # key: value # ) # # # bad # # foo :bar, - # :baz + # :baz, + # key: value # # foo( # :bar, - # :baz + # :baz, + # key: value # ) # # @example EnforcedStyle: with_fixed_indentation # # good # # foo :bar, - # :baz + # :baz, + # key: value # # # bad # # foo :bar, - # :baz + # :baz, + # key: value class ArgumentAlignment < Base include Alignment extend AutoCorrector @@ -47,14 +53,25 @@ class ArgumentAlignment < Base 'following the first line of a multi-line method call.' def on_send(node) - return if node.arguments.size < 2 || node.send_type? && node.method?(:[]=) + first_arg = node.first_argument + return if !multiple_arguments?(node, first_arg) || node.send_type? && node.method?(:[]=) - check_alignment(node.arguments, base_column(node, node.arguments)) + if first_arg.hash_type? + check_alignment(first_arg.pairs, base_column(node, first_arg.pairs.first)) + else + check_alignment(node.arguments, base_column(node, first_arg)) + end end alias on_csend on_send private + def multiple_arguments?(node, first_argument) + return true if node.arguments.size >= 2 + + first_argument&.hash_type? && first_argument.pairs.count >= 2 + end + def autocorrect(corrector, node) AlignmentCorrector.correct(corrector, processed_source, node, column_delta) end @@ -67,14 +84,14 @@ def fixed_indentation? cop_config['EnforcedStyle'] == 'with_fixed_indentation' end - def base_column(node, args) - if fixed_indentation? + def base_column(node, first_argument) + if fixed_indentation? || first_argument.nil? lineno = target_method_lineno(node) line = node.source_range.source_buffer.source_line(lineno) indentation_of_line = /\S.*/.match(line).begin(0) indentation_of_line + configured_indentation_width else - display_column(args.first.source_range) + display_column(first_argument.source_range) end end diff --git a/lib/rubocop/cop/layout/first_hash_element_indentation.rb b/lib/rubocop/cop/layout/first_hash_element_indentation.rb index 16e44e8ba32..42e49389c43 100644 --- a/lib/rubocop/cop/layout/first_hash_element_indentation.rb +++ b/lib/rubocop/cop/layout/first_hash_element_indentation.rb @@ -91,6 +91,8 @@ def on_hash(node) end def on_send(node) + return if enforce_first_argument_with_fixed_indentation? + each_argument_node(node, :hash) do |hash_node, left_parenthesis| check(hash_node, left_parenthesis) end @@ -182,6 +184,16 @@ def message_for_right_brace(left_parenthesis) 'where the left brace is.' end end + + def enforce_first_argument_with_fixed_indentation? + return false unless argument_alignment_config['Enabled'] + + argument_alignment_config['EnforcedStyle'] == 'with_fixed_indentation' + end + + def argument_alignment_config + config.for_cop('Layout/ArgumentAlignment') + end end end end diff --git a/lib/rubocop/cop/layout/hash_alignment.rb b/lib/rubocop/cop/layout/hash_alignment.rb index 94c23a83fd7..0daded023e5 100644 --- a/lib/rubocop/cop/layout/hash_alignment.rb +++ b/lib/rubocop/cop/layout/hash_alignment.rb @@ -200,14 +200,12 @@ def on_send(node) alias on_super on_send alias on_yield on_send - def on_hash(node) # rubocop:todo Metrics/CyclomaticComplexity - return if ignored_node?(node) - return if node.pairs.empty? || node.single_line? + def on_hash(node) + return if enforce_first_argument_with_fixed_indentation? || ignored_node?(node) || + node.pairs.empty? || node.single_line? - return unless alignment_for_hash_rockets - .any? { |a| a.checkable_layout?(node) } && - alignment_for_colons - .any? { |a| a.checkable_layout?(node) } + proc = ->(a) { a.checkable_layout?(node) } + return unless alignment_for_hash_rockets.any?(proc) && alignment_for_colons.any?(proc) check_pairs(node) end @@ -353,6 +351,16 @@ def adjust(corrector, delta, range) def good_alignment?(column_deltas) column_deltas.values.all?(&:zero?) end + + def enforce_first_argument_with_fixed_indentation? + return false unless argument_alignment_config['Enabled'] + + argument_alignment_config['EnforcedStyle'] == 'with_fixed_indentation' + end + + def argument_alignment_config + config.for_cop('Layout/ArgumentAlignment') + end end end end diff --git a/spec/rubocop/cop/layout/argument_alignment_spec.rb b/spec/rubocop/cop/layout/argument_alignment_spec.rb index 1b0aa85b839..4d797c47be5 100644 --- a/spec/rubocop/cop/layout/argument_alignment_spec.rb +++ b/spec/rubocop/cop/layout/argument_alignment_spec.rb @@ -88,6 +88,31 @@ RUBY end + it 'registers an offense and corrects when missed indendation kwargs' do + expect_offense(<<~RUBY) + func1(foo: 'foo', + bar: 'bar', + ^^^^^^^^^^ Align the arguments of a method call if they span more than one line. + baz: 'baz') + ^^^^^^^^^^ Align the arguments of a method call if they span more than one line. + func2(do_something, + foo: 'foo', + ^^^^^^^^^^^ Align the arguments of a method call if they span more than one line. + bar: 'bar', + baz: 'baz') + RUBY + + expect_correction(<<~RUBY) + func1(foo: 'foo', + bar: 'bar', + baz: 'baz') + func2(do_something, + foo: 'foo', + bar: 'bar', + baz: 'baz') + RUBY + end + it 'registers an offense and corrects splat operator' do expect_offense(<<~RUBY) func1(*a, @@ -386,6 +411,31 @@ class MyModel < ActiveRecord::Base RUBY end + it 'registers an offense and corrects when missed indendation kwargs' do + expect_offense(<<~RUBY) + func1(foo: 'foo', + bar: 'bar', + ^^^^^^^^^^ Use one level of indentation for arguments following the first line of a multi-line method call. + baz: 'baz') + ^^^^^^^^^^ Use one level of indentation for arguments following the first line of a multi-line method call. + func2(do_something, + foo: 'foo', + ^^^^^^^^^^^ Use one level of indentation for arguments following the first line of a multi-line method call. + bar: 'bar', + baz: 'baz') + RUBY + + expect_correction(<<~RUBY) + func1(foo: 'foo', + bar: 'bar', + baz: 'baz') + func2(do_something, + foo: 'foo', + bar: 'bar', + baz: 'baz') + RUBY + end + it 'autocorrects when first line is indented' do expect_offense(<<-RUBY.strip_margin('|')) | create :transaction, :closed,