Skip to content

Commit f2593cd

Browse files
authored
Merge pull request #1825 from rubocop/only-static-constants
Add configuration option `OnlyStaticConstants` to `RSpec/DescribedClass`
2 parents dd6ae03 + 350b50f commit f2593cd

File tree

5 files changed

+116
-39
lines changed

5 files changed

+116
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Master (Unreleased)
44

55
- Fix a false positive for `RSpec/RepeatedSubjectCall` when `subject.method_call`. ([@ydah])
6+
- Add configuration option `OnlyStaticConstants` to `RSpec/DescribedClass`. ([@ydah])
67

78
## 2.27.0 (2024-03-01)
89

config/default.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,10 @@ RSpec/DescribedClass:
283283
SupportedStyles:
284284
- described_class
285285
- explicit
286+
OnlyStaticConstants: true
286287
SafeAutoCorrect: false
287288
VersionAdded: '1.0'
288-
VersionChanged: '1.11'
289+
VersionChanged: "<<next>>"
289290
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DescribedClass
290291

291292
RSpec/DescribedClassModuleWrapping:

docs/modules/ROOT/pages/cops_rspec.adoc

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,16 +887,18 @@ end
887887
| Yes
888888
| Always (Unsafe)
889889
| 1.0
890-
| 1.11
890+
| <<next>>
891891
|===
892892
893893
Checks that tests use `described_class`.
894894
895895
If the first argument of describe is a class, the class is exposed to
896896
each example via described_class.
897897
898-
This cop can be configured using the `EnforcedStyle` and `SkipBlocks`
899-
options.
898+
This cop can be configured using the `EnforcedStyle`, `SkipBlocks`
899+
and `OnlyStaticConstants` options.
900+
`OnlyStaticConstants` is only relevant when `EnforcedStyle` is
901+
`described_class`.
900902
901903
There's a known caveat with rspec-rails's `controller` helper that
902904
runs its block in a different context, and `described_class` is not
@@ -924,6 +926,26 @@ describe MyClass do
924926
end
925927
----
926928
929+
==== `OnlyStaticConstants: true` (default)
930+
931+
[source,ruby]
932+
----
933+
# good
934+
describe MyClass do
935+
subject { MyClass::CONSTANT }
936+
end
937+
----
938+
939+
==== `OnlyStaticConstants: false`
940+
941+
[source,ruby]
942+
----
943+
# bad
944+
describe MyClass do
945+
subject { MyClass::CONSTANT }
946+
end
947+
----
948+
927949
==== `EnforcedStyle: explicit`
928950
929951
[source,ruby]
@@ -967,6 +989,10 @@ end
967989
| EnforcedStyle
968990
| `described_class`
969991
| `described_class`, `explicit`
992+
993+
| OnlyStaticConstants
994+
| `true`
995+
| Boolean
970996
|===
971997
972998
=== References

lib/rubocop/cop/rspec/described_class.rb

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ module RSpec
88
# If the first argument of describe is a class, the class is exposed to
99
# each example via described_class.
1010
#
11-
# This cop can be configured using the `EnforcedStyle` and `SkipBlocks`
12-
# options.
11+
# This cop can be configured using the `EnforcedStyle`, `SkipBlocks`
12+
# and `OnlyStaticConstants` options.
13+
# `OnlyStaticConstants` is only relevant when `EnforcedStyle` is
14+
# `described_class`.
1315
#
1416
# @example `EnforcedStyle: described_class` (default)
1517
# # bad
@@ -22,6 +24,18 @@ module RSpec
2224
# subject { described_class.do_something }
2325
# end
2426
#
27+
# @example `OnlyStaticConstants: true` (default)
28+
# # good
29+
# describe MyClass do
30+
# subject { MyClass::CONSTANT }
31+
# end
32+
#
33+
# @example `OnlyStaticConstants: false`
34+
# # bad
35+
# describe MyClass do
36+
# subject { MyClass::CONSTANT }
37+
# end
38+
#
2539
# @example `EnforcedStyle: explicit`
2640
# # bad
2741
# describe MyClass do
@@ -54,7 +68,7 @@ module RSpec
5468
# end
5569
# end
5670
#
57-
class DescribedClass < Base
71+
class DescribedClass < Base # rubocop:disable Metrics/ClassLength
5872
extend AutoCorrector
5973
include ConfigurableEnforcedStyle
6074
include Namespace
@@ -112,14 +126,17 @@ def autocorrect(corrector, match)
112126

