diff --git a/.rubocop.yml b/.rubocop.yml index f4d81a2ecdd..61765c3323f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -49,6 +49,7 @@ Layout/ClassStructure: Layout/RedundantLineBreak: Enabled: true + InspectBlocks: true Layout/TrailingWhitespace: AllowInHeredoc: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 378b61dcf07..a261d67bcc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features * [#7977](https://github.com/rubocop/rubocop/issues/7977): Add `Layout/RedundantLineBreak` cop. ([@jonas054][]) +* [#9691](https://github.com/rubocop/rubocop/issues/9691): Add configuration parameter `InspectBlocks` to `Layout/RedundantLineBreak`. ([@jonas054][]) ## 1.12.1 (2021-04-04) diff --git a/config/default.yml b/config/default.yml index d29f1371265..b561e77fd6b 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1106,6 +1106,7 @@ Layout/RedundantLineBreak: Do not break up an expression into multiple lines when it fits on a single line. Enabled: false + InspectBlocks: false VersionAdded: '<>' Layout/RescueEnsureAlignment: diff --git a/lib/rubocop/cop/layout/redundant_line_break.rb b/lib/rubocop/cop/layout/redundant_line_break.rb index e8686ea31b3..2009a862779 100644 --- a/lib/rubocop/cop/layout/redundant_line_break.rb +++ b/lib/rubocop/cop/layout/redundant_line_break.rb @@ -6,17 +6,13 @@ module Layout # This cop checks whether certain expressions, e.g. method calls, that could fit # completely on a single line, are broken up into multiple lines unnecessarily. # - # @example + # @example any configuration # # bad # foo( # a, # b # ) # - # foo(a) do |x| - # puts x - # end - # # puts 'string that fits on ' \ # 'a single line' # @@ -27,12 +23,25 @@ module Layout # # good # foo(a, b) # - # foo(a) { |x| puts x } - # # puts 'string that fits on a single line' # # things.select { |thing| thing.cond? }.join('-') # + # @example InspectBlocks: false (default) + # # good + # foo(a) do |x| + # puts x + # end + # + # @example InspectBlocks: true + # # bad + # foo(a) do |x| + # puts x + # end + # + # # good + # foo(a) { |x| puts x } + # class RedundantLineBreak < Cop include CheckAssignment @@ -64,7 +73,14 @@ def autocorrect(node) private def offense?(node) - !single_line?(node) && !too_long?(node) && suitable_as_single_line?(node) + return false if configured_to_not_be_inspected?(node) + + node.multiline? && !too_long?(node) && suitable_as_single_line?(node) + end + + def configured_to_not_be_inspected?(node) + !cop_config['InspectBlocks'] && (node.block_type? || + node.each_child_node(:block).any?(&:multiline?)) end def suitable_as_single_line?(node) @@ -87,10 +103,6 @@ def comment_within?(node) end end - def single_line?(node) - node.first_line == node.last_line - end - def too_long?(node) lines = processed_source.lines[(node.first_line - 1)...node.last_line] to_single_line(lines.join("\n")).length > max_line_length diff --git a/spec/rubocop/cop/layout/redundant_line_break_spec.rb b/spec/rubocop/cop/layout/redundant_line_break_spec.rb index 7c9ed85c046..2fc66bb9933 100644 --- a/spec/rubocop/cop/layout/redundant_line_break_spec.rb +++ b/spec/rubocop/cop/layout/redundant_line_break_spec.rb @@ -1,341 +1,383 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::Layout::RedundantLineBreak, :config do - let(:config) { RuboCop::Config.new('Layout/LineLength' => { 'Max' => 31 }) } - - context 'for an expression that fits on a single line' do - it 'accepts an assignment containing an if expression' do - expect_no_offenses(<<~RUBY) - a = - if x - 1 - else - 2 - end - RUBY - end - - it 'accepts an assignment containing a case expression' do - expect_no_offenses(<<~RUBY) - a = - case x - when :a - 1 - else - 2 - end - RUBY - end - - it 'accepts a binary expression containing an if expression' do - expect_no_offenses(<<~RUBY) - a + - if x - 1 - else - 2 - end - RUBY - end + let(:config) do + RuboCop::Config.new('Layout/LineLength' => { 'Max' => max_line_length }, + 'Layout/RedundantLineBreak' => { 'InspectBlocks' => inspect_blocks }) + end + let(:max_line_length) { 31 } - it 'accepts a method call with a block' do - expect_no_offenses(<<~RUBY) - a do - x - y - end - RUBY - end + shared_examples 'common behavior' do + context 'for an expression that fits on a single line' do + it 'accepts an assignment containing an if expression' do + expect_no_offenses(<<~RUBY) + a = + if x + 1 + else + 2 + end + RUBY + end - it 'accepts an assignment containing a begin-end expression' do - expect_no_offenses(<<~RUBY) - a ||= begin - x - y - end - RUBY - end + it 'accepts an assignment containing a case expression' do + expect_no_offenses(<<~RUBY) + a = + case x + when :a + 1 + else + 2 + end + RUBY + end - it 'accepts a method call on a single line' do - expect_no_offenses(<<~RUBY) - my_method(1, 2, "x") - RUBY - end + it 'accepts a binary expression containing an if expression' do + expect_no_offenses(<<~RUBY) + a + + if x + 1 + else + 2 + end + RUBY + end - it 'registers an offense for a method call on multiple lines with backslash' do - expect_offense(<<~RUBY) - my_method(1) \\ - ^^^^^^^^^^^^^^ Redundant line break detected. - [:a] - RUBY + it 'accepts a method call with a block' do + expect_no_offenses(<<~RUBY) + a do + x + y + end + RUBY + end - expect_correction(<<~RUBY) - my_method(1) [:a] - RUBY - end + it 'accepts an assignment containing a begin-end expression' do + expect_no_offenses(<<~RUBY) + a ||= begin + x + y + end + RUBY + end - context 'with LineLength Max 100' do - let(:config) { RuboCop::Config.new('Layout/LineLength' => { 'Max' => 100 }) } + it 'accepts a method call on a single line' do + expect_no_offenses(<<~RUBY) + my_method(1, 2, "x") + RUBY + end - it 'registers an offense for a method without parentheses on multiple lines' do + it 'registers an offense for a method call on multiple lines with backslash' do expect_offense(<<~RUBY) - def resolve_inheritance_from_gems(hash) - gems = hash.delete('inherit_gem') - (gems || {}).each_pair do |gem_name, config_path| - if gem_name == 'rubocop' - raise ArgumentError, - ^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. - "can't inherit configuration from the rubocop gem" - end - #{' '} - hash['inherit_from'] = Array(hash['inherit_from']) - Array(config_path).reverse_each do |path| - # Put gem configuration first so local configuration overrides it. - hash['inherit_from'].unshift gem_config_path(gem_name, path) - end - end - end + my_method(1) \\ + ^^^^^^^^^^^^^^ Redundant line break detected. + [:a] RUBY expect_correction(<<~RUBY) - def resolve_inheritance_from_gems(hash) - gems = hash.delete('inherit_gem') - (gems || {}).each_pair do |gem_name, config_path| - if gem_name == 'rubocop' - raise ArgumentError, "can't inherit configuration from the rubocop gem" + my_method(1) [:a] + RUBY + end + + context 'with LineLength Max 100' do + let(:max_line_length) { 100 } + + it 'registers an offense for a method without parentheses on multiple lines' do + expect_offense(<<~RUBY) + def resolve_inheritance_from_gems(hash) + gems = hash.delete('inherit_gem') + (gems || {}).each_pair do |gem_name, config_path| + if gem_name == 'rubocop' + raise ArgumentError, + ^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. + "can't inherit configuration from the rubocop gem" + end + #{' '} + hash['inherit_from'] = Array(hash['inherit_from']) + Array(config_path).reverse_each do |path| + # Put gem configuration first so local configuration overrides it. + hash['inherit_from'].unshift gem_config_path(gem_name, path) + end end - #{' '} - hash['inherit_from'] = Array(hash['inherit_from']) - Array(config_path).reverse_each do |path| - # Put gem configuration first so local configuration overrides it. - hash['inherit_from'].unshift gem_config_path(gem_name, path) + end + RUBY + + expect_correction(<<~RUBY) + def resolve_inheritance_from_gems(hash) + gems = hash.delete('inherit_gem') + (gems || {}).each_pair do |gem_name, config_path| + if gem_name == 'rubocop' + raise ArgumentError, "can't inherit configuration from the rubocop gem" + end + #{' '} + hash['inherit_from'] = Array(hash['inherit_from']) + Array(config_path).reverse_each do |path| + # Put gem configuration first so local configuration overrides it. + hash['inherit_from'].unshift gem_config_path(gem_name, path) + end end end - end - RUBY + RUBY + end end - end - it 'registers an offense for a method call on multiple lines' do - expect_offense(<<~RUBY) - my_method(1, - ^^^^^^^^^^^^ Redundant line break detected. - 2, - "x") - RUBY - - expect_correction(<<~RUBY) - my_method(1, 2, "x") - RUBY - end - - it 'accepts a method call on multiple lines if there are comments on them' do - expect_no_offenses(<<~RUBY) - my_method(1, - 2, - "x") # X - RUBY - end + it 'registers an offense for a method call on multiple lines' do + expect_offense(<<~RUBY) + my_method(1, + ^^^^^^^^^^^^ Redundant line break detected. + 2, + "x") + RUBY - it 'registers an offense for a method call with a double quoted split string in parentheses' do - expect_offense(<<~RUBY) - my_method("a" \\ - ^^^^^^^^^^^^^^^ Redundant line break detected. - "b") - RUBY + expect_correction(<<~RUBY) + my_method(1, 2, "x") + RUBY + end - expect_correction(<<~RUBY) - my_method("ab") - RUBY - end + it 'accepts a method call on multiple lines if there are comments on them' do + expect_no_offenses(<<~RUBY) + my_method(1, + 2, + "x") # X + RUBY + end - it 'registers an offense for a method call with a double quoted split string without parentheses' do - expect_offense(<<~RUBY) - puts "(\#{pl(i)}, " \\ - ^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. - "\#{pl(f)})" - RUBY + it 'registers an offense for a method call with a double quoted split string in parentheses' do + expect_offense(<<~RUBY) + my_method("a" \\ + ^^^^^^^^^^^^^^^ Redundant line break detected. + "b") + RUBY - expect_correction(<<~RUBY) - puts "(\#{pl(i)}, \#{pl(f)})" - RUBY - end + expect_correction(<<~RUBY) + my_method("ab") + RUBY + end - it 'registers an offense for a method call with a single quoted split string' do - expect_offense(<<~RUBY) - my_method('a'\\ - ^^^^^^^^^^^^^^ Redundant line break detected. - 'b') - RUBY + it 'registers an offense for a method call with a double quoted split string without parentheses' do + expect_offense(<<~RUBY) + puts "(\#{pl(i)}, " \\ + ^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. + "\#{pl(f)})" + RUBY - expect_correction(<<~RUBY) - my_method('ab') - RUBY - end + expect_correction(<<~RUBY) + puts "(\#{pl(i)}, \#{pl(f)})" + RUBY + end - it 'registers an offense for a method call with a double and single quoted split string' do - expect_offense(<<~RUBY) - my_method("a" \\ - ^^^^^^^^^^^^^^^ Redundant line break detected. - 'b') - my_method('a' \\ - ^^^^^^^^^^^^^^^ Redundant line break detected. - "b") - RUBY - - expect_correction(<<~RUBY) - my_method("a" + 'b') - my_method('a' + "b") - RUBY - end + it 'registers an offense for a method call with a single quoted split string' do + expect_offense(<<~RUBY) + my_method('a'\\ + ^^^^^^^^^^^^^^ Redundant line break detected. + 'b') + RUBY - it 'registers an offense for a method call with a split operation' do - expect_offense(<<~RUBY) - my_method(1 + - ^^^^^^^^^^^^^ Redundant line break detected. - 2 + - 3) - RUBY - - expect_correction(<<~RUBY) - my_method(1 + 2 + 3) - RUBY - end + expect_correction(<<~RUBY) + my_method('ab') + RUBY + end - it 'registers an offense for a method call as right hand side of an assignment' do - expect_offense(<<~RUBY) - a = - ^^^ Redundant line break detected. - m(1 + - 2 + - 3) - b = m(4 + - ^^^^^^^^^ Redundant line break detected. - 5 + - 6) - long_variable_name = - m(7 + - ^^^^^ Redundant line break detected. - 8 + - 9) - RUBY - - expect_correction(<<~RUBY) - a = m(1 + 2 + 3) - b = m(4 + 5 + 6) - long_variable_name = - m(7 + 8 + 9) - RUBY - end - end + it 'registers an offense for a method call with a double and single quoted split string' do + expect_offense(<<~RUBY) + my_method("a" \\ + ^^^^^^^^^^^^^^^ Redundant line break detected. + 'b') + my_method('a' \\ + ^^^^^^^^^^^^^^^ Redundant line break detected. + "b") + RUBY - context 'for an expression that does not fit on a single line' do - it 'accepts a method call on a multiple lines' do - expect_no_offenses(<<~RUBY) - my_method(11111, - 22222, - "abcxyz") - my_method(111111 + - 222222 + - 333333) - RUBY - end + expect_correction(<<~RUBY) + my_method("a" + 'b') + my_method('a' + "b") + RUBY + end - context 'with a longer max line length' do - let(:config) { RuboCop::Config.new('Layout/LineLength' => { 'Max' => 82 }) } + it 'registers an offense for a method call with a split operation' do + expect_offense(<<~RUBY) + my_method(1 + + ^^^^^^^^^^^^^ Redundant line break detected. + 2 + + 3) + RUBY - it 'accepts an assignment containing a method definition' do - expect_no_offenses(<<~RUBY) - VariableReference = Struct.new(:name) do - def assignment? - false - end - end + expect_correction(<<~RUBY) + my_method(1 + 2 + 3) RUBY end - it 'accepts a method call followed by binary operations that are too long taken together' do - expect_no_offenses(<<~RUBY) - File.fnmatch?( - pattern, path, - File::FNM_PATHNAME | File::FNM_EXTGLOB - ) && a && File.basename(path).start_with?('.') && !hidden_dir?(path) + it 'registers an offense for a method call as right hand side of an assignment' do + expect_offense(<<~RUBY) + a = + ^^^ Redundant line break detected. + m(1 + + 2 + + 3) + b = m(4 + + ^^^^^^^^^ Redundant line break detected. + 5 + + 6) + long_variable_name = + m(7 + + ^^^^^ Redundant line break detected. + 8 + + 9) RUBY - expect_no_offenses(<<~RUBY) - File.fnmatch?( - pattern, path, - File::FNM_PATHNAME | File::FNM_EXTGLOB - ) + a + File.basename(path).start_with?('.') + !hidden_dir?(path) + + expect_correction(<<~RUBY) + a = m(1 + 2 + 3) + b = m(4 + 5 + 6) + long_variable_name = + m(7 + 8 + 9) RUBY end + end - it 'accepts an assignment containing a heredoc' do + context 'for an expression that does not fit on a single line' do + it 'accepts a method call on a multiple lines' do expect_no_offenses(<<~RUBY) - correct = lambda do - autocorrect_source(<<~EOT1) - <<-EOT2 - foo - EOT2 - EOT1 - end + my_method(11111, + 22222, + "abcxyz") + my_method(111111 + + 222222 + + 333333) RUBY end - context 'for a block' do - it 'accepts when it is difficult to convert to single line' do + context 'with a longer max line length' do + let(:max_line_length) { 82 } + + it 'accepts an assignment containing a method definition' do expect_no_offenses(<<~RUBY) - RSpec.shared_context 'ruby 2.4', :ruby24 do - let(:ruby_version) { 2.4 } + VariableReference = Struct.new(:name) do + def assignment? + false + end end RUBY end - it 'registers an offense when the method call has parentheses' do - expect_offense(<<~RUBY) - RSpec.shared_context('ruby 2.4', :ruby24) do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. - let(:ruby_version) { 2.4 } + it 'accepts a method call followed by binary operations that are too long taken together' do + expect_no_offenses(<<~RUBY) + File.fnmatch?( + pattern, path, + File::FNM_PATHNAME | File::FNM_EXTGLOB + ) && a && File.basename(path).start_with?('.') && !hidden_dir?(path) + RUBY + expect_no_offenses(<<~RUBY) + File.fnmatch?( + pattern, path, + File::FNM_PATHNAME | File::FNM_EXTGLOB + ) + a + File.basename(path).start_with?('.') + !hidden_dir?(path) + RUBY + end + + it 'accepts an assignment containing a heredoc' do + expect_no_offenses(<<~RUBY) + correct = lambda do + autocorrect_source(<<~EOT1) + <<-EOT2 + foo + EOT2 + EOT1 end RUBY end - it 'registers an offense when the method call has no argumnets' do + it 'accepts a complex method call on a multiple lines' do + expect_no_offenses(<<~RUBY) + node.each_node(:dstr) + .select(&:heredoc?) + .map { |n| n.loc.heredoc_body } + .flat_map { |b| (b.line...b.last_line).to_a } + RUBY + end + + it 'accepts method call with a do keyword that would just surpass the max line length' do + expect_no_offenses(<<~RUBY) + context 'when the configuration includes ' \\ + 'an unsafe cop that is 123456789012345678' do + end + RUBY + end + + it 'registers an offense for a method call with a do keyword that is just under the max line length' do expect_offense(<<~RUBY) - RSpec.shared_context do - ^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. - let(:ruby_version) { 2.4 } + context 'when the configuration includes ' \\ + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. + 'an unsafe cop that is 123456789012345' do + end + RUBY + + expect_correction(<<~RUBY) + context 'when the configuration includes an unsafe cop that is 123456789012345' do end RUBY end + + context 'for a block' do + it 'accepts when it is difficult to convert to single line' do + expect_no_offenses(<<~RUBY) + RSpec.shared_context 'ruby 2.4', :ruby24 do + let(:ruby_version) { 2.4 } + end + RUBY + end + end end + end + end - it 'accepts a complex method call on a multiple lines' do - expect_no_offenses(<<~RUBY) - node.each_node(:dstr) - .select(&:heredoc?) - .map { |n| n.loc.heredoc_body } - .flat_map { |b| (b.line...b.last_line).to_a } + context 'when InspectBlocks is true' do + let(:inspect_blocks) { true } + + include_examples 'common behavior' + + context 'for a block' do + let(:max_line_length) { 82 } + + it 'registers an offense when the method call has parentheses' do + expect_offense(<<~RUBY) + RSpec.shared_context('ruby 2.4', :ruby24) do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. + let(:ruby_version) { 2.4 } + end RUBY end - it 'accepts method call with a do keyword that would just surpass the max line length' do - expect_no_offenses(<<~RUBY) - context 'when the configuration includes ' \\ - 'an unsafe cop that is 123456789012345678' do + it 'registers an offense when the method call has no argumnets' do + expect_offense(<<~RUBY) + RSpec.shared_context do + ^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. + let(:ruby_version) { 2.4 } end RUBY end + end + end - it 'registers an offense for a method call with a do keyword that is just under the max line length' do - expect_offense(<<~RUBY) - context 'when the configuration includes ' \\ - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Redundant line break detected. - 'an unsafe cop that is 123456789012345' do + context 'when InspectBlocks is false' do + let(:inspect_blocks) { false } + + include_examples 'common behavior' + + context 'for a block' do + let(:max_line_length) { 100 } + + it 'accepts when the method call has parentheses' do + expect_no_offenses(<<~RUBY) + a = RSpec.shared_context('ruby 2.4', :ruby24) do + let(:ruby_version) { 2.4 } end RUBY + end - expect_correction(<<~RUBY) - context 'when the configuration includes an unsafe cop that is 123456789012345' do + it 'accepts when the method call has no argumnets' do + expect_no_offenses(<<~RUBY) + RSpec.shared_context do + let(:ruby_version) { 2.4 } end RUBY end