Skip to content

Commit

Permalink
Add support for enforced symbolized shared examples
Browse files Browse the repository at this point in the history
* Existing check only allows to enforce one style, which is called
  "titelized" in the error message but really just looks for a string
  type.
* Enforcing the existing style raises an error when a shared example
  uses a symbol definition rather than a string.
* But there are some benefits to using symbol types instead, as is
  discussed here: https://gitlab.com/gitlab-org/gitlab/-/issues/427697
* As a result, some codebases might want to enforce a symbol type
  instead.
  • Loading branch information
jessieay committed Dec 11, 2023
1 parent 9d51eb0 commit 842e690
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 97 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples

RSpec/SingleArgumentMessageChain:
Expand Down
40 changes: 38 additions & 2 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4984,13 +4984,20 @@ end
| Yes
| Yes
| 1.25
| -
| <<next>>
|===

Enforces use of string to titleize shared examples.
Checks for consistent style for shared example names.

Enforces either `string` or `symbol` for shared example
names.

This cop can be configured using the `EnforcedStyle` option

=== Examples

==== `EnforcedStyle: string` (default)

[source,ruby]
----
# bad
Expand All @@ -5008,6 +5015,35 @@ shared_examples_for 'foo bar baz'
include_examples 'foo bar baz'
----

==== `EnforcedStyle: symbol`

[source,ruby]
----
# 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
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| EnforcedStyle
| `string`
| `string`, `symbol`
|===

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples
Expand Down
80 changes: 64 additions & 16 deletions lib/rubocop/cop/rspec/shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -34,19 +55,37 @@ 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?(ast_node)

checker = Checker.new(ast_node)
add_offense(checker.node, message: checker.message) do |corrector|
corrector.replace(checker.node, checker.preferred_style)
checker = new_checker(ast_node)
add_offense(ast_node, message: checker.message) do |corrector|
corrector.replace(ast_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 %<prefer>s over `%<current>s` ' \
'to titleize shared examples.'
'to symbolize shared examples.'

attr_reader :node

Expand All @@ -55,22 +94,31 @@ 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

# :nodoc:
class StringChecker
MSG = 'Prefer %<prefer>s over `%<current>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
Expand Down

0 comments on commit 842e690

Please sign in to comment.