diff --git a/CHANGELOG.md b/CHANGELOG.md index e7974d61b..92ed9e043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Add support for `symbol` style for `RSpec/SharedExamples`. ([@jessieay]) + ## 2.25.0 (2023-10-27) - Add support single quoted string and percent string and heredoc for `RSpec/Rails/HttpStatus`. ([@ydah]) @@ -856,6 +858,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features. [@jaredbeck]: https://github.com/jaredbeck [@jaredmoody]: https://github.com/jaredmoody [@jeffreyc]: https://github.com/jeffreyc +[@jessieay]: https://github.com/jessieay [@jfragoulis]: https://github.com/jfragoulis [@johnny-miyake]: https://github.com/johnny-miyake [@jojos003]: https://github.com/jojos003 diff --git a/config/default.yml b/config/default.yml index f672f26f4..55b865501 100644 --- a/config/default.yml +++ b/config/default.yml @@ -832,9 +832,14 @@ RSpec/SharedContext: Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext RSpec/SharedExamples: - Description: Enforces use of string to titleize shared examples. + Description: Checks for consistent style for shared example names. Enabled: true + EnforcedStyle: string + SupportedStyles: + - string + - symbol VersionAdded: '1.25' + VersionChanged: "<>" Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples RSpec/SingleArgumentMessageChain: diff --git a/lib/rubocop/cop/rspec/shared_examples.rb b/lib/rubocop/cop/rspec/shared_examples.rb index 3008b0c42..066e7f19d 100644 --- a/lib/rubocop/cop/rspec/shared_examples.rb +++ b/lib/rubocop/cop/rspec/shared_examples.rb @@ -3,9 +3,14 @@ module RuboCop module Cop module RSpec - # Enforces use of string to titleize shared examples. + # Checks for consistent style for shared example names. # - # @example + # Enforces either `string` or `symbol` for shared example + # names. + # + # This cop can be configured using the `EnforcedStyle` option + # + # @example `EnforcedStyle: string` (default) # # bad # it_behaves_like :foo_bar_baz # it_should_behave_like :foo_bar_baz @@ -20,8 +25,24 @@ module RSpec # shared_examples_for 'foo bar baz' # include_examples 'foo bar baz' # + # @example `EnforcedStyle: symbol` + # # bad + # it_behaves_like 'foo bar baz' + # it_should_behave_like 'foo bar baz' + # shared_examples 'foo bar baz' + # shared_examples_for 'foo bar baz' + # include_examples 'foo bar baz' + # + # # good + # it_behaves_like :foo_bar_baz + # it_should_behave_like :foo_bar_baz + # shared_examples :foo_bar_baz + # shared_examples_for :foo_bar_baz + # include_examples :foo_bar_baz + # class SharedExamples < Base extend AutoCorrector + include ConfigurableEnforcedStyle # @!method shared_examples(node) def_node_matcher :shared_examples, <<~PATTERN @@ -34,19 +55,38 @@ class SharedExamples < Base def on_send(node) shared_examples(node) do ast_node = node.first_argument - next unless ast_node&.sym_type? + next unless offense?(node.first_argument) - checker = Checker.new(ast_node) + checker = new_checker(ast_node) add_offense(checker.node, message: checker.message) do |corrector| corrector.replace(checker.node, checker.preferred_style) end end end + private + + def offense?(ast_node) + if style == :symbol + ast_node&.str_type? + else # string + ast_node&.sym_type? + end + end + + def new_checker(ast_node) + if style == :symbol + SymbolChecker.new(ast_node) + else # string + StringChecker.new(ast_node) + end + + end + # :nodoc: - class Checker + class SymbolChecker MSG = 'Prefer %s over `%s` ' \ - 'to titleize shared examples.' + 'to symbolize shared examples.' attr_reader :node @@ -55,22 +95,30 @@ def initialize(node) end def message - format(MSG, prefer: preferred_style, current: symbol.inspect) + format(MSG, prefer: preferred_style, current: node.value.inspect) end def preferred_style - string = symbol.to_s.tr('_', ' ') - wrap_with_single_quotes(string) + ":#{node.value.to_s.downcase.tr(' ', '_')}" end + end + + class StringChecker + MSG = 'Prefer %s over `%s` ' \ + 'to titleize shared examples.' - private + attr_reader :node - def symbol - node.value + def initialize(node) + @node = node end - def wrap_with_single_quotes(string) - "'#{string}'" + def message + format(MSG, prefer: preferred_style, current: node.value.inspect) + end + + def preferred_style + "'#{node.value.to_s.tr('_', ' ')}'" end end end diff --git a/spec/rubocop/cop/rspec/shared_examples_spec.rb b/spec/rubocop/cop/rspec/shared_examples_spec.rb index 8d8572e4b..bc0347714 100644 --- a/spec/rubocop/cop/rspec/shared_examples_spec.rb +++ b/spec/rubocop/cop/rspec/shared_examples_spec.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true RSpec.describe RuboCop::Cop::RSpec::SharedExamples do - it 'registers an offense when using symbolic title' do - expect_offense(<<-RUBY) + context 'when using default (string) enforced style' do + it 'registers an offense when using symbolic title' do + expect_offense(<<-RUBY) it_behaves_like :foo_bar_baz ^^^^^^^^^^^^ Prefer 'foo bar baz' over `:foo_bar_baz` to titleize shared examples. it_should_behave_like :foo_bar_baz @@ -16,16 +17,16 @@ include_examples :foo_bar_baz, 'foo', 'bar' ^^^^^^^^^^^^ Prefer 'foo bar baz' over `:foo_bar_baz` to titleize shared examples. - shared_examples :foo_bar_baz, 'foo', 'bar' do |param| + shared_examples :foo_bar_baz, 'foo', 'bar', :foo_bar, 5 do |param| ^^^^^^^^^^^^ Prefer 'foo bar baz' over `:foo_bar_baz` to titleize shared examples. # ... end RSpec.shared_examples :foo_bar_baz ^^^^^^^^^^^^ Prefer 'foo bar baz' over `:foo_bar_baz` to titleize shared examples. - RUBY + RUBY - expect_correction(<<-RUBY) + expect_correction(<<-RUBY) it_behaves_like 'foo bar baz' it_should_behave_like 'foo bar baz' shared_examples 'foo bar baz' @@ -33,16 +34,16 @@ include_examples 'foo bar baz' include_examples 'foo bar baz', 'foo', 'bar' - shared_examples 'foo bar baz', 'foo', 'bar' do |param| + shared_examples 'foo bar baz', 'foo', 'bar', :foo_bar, 5 do |param| # ... end RSpec.shared_examples 'foo bar baz' - RUBY - end + RUBY + end - it 'does not register an offense when using string title' do - expect_no_offenses(<<-RUBY) + it 'does not register an offense when using string title' do + expect_no_offenses(<<-RUBY) it_behaves_like 'foo bar baz' it_should_behave_like 'foo bar baz' shared_examples 'foo bar baz' @@ -53,11 +54,11 @@ shared_examples 'foo bar baz', 'foo', 'bar' do |param| # ... end - RUBY - end + RUBY + end - it 'does not register an offense when using Module/Class title' do - expect_no_offenses(<<-RUBY) + it 'does not register an offense when using Module/Class title' do + expect_no_offenses(<<-RUBY) it_behaves_like FooBarBaz it_should_behave_like FooBarBaz shared_examples FooBarBaz @@ -68,6 +69,86 @@ shared_examples FooBarBaz, 'foo', 'bar' do |param| # ... end - RUBY + RUBY + end + end + + context 'when using symbol enforced style' do + let(:cop_config) { { 'EnforcedStyle' => 'symbol' } } + + it 'registers an offense when using string title' do + expect_offense(<<-RUBY) + it_behaves_like 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + it_should_behave_like 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + shared_examples 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + shared_examples_for 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + include_examples 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + include_examples 'Foo Bar Baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"Foo Bar Baz"` to symbolize shared examples. + include_examples 'foo bar baz', 'foo', 'bar' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + + shared_examples 'foo bar baz', 'foo', 'bar' do |param| + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + # ... + end + + RSpec.shared_examples 'foo bar baz' + ^^^^^^^^^^^^^ Prefer :foo_bar_baz over `"foo bar baz"` to symbolize shared examples. + RUBY + + expect_correction(<<-RUBY) + it_behaves_like :foo_bar_baz + it_should_behave_like :foo_bar_baz + shared_examples :foo_bar_baz + shared_examples_for :foo_bar_baz + include_examples :foo_bar_baz + include_examples :foo_bar_baz + include_examples :foo_bar_baz, 'foo', 'bar' + + shared_examples :foo_bar_baz, 'foo', 'bar' do |param| + # ... + end + + RSpec.shared_examples :foo_bar_baz + RUBY + end + + it 'does not register an offense when using symbol' do + expect_no_offenses(<<-RUBY) + it_behaves_like :foo_bar_baz + it_should_behave_like :foo_bar_baz + shared_examples :foo_bar_baz + shared_examples_for :foo_bar_baz + include_examples :foo_bar_baz + include_examples :foo_bar_baz, :foo, :bar + + shared_examples :foo_bar_baz, 'foo', 'bar', 'foo bar', 5 do |param| + # ... + end + + RSpec.shared_examples :foo_bar_baz + RUBY + end + + it 'does not register an offense when using Module/Class title' do + expect_no_offenses(<<-RUBY) + it_behaves_like FooBarBaz + it_should_behave_like FooBarBaz + shared_examples FooBarBaz + shared_examples_for FooBarBaz + include_examples FooBarBaz + include_examples FooBarBaz, 'foo', 'bar' + + shared_examples FooBarBaz, 'foo', 'bar', 'foo bar', 5 do |param| + # ... + end + RUBY + end end end