Skip to content

Commit

Permalink
Add new InternalAffairs/ExampleDescription cop.
Browse files Browse the repository at this point in the history
The cop checks for examples in rubocop's own RSpec suite where the description does not match the expectation (ie. `expects_offense` with "does not register an offense", etc.).
  • Loading branch information
dvandersluis authored and bbatsov committed Jan 27, 2021
1 parent e3ca0e3 commit c37b550
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,7 @@ Performance/CollectionLiteralInLoop:

RSpec/StubbedMock:
Enabled: false

InternalAffairs/ExampleDescription:
Include:
- 'spec/rubocop/cop/**/*.rb'
1 change: 1 addition & 0 deletions lib/rubocop/cop/internal_affairs.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
require_relative 'internal_affairs/example_description'
require_relative 'internal_affairs/method_name_equal'
require_relative 'internal_affairs/node_destructuring'
require_relative 'internal_affairs/node_type_predicate'
Expand Down
89 changes: 89 additions & 0 deletions lib/rubocop/cop/internal_affairs/example_description.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

module RuboCop
module Cop
module InternalAffairs
# Checks that RSpec examples that use `expects_offense`
# or `expects_no_offenses` do not have conflicting
# descriptions.
#
# @example
# # bad
# it 'does not register an offense' do
# expect_offense('...')
# end
#
# it 'registers an offense' do
# expect_no_offenses('...')
# end
#
# # good
# it 'registers an offense' do
# expect_offense('...')
# end
#
# it 'does not register an offense' do
# expect_no_offenses('...')
# end
class ExampleDescription < Base
class << self
attr_accessor :descriptions
end

MSG = 'Description does not match use of `%<method_name>s`.'

RESTRICT_ON_SEND = %i[
expect_offense
expect_no_offenses
expect_correction
expect_no_corrections
].to_set.freeze

EXPECT_NO_OFFENSES_INCORRECT_DESCRIPTIONS = [
/^(adds|registers|reports|finds) (an? )?offense/,
/^flags\b/
].freeze

EXPECT_OFFENSE_INCORRECT_DESCRIPTIONS = [
/^(does not|doesn't) (register|find|flag|report)/,
/^(does not|doesn't) add (a|an|any )?offense/
].freeze

EXPECT_NO_CORRECTIONS_INCORRECT_DESCRIPTIONS = [
/^(auto[- ]?)?correct/
].freeze

EXPECT_CORRECTION_INCORRECT_DESCRIPTIONS = [
/\b(does not|doesn't) (auto[- ]?)?correct/
].freeze

def_node_matcher :offense_example?, <<~PATTERN
(block
(send _ {:it :specify} $_description)
_args
`(send nil? %RESTRICT_ON_SEND ...)
)
PATTERN

def on_send(node)
parent = node.each_ancestor(:block).first
return unless parent && (description = offense_example?(parent))

method_name = node.method_name
message = format(MSG, method_name: method_name)

regexp_group = self.class.const_get("#{method_name}_incorrect_descriptions".upcase)
check_description(description, regexp_group, message)
end

private

def check_description(description, regexps, message)
return unless regexps.any? { |regexp| regexp.match?(description.value) }

add_offense(description, message: message)
end
end
end
end
end
124 changes: 124 additions & 0 deletions spec/rubocop/cop/internal_affairs/example_description_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::InternalAffairs::ExampleDescription, :config do
context 'with `expect_offense`' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'does not register an offense' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_offense`.
expect_offense('code')
end
RUBY
end

it 'does not register an offense when given a proper description' do
expect_no_offenses(<<~RUBY)
it 'finds an offense' do
expect_offense('code')
end
RUBY
end

it 'does not register an offense when given an unexpected description' do
expect_no_offenses(<<~RUBY)
it 'foo bar baz' do
expect_offense('code')
end
RUBY
end
end

context 'with `expect_no_offenses`' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'registers an offense' do
^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_no_offenses`.
expect_no_offenses('code')
end
RUBY
end

it 'does not register an offense when given a proper description' do
expect_no_offenses(<<~RUBY)
it 'does not flag' do
expect_no_offense('code')
end
RUBY
end

it 'does not register an offense when given an unexpected description' do
expect_no_offenses(<<~RUBY)
it 'foo bar baz' do
expect_offense('code')
end
RUBY
end
end

context 'with `expect_correction`' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'does not auto-correct' do
^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_correction`.
expect_correction('code', source: 'new code')
end
RUBY
end

context 'in conjunction with expect_offense' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'registers an offense but does not auto-correct' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_correction`.
expect_offense('code')
expect_correction('code')
end
RUBY
end

context 'when the description is invalid for both methods' do
it 'registers an offense for the first method encountered' do
expect_offense(<<~RUBY)
it 'does not register an offense and does not auto-correct' do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_offense`.
expect_offense('code')
expect_correction('code')
end
RUBY
end
end
end
end

context 'with `expect_no_corrections`' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'autocorrects' do
^^^^^^^^^^^^^^ Description does not match use of `expect_no_corrections`.
expect_no_corrections
end
RUBY
end

context 'in conjunction with expect_offense' do
it 'registers an offense when given an improper description' do
expect_offense(<<~RUBY)
it 'auto-corrects' do
^^^^^^^^^^^^^^^ Description does not match use of `expect_no_corrections`.
expect_offense('code')
expect_no_corrections
end
RUBY
end
end
end

context 'when not making an expectation on offenses' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
it 'registers an offense' do
end
RUBY
end
end
end

0 comments on commit c37b550

Please sign in to comment.