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 7fa2a1c
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 13 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
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 7fa2a1c

Please sign in to comment.