diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e6d5495f3a..f7964902078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * Add `BracesRequiredMethods` parameter to `Style/BlockDelimiters` to require braces for specific methods such as Sorbet's `sig`. ([@maxh][]) * [#7686](https://github.com/rubocop-hq/rubocop/pull/7686): Add new `JUnitFormatter` formatter based on `rubocop-junit-formatter` gem. ([@koic][]) * [#7715](https://github.com/rubocop-hq/rubocop/pull/7715): Add `Steepfile` to default `Include` list. ([@ybiquitous][]) +* [#7654](https://github.com/rubocop-hq/rubocop/issues/7654): Support `with_fixed_indentation` option for `Layout/ArrayAlignment` cop. ([@nikitasakov][]) ### Bug fixes @@ -4378,3 +4379,4 @@ [@masarakki]: https://github.com/masarakki [@djudd]: https://github.com/djudd [@jemmaissroff]: https://github.com/jemmaissroff +[@nikitasakov]: https://github.com/nikitasakov diff --git a/config/default.yml b/config/default.yml index 5e6bc7f5e0d..5049f96a154 100644 --- a/config/default.yml +++ b/config/default.yml @@ -255,10 +255,30 @@ Layout/ArrayAlignment: Description: >- Align the elements of an array literal if they span more than one line. - StyleGuide: '#align-multiline-arrays' + StyleGuide: '#no-double-indent' Enabled: true VersionAdded: '0.49' VersionChanged: '0.77' + # Alignment of elements of a multi-line array. + # + # The `with_first_parameter` style aligns the following lines along the same + # column as the first element. + # + # array = [1, 2, 3, + # 4, 5, 6] + # + # The `with_fixed_indentation` style aligns the following lines with one + # level of indentation relative to the start of the line with start of array. + # + # array = [1, 2, 3, + # 4, 5, 6] + EnforcedStyle: with_first_element + SupportedStyles: + - with_first_element + - with_fixed_indentation + # By default, the indentation width from Layout/IndentationWidth is used + # But it can be overridden by setting this parameter + IndentationWidth: ~ Layout/AssignmentIndentation: Description: >- diff --git a/lib/rubocop/cop/layout/array_alignment.rb b/lib/rubocop/cop/layout/array_alignment.rb index 0db3e85e3cc..ea57a425ed6 100644 --- a/lib/rubocop/cop/layout/array_alignment.rb +++ b/lib/rubocop/cop/layout/array_alignment.rb @@ -6,33 +6,76 @@ module Layout # Here we check if the elements of a multi-line array literal are # aligned. # - # @example + # @example EnforcedStyle: with_first_element (default) + # # good + # + # array = [1, 2, 3, + # 4, 5, 6] + # array = ['run', + # 'forrest', + # 'run'] + # # # bad - # a = [1, 2, 3, + # + # array = [1, 2, 3, # 4, 5, 6] # array = ['run', # 'forrest', # 'run'] # + # @example EnforcedStyle: with_fixed_indentation # # good - # a = [1, 2, 3, - # 4, 5, 6] - # a = ['run', - # 'forrest', - # 'run'] + # + # array = [1, 2, 3, + # 4, 5, 6] + # + # # bad + # + # array = [1, 2, 3, + # 4, 5, 6] class ArrayAlignment < Cop include Alignment - MSG = 'Align the elements of an array literal if they span more ' \ - 'than one line.' + ALIGN_ELEMENTS_MSG = 'Align the elements of an array literal ' \ + 'if they span more than one line.' + + FIXED_INDENT_MSG = 'Use one level of indentation for elements ' \ + 'following the first line of a multi-line array.' def on_array(node) - check_alignment(node.children) + return if node.children.size < 2 + + check_alignment(node.children, base_column(node, node.children)) end def autocorrect(node) AlignmentCorrector.correct(processed_source, node, column_delta) end + + private + + def message(_node) + fixed_indentation? ? FIXED_INDENT_MSG : ALIGN_ELEMENTS_MSG + end + + def fixed_indentation? + cop_config['EnforcedStyle'] == 'with_fixed_indentation' + end + + def base_column(node, args) + if fixed_indentation? + 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) + end + end + + def target_method_lineno(node) + node.loc.line + end end end end diff --git a/manual/cops_layout.md b/manual/cops_layout.md index 50d87f25b57..2cccc572a34 100644 --- a/manual/cops_layout.md +++ b/manual/cops_layout.md @@ -124,25 +124,49 @@ aligned. ### Examples +#### EnforcedStyle: with_first_element (default) + ```ruby +# good + +array = [1, 2, 3, + 4, 5, 6] +array = ['run', + 'forrest', + 'run'] + # bad -a = [1, 2, 3, + +array = [1, 2, 3, 4, 5, 6] array = ['run', 'forrest', 'run'] +``` +#### EnforcedStyle: with_fixed_indentation +```ruby # good -a = [1, 2, 3, - 4, 5, 6] -a = ['run', - 'forrest', - 'run'] + +array = [1, 2, 3, + 4, 5, 6] + +# bad + +array = [1, 2, 3, + 4, 5, 6] ``` +### Configurable attributes + +Name | Default value | Configurable values +--- | --- | --- +EnforcedStyle | `with_first_element` | `with_first_element`, `with_fixed_indentation` +IndentationWidth | `` | Integer + ### References -* [https://rubystyle.guide#align-multiline-arrays](https://rubystyle.guide#align-multiline-arrays) +* [https://rubystyle.guide#no-double-indent](https://rubystyle.guide#no-double-indent) ## Layout/AssignmentIndentation diff --git a/spec/rubocop/cop/layout/array_alignment_spec.rb b/spec/rubocop/cop/layout/array_alignment_spec.rb index 45ec3aeb6e2..087349905b9 100644 --- a/spec/rubocop/cop/layout/array_alignment_spec.rb +++ b/spec/rubocop/cop/layout/array_alignment_spec.rb @@ -1,133 +1,332 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Layout::ArrayAlignment do - subject(:cop) { described_class.new } - - it 'registers an offense and corrects misaligned array elements' do - expect_offense(<<~RUBY) - array = [ - a, - b, - ^ Align the elements of an array literal if they span more than one line. - c, - d - ^ Align the elements of an array literal if they span more than one line. - ] - RUBY - - expect_correction(<<~RUBY) - array = [ - a, - b, - c, - d - ] - RUBY - end + subject(:cop) { described_class.new(config) } - it 'accepts aligned array keys' do - expect_no_offenses(<<~RUBY) - array = [ - a, - b, - c, - d - ] - RUBY + let(:config) do + RuboCop::Config.new('Layout/ArrayAlignment' => cop_config, + 'Layout/IndentationWidth' => { + 'Width' => indentation_width + }) end + let(:indentation_width) { 2 } - it 'accepts single line array' do - expect_no_offenses('array = [ a, b ]') - end + context 'when aligned with first parameter' do + let(:cop_config) do + { + 'EnforcedStyle' => 'with_first_element' + } + end - it 'accepts several elements per line' do - expect_no_offenses(<<~RUBY) - array = [ a, b, - c, d ] - RUBY - end + it 'registers an offense and corrects misaligned array elements' do + expect_offense(<<~RUBY) + array = [a, + b, + ^ Align the elements of an array literal if they span more than one line. + c, + ^ Align the elements of an array literal if they span more than one line. + d] + ^ Align the elements of an array literal if they span more than one line. + RUBY - it 'accepts aligned array with fullwidth characters' do - expect_no_offenses(<<~RUBY) - puts 'Ruby', [ a, - b ] - RUBY - end + expect_correction(<<~RUBY) + array = [a, + b, + c, + d] + RUBY + end + + it 'accepts aligned array keys' do + expect_no_offenses(<<~RUBY) + array = [a, + b, + c, + d] + RUBY + end + + it 'accepts single line array' do + expect_no_offenses('array = [ a, b ]') + end + + it 'accepts several elements per line' do + expect_no_offenses(<<~RUBY) + array = [ a, b, + c, d ] + RUBY + end + + it 'accepts aligned array with fullwidth characters' do + expect_no_offenses(<<~RUBY) + puts 'Ruby', [ a, + b ] + RUBY + end - it 'does not auto-correct array within array with too much indentation' do - expect_offense(<<~RUBY) - [:l1, + it 'does not auto-correct array within array with too much indentation' do + expect_offense(<<~RUBY) + [:l1, + [:l2, + ^^^^^ Align the elements of an array literal if they span more than one line. + [:l3, + ^^^^^ Align the elements of an array literal if they span more than one line. + [:l4]]]] + RUBY + + expect_correction(<<~RUBY) + [:l1, + [:l2, + [:l3, + [:l4]]]] + RUBY + end + + it 'does not auto-correct array within array with too little indentation' do + expect_offense(<<~RUBY) + [:l1, [:l2, ^^^^^ Align the elements of an array literal if they span more than one line. - [:l3, ^^^^^ Align the elements of an array literal if they span more than one line. [:l4]]]] - RUBY + RUBY - expect_correction(<<~RUBY) - [:l1, - [:l2, + expect_correction(<<~RUBY) + [:l1, + [:l2, + [:l3, + [:l4]]]] + RUBY + end - [:l3, - [:l4]]]] - RUBY - end + it 'does not indent heredoc strings in autocorrect' do + expect_offense(<<~RUBY) + var = [ + { :type => 'something', + :sql => < 'something', + ^^^^^^^^^^^^^^^^^^^^^^^ Align the elements of an array literal if they span more than one line. + :sql => < 'something', + :sql => < 'something', + :sql => < 'something', - :sql => < 'something', - ^^^^^^^^^^^^^^^^^^^^^^^ Align the elements of an array literal if they span more than one line. - :sql => < 'something', - :sql => < 'something', - :sql => < 'with_fixed_indentation' + } + end + + it 'registers an offense and corrects misaligned array elements' do + expect_offense(<<~RUBY) + array = [a, + b, + ^ Use one level of indentation for elements following the first line of a multi-line array. + c, + d] + ^ Use one level of indentation for elements following the first line of a multi-line array. + RUBY + + expect_correction(<<~RUBY) + array = [a, + b, + c, + d] + RUBY + end + + it 'accepts aligned array keys' do + expect_no_offenses(<<~RUBY) + array = [a, + b, + c, + d] + RUBY + end + + it 'accepts single line array' do + expect_no_offenses('array = [ a, b ]') + end + + it 'accepts several elements per line' do + expect_no_offenses(<<~RUBY) + array = [ a, b, + c, d ] + RUBY + end + + it 'accepts aligned array with fullwidth characters' do + expect_no_offenses(<<~RUBY) + puts 'Ruby', [ a, + b ] + RUBY + end + + it 'does not auto-correct array within array with too much indentation' do + expect_offense(<<~RUBY) + [:l1, + [:l2, + ^^^^^ Use one level of indentation for elements following the first line of a multi-line array. + [:l3, + ^^^^^ Use one level of indentation for elements following the first line of a multi-line array. + [:l4]]]] + RUBY + + expect_correction(<<~RUBY) + [:l1, + [:l2, + [:l3, + [:l4]]]] + RUBY + end + + it 'does not auto-correct array within array with too little indentation' do + expect_offense(<<~RUBY) + [:l1, + [:l2, + ^^^^^ Use one level of indentation for elements following the first line of a multi-line array. + [:l3, + ^^^^^ Use one level of indentation for elements following the first line of a multi-line array. + [:l4]]]] + RUBY + + expect_correction(<<~RUBY) + [:l1, + [:l2, + [:l3, + [:l4]]]] + RUBY + end + + it 'does not indent heredoc strings in autocorrect' do + expect_offense(<<~RUBY) + var = [ + { :type => 'something', + :sql => < 'something', + ^^^^^^^^^^^^^^^^^^^^^^^ Use one level of indentation for elements following the first line of a multi-line array. + :sql => < 'something', + :sql => < 'something', + :sql => <