diff --git a/CHANGELOG.md b/CHANGELOG.md index 687cf4aa587..3a7d2912b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* [#6973](https://github.com/rubocop-hq/rubocop/pull/6973): Add `always_braces` to `Style/BlockDelimiter`. ([@iGEL][]) * [#6841](https://github.com/rubocop-hq/rubocop/issues/6841): Node patterns can now match children in any order using `<>`. ([@marcandre][]) * [#6928](https://github.com/rubocop-hq/rubocop/pull/6928): Add `--init` option for generate `.rubocop.yml` file in the current directory. ([@koic][]) * Add new `Layout/HeredocArgumentClosingParenthesis` cop. ([@maxh][]) diff --git a/config/default.yml b/config/default.yml index 24fa2bc7ec1..6a1e9fbd138 100644 --- a/config/default.yml +++ b/config/default.yml @@ -2663,6 +2663,8 @@ Style/BlockDelimiters: # return value is being chained with another method (in which case braces # are enforced). - braces_for_chaining + # The `always_braces` style always enforces braces. + - always_braces ProceduralMethods: # Methods that are known to be procedural in nature but look functional from # their usage, e.g. diff --git a/lib/rubocop/cop/style/block_delimiters.rb b/lib/rubocop/cop/style/block_delimiters.rb index ce0e25a7ffb..c2fa577c641 100644 --- a/lib/rubocop/cop/style/block_delimiters.rb +++ b/lib/rubocop/cop/style/block_delimiters.rb @@ -95,10 +95,24 @@ module Style # word.flip.flop # }.join("-") # + # @example EnforcedStyle: always_braces + # # bad + # words.each do |word| + # word.flip.flop + # end + # + # # good + # words.each { |word| + # word.flip.flop + # } + # class BlockDelimiters < Cop include ConfigurableEnforcedStyle include IgnoredMethods + ALWAYS_BRACES_MESSAGE = 'Prefer `{...}` over `do...end` for blocks.' + .freeze + def on_send(node) return unless node.arguments? return if node.parenthesized? || node.operator_method? @@ -166,6 +180,7 @@ def message(node) when :line_count_based then line_count_based_message(node) when :semantic then semantic_message(node) when :braces_for_chaining then braces_for_chaining_message(node) + when :always_braces then ALWAYS_BRACES_MESSAGE end end @@ -229,6 +244,7 @@ def proper_block_style?(node) when :line_count_based then line_count_based_block_style?(node) when :semantic then semantic_block_style?(node) when :braces_for_chaining then braces_for_chaining_style?(node) + when :always_braces then braces_style?(node) end end @@ -257,6 +273,10 @@ def braces_for_chaining_style?(node) end end + def braces_style?(node) + node.loc.begin.source == '{' + end + def return_value_chaining?(node) node.parent && node.parent.send_type? && node.parent.dot? end diff --git a/manual/cops_style.md b/manual/cops_style.md index 218f78ed462..f89dacef5fb 100644 --- a/manual/cops_style.md +++ b/manual/cops_style.md @@ -460,12 +460,25 @@ words.each { |word| word.flip.flop }.join("-") ``` +#### EnforcedStyle: always_braces + +```ruby +# bad +words.each do |word| + word.flip.flop +end + +# good +words.each { |word| + word.flip.flop +} +``` ### Configurable attributes Name | Default value | Configurable values --- | --- | --- -EnforcedStyle | `line_count_based` | `line_count_based`, `semantic`, `braces_for_chaining` +EnforcedStyle | `line_count_based` | `line_count_based`, `semantic`, `braces_for_chaining`, `always_braces` ProceduralMethods | `benchmark`, `bm`, `bmbm`, `create`, `each_with_object`, `measure`, `new`, `realtime`, `tap`, `with_object` | Array FunctionalMethods | `let`, `let!`, `subject`, `watch` | Array IgnoredMethods | `lambda`, `proc`, `it` | Array diff --git a/spec/rubocop/cop/style/block_delimiters_spec.rb b/spec/rubocop/cop/style/block_delimiters_spec.rb index 51e4b466999..76c7b562cd0 100644 --- a/spec/rubocop/cop/style/block_delimiters_spec.rb +++ b/spec/rubocop/cop/style/block_delimiters_spec.rb @@ -507,4 +507,97 @@ end end end + + context 'always braces' do + cop_config = { + 'EnforcedStyle' => 'always_braces', + 'IgnoredMethods' => %w[proc] + } + + let(:cop_config) { cop_config } + + it 'registers an offense for a single line block with do-end' do + expect_offense(<<-RUBY.strip_indent) + each do |x| end + ^^ Prefer `{...}` over `do...end` for blocks. + RUBY + end + + it 'accepts a single line block with braces' do + expect_no_offenses('each { |x| }') + end + + it 'registers an offence for a multi-line block with do-end' do + expect_offense(<<-RUBY.strip_indent) + each do |x| + ^^ Prefer `{...}` over `do...end` for blocks. + end + RUBY + end + + it 'auto-corrects do and end for single line blocks to { and }' do + new_source = autocorrect_source('block do |x| end') + expect(new_source).to eq('block { |x| }') + end + + it 'does not auto-correct do-end if {} would change the meaning' do + src = "s.subspec 'Subspec' do |sp| end" + new_source = autocorrect_source(src) + expect(new_source).to eq(src) + end + + it 'accepts a multi-line block that needs braces to be valid ruby' do + expect_no_offenses(<<-RUBY.strip_indent) + puts [1, 2, 3].map { |n| + n * n + }, 1 + RUBY + end + + it 'registers an offense for multi-line chained do-end blocks' do + expect_offense(<<-RUBY.strip_indent) + each do |x| + ^^ Prefer `{...}` over `do...end` for blocks. + end.map(&:to_s) + RUBY + end + + it 'auto-corrects do-end for chained blocks' do + src = <<-RUBY.strip_indent + each do |x| + end.map(&:to_s) + RUBY + new_source = autocorrect_source(src) + expect(new_source).to eq(<<-RUBY.strip_indent) + each { |x| + }.map(&:to_s) + RUBY + end + + it 'accepts a multi-line functional block with do-end if it is ' \ + 'an ignored method' do + expect_no_offenses(<<-RUBY.strip_indent) + foo = proc do + puts 42 + end + RUBY + end + + context 'when there are braces around a multi-line block' do + it 'registers an offense in the simple case' do + expect_offense(<<-RUBY.strip_indent) + each do |x| + ^^ Prefer `{...}` over `do...end` for blocks. + end + RUBY + end + + it 'allows when the block is being chained' do + expect_no_offenses(<<-RUBY.strip_indent) + each { |x| + }.map(&:to_sym) + RUBY + end + end + end end