Skip to content

Commit c37b550

Browse files
dvandersluisbbatsov
authored andcommitted
Add new InternalAffairs/ExampleDescription cop.
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.).
1 parent e3ca0e3 commit c37b550

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,7 @@ Performance/CollectionLiteralInLoop:
130130

131131
RSpec/StubbedMock:
132132
Enabled: false
133+
134+
InternalAffairs/ExampleDescription:
135+
Include:
136+
- 'spec/rubocop/cop/**/*.rb'

lib/rubocop/cop/internal_affairs.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
4+
require_relative 'internal_affairs/example_description'
45
require_relative 'internal_affairs/method_name_equal'
56
require_relative 'internal_affairs/node_destructuring'
67
require_relative 'internal_affairs/node_type_predicate'
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module InternalAffairs
6+
# Checks that RSpec examples that use `expects_offense`
7+
# or `expects_no_offenses` do not have conflicting
8+
# descriptions.
9+
#
10+
# @example
11+
# # bad
12+
# it 'does not register an offense' do
13+
# expect_offense('...')
14+
# end
15+
#
16+
# it 'registers an offense' do
17+
# expect_no_offenses('...')
18+
# end
19+
#
20+
# # good
21+
# it 'registers an offense' do
22+
# expect_offense('...')
23+
# end
24+
#
25+
# it 'does not register an offense' do
26+
# expect_no_offenses('...')
27+
# end
28+
class ExampleDescription < Base
29+
class << self
30+
attr_accessor :descriptions
31+
end
32+
33+
MSG = 'Description does not match use of `%<method_name>s`.'
34+
35+
RESTRICT_ON_SEND = %i[
36+
expect_offense
37+
expect_no_offenses
38+
expect_correction
39+
expect_no_corrections
40+
].to_set.freeze
41+
42+
EXPECT_NO_OFFENSES_INCORRECT_DESCRIPTIONS = [
43+
/^(adds|registers|reports|finds) (an? )?offense/,
44+
/^flags\b/
45+
].freeze
46+
47+
EXPECT_OFFENSE_INCORRECT_DESCRIPTIONS = [
48+
/^(does not|doesn't) (register|find|flag|report)/,
49+
/^(does not|doesn't) add (a|an|any )?offense/
50+
].freeze
51+
52+
EXPECT_NO_CORRECTIONS_INCORRECT_DESCRIPTIONS = [
53+
/^(auto[- ]?)?correct/
54+
].freeze
55+
56+
EXPECT_CORRECTION_INCORRECT_DESCRIPTIONS = [
57+
/\b(does not|doesn't) (auto[- ]?)?correct/
58+
].freeze
59+
60+
def_node_matcher :offense_example?, <<~PATTERN
61+
(block
62+
(send _ {:it :specify} $_description)
63+
_args
64+
`(send nil? %RESTRICT_ON_SEND ...)
65+
)
66+
PATTERN
67+
68+
def on_send(node)
69+
parent = node.each_ancestor(:block).first
70+
return unless parent && (description = offense_example?(parent))
71+
72+
method_name = node.method_name
73+
message = format(MSG, method_name: method_name)
74+
75+
regexp_group = self.class.const_get("#{method_name}_incorrect_descriptions".upcase)
76+
check_description(description, regexp_group, message)
77+
end
78+
79+
private
80+
81+
def check_description(description, regexps, message)
82+
return unless regexps.any? { |regexp| regexp.match?(description.value) }
83+
84+
add_offense(description, message: message)
85+
end
86+
end
87+
end
88+
end
89+
end
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::InternalAffairs::ExampleDescription, :config do
4+
context 'with `expect_offense`' do
5+
it 'registers an offense when given an improper description' do
6+
expect_offense(<<~RUBY)
7+
it 'does not register an offense' do
8+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_offense`.
9+
expect_offense('code')
10+
end
11+
RUBY
12+
end
13+
14+
it 'does not register an offense when given a proper description' do
15+
expect_no_offenses(<<~RUBY)
16+
it 'finds an offense' do
17+
expect_offense('code')
18+
end
19+
RUBY
20+
end
21+
22+
it 'does not register an offense when given an unexpected description' do
23+
expect_no_offenses(<<~RUBY)
24+
it 'foo bar baz' do
25+
expect_offense('code')
26+
end
27+
RUBY
28+
end
29+
end
30+
31+
context 'with `expect_no_offenses`' do
32+
it 'registers an offense when given an improper description' do
33+
expect_offense(<<~RUBY)
34+
it 'registers an offense' do
35+
^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_no_offenses`.
36+
expect_no_offenses('code')
37+
end
38+
RUBY
39+
end
40+
41+
it 'does not register an offense when given a proper description' do
42+
expect_no_offenses(<<~RUBY)
43+
it 'does not flag' do
44+
expect_no_offense('code')
45+
end
46+
RUBY
47+
end
48+
49+
it 'does not register an offense when given an unexpected description' do
50+
expect_no_offenses(<<~RUBY)
51+
it 'foo bar baz' do
52+
expect_offense('code')
53+
end
54+
RUBY
55+
end
56+
end
57+
58+
context 'with `expect_correction`' do
59+
it 'registers an offense when given an improper description' do
60+
expect_offense(<<~RUBY)
61+
it 'does not auto-correct' do
62+
^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_correction`.
63+
expect_correction('code', source: 'new code')
64+
end
65+
RUBY
66+
end
67+
68+
context 'in conjunction with expect_offense' do
69+
it 'registers an offense when given an improper description' do
70+
expect_offense(<<~RUBY)
71+
it 'registers an offense but does not auto-correct' do
72+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_correction`.
73+
expect_offense('code')
74+
expect_correction('code')
75+
end
76+
RUBY
77+
end
78+
79+
context 'when the description is invalid for both methods' do
80+
it 'registers an offense for the first method encountered' do
81+
expect_offense(<<~RUBY)
82+
it 'does not register an offense and does not auto-correct' do
83+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Description does not match use of `expect_offense`.
84+
expect_offense('code')
85+
expect_correction('code')
86+
end
87+
RUBY
88+
end
89+
end
90+
end
91+
end
92+
93+
context 'with `expect_no_corrections`' do
94+
it 'registers an offense when given an improper description' do
95+
expect_offense(<<~RUBY)
96+
it 'autocorrects' do
97+
^^^^^^^^^^^^^^ Description does not match use of `expect_no_corrections`.
98+
expect_no_corrections
99+
end
100+
RUBY
101+
end
102+
103+
context 'in conjunction with expect_offense' do
104+
it 'registers an offense when given an improper description' do
105+
expect_offense(<<~RUBY)
106+
it 'auto-corrects' do
107+
^^^^^^^^^^^^^^^ Description does not match use of `expect_no_corrections`.
108+
expect_offense('code')
109+
expect_no_corrections
110+
end
111+
RUBY
112+
end
113+
end
114+
end
115+
116+
context 'when not making an expectation on offenses' do
117+
it 'does not register an offense' do
118+
expect_no_offenses(<<~RUBY)
119+
it 'registers an offense' do
120+
end
121+
RUBY
122+
end
123+
end
124+
end

0 commit comments

Comments
 (0)