113127
def find_usage(node, &block)
114128
yield(node) if offensive?(node)
115-
116-
return if scope_change?(node)
129+
return if scope_change?(node) || allowed?(node)
117130

118131
node.each_child_node do |child|
119132
find_usage(child, &block)
120133
end
121134
end
122135

136+
def allowed?(node)
137+
node.const_type? && only_static_constants?
138+
end
139+
123140
def message(offense)
124141
if style == :described_class
125142
format(MSG, replacement: DESCRIBED_CLASS, src: offense)
@@ -139,6 +156,10 @@ def skippable_block?(node)
139156
node.block_type? && !rspec_block?(node) && cop_config['SkipBlocks']
140157
end
141158

159+
def only_static_constants?
160+
cop_config.fetch('OnlyStaticConstants', true)
161+
end
162+
142163
def offensive?(node)
143164
if style == :described_class
144165
offensive_described_class?(node)

spec/rubocop/cop/rspec/described_class_spec.rb

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -148,36 +148,6 @@ module MyModule
148148
RUBY
149149
end
150150

151-
it 'flags class with constant' do
152-
expect_offense(<<~RUBY)
153-
describe MyClass do
154-
subject { MyClass::FOO }
155-
^^^^^^^ Use `described_class` instead of `MyClass`.
156-
end
157-
RUBY
158-
159-
expect_correction(<<~RUBY)
160-
describe MyClass do
161-
subject { described_class::FOO }
162-
end
163-
RUBY
164-
end
165-
166-
it 'flags class with subclasses' do
167-
expect_offense(<<~RUBY)
168-
describe MyClass do
169-
subject { MyClass::Subclass }
170-
^^^^^^^ Use `described_class` instead of `MyClass`.
171-
end
172-
RUBY
173-
174-
expect_correction(<<~RUBY)
175-
describe MyClass do
176-
subject { described_class::Subclass }
177-
end
178-
RUBY
179-
end
180-
181151
it 'ignores non-matching namespace defined on `describe` level' do
182152
expect_no_offenses(<<~RUBY)
183153
describe MyNamespace::MyClass do
@@ -310,6 +280,64 @@ module SomeGem
310280
end
311281
RUBY
312282
end
283+
284+
context 'when OnlyStaticConstants is `true`' do
285+
let(:cop_config) do
286+
{ 'EnforcedStyle' => :described_class, 'OnlyStaticConstants' => true }
287+
end
288+
289+
it 'ignores class with constant' do
290+
expect_no_offenses(<<~RUBY)
291+
describe MyClass do
292+
subject { MyClass::FOO }
293+
end
294+
RUBY
295+
end
296+
297+
it 'ignores class with subclasses' do
298+
expect_no_offenses(<<~RUBY)
299+
describe MyClass do
300+
subject { MyClass::Subclass }
301+
end
302+
RUBY
303+
end
304+
end
305+
306+
context 'when OnlyStaticConstants is `false`' do
307+
let(:cop_config) do
308+
{ 'EnforcedStyle' => :described_class, 'OnlyStaticConstants' => false }
309+
end
310+
311+
it 'flags class with constant' do
312+
expect_offense(<<~RUBY)
313+
describe MyClass do
314+
subject { MyClass::FOO }
315+
^^^^^^^ Use `described_class` instead of `MyClass`.
316+
end
317+
RUBY
318+
319+
expect_correction(<<~RUBY)
320+
describe MyClass do
321+
subject { described_class::FOO }
322+
end
323+
RUBY
324+
end
325+
326+
it 'flags class with subclasses' do
327+
expect_offense(<<~RUBY)
328+
describe MyClass do
329+
subject { MyClass::Subclass }
330+
^^^^^^^ Use `described_class` instead of `MyClass`.
331+
end
332+
RUBY
333+
334+
expect_correction(<<~RUBY)
335+
describe MyClass do
336+
subject { described_class::Subclass }
337+
end
338+
RUBY
339+
end
340+
end
313341
end
314342

315343
context 'when EnforcedStyle is :explicit' do

0 commit comments

Comments
 (0)