Skip to content

Commit

Permalink
Add Suffixes configuration option to RSpec/ContextWording
Browse files Browse the repository at this point in the history
Resolve: #1356
  • Loading branch information
ydah committed Aug 5, 2022
1 parent 1a97bcd commit 25ed74f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Fix a false positive for `RSpec/Capybara/SpecificMatcher`. ([@ydah][])
* Fix a false negative for `RSpec/Capybara/SpecificMatcher` for `have_field`. ([@ydah][])
* Fix a false positive for `RSpec/Capybara/SpecificMatcher` when may not have a `href` by `have_link`. ([@ydah][])
* Add `Suffixes` configuration option to `RSpec/ContextWording`. ([@ydah][])

## 2.12.1 (2022-07-03)

Expand Down
3 changes: 2 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,9 @@ RSpec/ContextWording:
- when
- with
- without
Suffixes: []
VersionAdded: '1.20'
VersionChanged: 1.20.1
VersionChanged: '2.13'
StyleGuide: https://rspec.rubystyle.guide/#context-descriptions
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContextWording

Expand Down
40 changes: 39 additions & 1 deletion docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ end
| Yes
| No
| 1.20
| 1.20.1
| 2.13
|===

Checks that `context` docstring starts with an allowed prefix.
Expand All @@ -494,6 +494,12 @@ the configuration to meet project needs. Other acceptable prefixes may
include `if`, `unless`, `for`, `before`, `after`, or `during`.
They may consist of multiple words if desired.

The default list of suffixes is empty. Users are encouraged to tailor
the configuration to meet project needs. In many cases, this setting
is not necessary, but it is provided for localization of languages
where suffixes are important.
They may consist of multiple words if desired.

=== Examples

==== `Prefixes` configuration
Expand Down Expand Up @@ -524,6 +530,34 @@ context 'when the display name is not present' do
end
----

==== `Suffixes` configuration

[source,ruby]
----
# .rubocop.yml
# RSpec/ContextWording:
# Suffixes:
# - success
----

[source,ruby]
----
# bad
context 'when this test is failed' do
# ...
end
# good
context 'when this test is success' do
# ...
end
# good - behavior different from prefix
context 'when this test is unsuccess' do
# ...
end
----

=== Configurable attributes

|===
Expand All @@ -532,6 +566,10 @@ end
| Prefixes
| `when`, `with`, `without`
| Array

| Suffixes
| `[]`
| Array
|===

=== References
Expand Down
73 changes: 62 additions & 11 deletions lib/rubocop/cop/rspec/context_wording.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,92 @@ module RSpec
# context 'when the display name is not present' do
# # ...
# end
#
# The default list of suffixes is empty. Users are encouraged to tailor
# the configuration to meet project needs. In many cases, this setting
# is not necessary, but it is provided for localization of languages
# where suffixes are important.
# They may consist of multiple words if desired.
#
# @example `Suffixes` configuration
#
# # .rubocop.yml
# # RSpec/ContextWording:
# # Suffixes:
# # - success
#
# @example
# # bad
# context 'when this test is failed' do
# # ...
# end
#
# # good
# context 'when this test is success' do
# # ...
# end
#
# # good - behavior different from prefix
# context 'when this test is unsuccess' do
# # ...
# end
#
class ContextWording < Base
MSG = 'Start context description with %<prefixes>s.'
PREFIX_MSG = 'Start context description with %<prefixes>s.'
SUFFIX_MSG = 'End context description with %<suffixes>s.'

# @!method context_wording(node)
def_node_matcher :context_wording, <<-PATTERN
(block (send #rspec? { :context :shared_context } $(str #bad_prefix?) ...) ...)
(block (send #rspec? { :context :shared_context } $(str $_) ...) ...)
PATTERN

def on_block(node)
context_wording(node) do |context|
add_offense(context,
message: format(MSG, prefixes: joined_prefixes))
context_wording(node) do |context, description|
offense_prefix(context) if bad_prefix?(description)
offense_suffix(context) if bad_suffix?(description)
end
end

private

def offense_prefix(context)
add_offense(context,
message: format(PREFIX_MSG,
prefixes: expect_word(prefixes)))
end

def offense_suffix(context)
add_offense(context,
message: format(SUFFIX_MSG,
suffixes: expect_word(suffixes)))
end

def bad_prefix?(description)
!prefix_regex.match?(description)
return false if prefixes.empty?

!/^#{Regexp.union(prefixes)}\b/.match?(description)
end

def bad_suffix?(description)
return false if suffixes.empty?

!/#{Regexp.union(suffixes)}$/.match?(description)
end

def joined_prefixes
quoted = prefixes.map { |prefix| "'#{prefix}'" }
def expect_word(config_values)
quoted = config_values.map { |value| "'#{value}'" }
return quoted.first if quoted.size == 1

quoted << "or #{quoted.pop}"
quoted.join(', ')
end

def prefixes
cop_config['Prefixes'] || []
cop_config['Prefixes']
end

def prefix_regex
/^#{Regexp.union(prefixes)}\b/
def suffixes
cop_config['Suffixes']
end
end
end
Expand Down
38 changes: 37 additions & 1 deletion spec/rubocop/cop/rspec/context_wording_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::ContextWording do
let(:cop_config) { { 'Prefixes' => %w[when with] } }
let(:cop_config) { { 'Prefixes' => %w[when with], 'Suffixes' => %w[] } }

it 'skips describe blocks' do
expect_no_offenses(<<-RUBY)
Expand Down Expand Up @@ -125,5 +125,41 @@
RUBY
end
end

context "when `Suffixes: ['success', 'failed']`" do
let(:cop_config) do
{ 'Prefixes' => %w[when with], 'Suffixes' => %w[success failed] }
end

it 'finds context without `success` at the ending' do
expect_offense(<<-RUBY)
context 'when the display name not present' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End context description with 'success', or 'failed'.
end
RUBY
end

it 'finds shared_context without `success` at the ending' do
expect_offense(<<-RUBY)
shared_context 'when the display name not present' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End context description with 'success', or 'failed'.
end
RUBY
end

it "skips descriptions ending with 'success'" do
expect_no_offenses(<<-RUBY)
context 'when the display name is not present success' do
end
RUBY
end

it "skips descriptions ending with 'unsuccess'" do
expect_no_offenses(<<-RUBY)
context 'when the display name is not unsuccess' do
end
RUBY
end
end
end
end

0 comments on commit 25ed74f

Please sign in to comment.