rubocop-rspec 2.22.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +1 -1
- data/config/default.yml +37 -16
- data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -0
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +11 -4
- data/lib/rubocop/cop/rspec/expect_actual.rb +2 -2
- data/lib/rubocop/cop/rspec/focus.rb +13 -0
- data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +4 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/pending.rb +12 -2
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
- data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +92 -0
- data/lib/rubocop/cop/rspec/receive_messages.rb +155 -0
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +1 -1
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +2 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0eebbfb772c1598b0ee4cfe88c74d8b81171e197dbd4d5dc6020df95005440e
|
4
|
+
data.tar.gz: 2936be502e0062b94a0346bd98696194fd33fe6f7693f8c92bc38a952ee31b26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14e3e53093b7e104ab212217fdc8226a909fba9bdd84fa44c22963cf8e62d17ce858a2180b8b5b3727b5dd2a74a9b4c6e2139d8d61cbc6595b2c46f9022214af
|
7
|
+
data.tar.gz: 9408c50d35ae50e1825fef43f2c6a6def0d87e44bfbbd968bb19996b1a867f215df36ab62d451af892e6855c4330c0d4fb9d09b8400ff5f35b4ecef1cfb86adb
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
## Master (Unreleased)
|
4
4
|
|
5
|
+
## 2.23.0 (2023-07-30)
|
6
|
+
|
7
|
+
- Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah])
|
8
|
+
- Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah])
|
9
|
+
- Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah])
|
10
|
+
- Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand])
|
11
|
+
- Fix a false negative for `RSpec/Pending` when `it` without body. ([@ydah])
|
12
|
+
- Add new `RSpec/ReceiveMessages` cop. ([@ydah])
|
13
|
+
- Change default.yml path to use `**/spec/*` instead of `spec/*`. ([@ydah])
|
14
|
+
- Add `AllowedIdentifiers` and `AllowedPatterns` configuration option to `RSpec/IndexedLet`. ([@ydah])
|
15
|
+
- Fix `RSpec/NamedSubject` when block has no body. ([@splattael])
|
16
|
+
- Fix `RSpec/LetBeforeExamples` autocorrect incompatible with `RSpec/ScatteredLet` autocorrect. ([@ydah])
|
17
|
+
- Update `RSpec/Focus` to support `shared_context` and `shared_examples` ([@tmaier])
|
18
|
+
|
5
19
|
## 2.22.0 (2023-05-06)
|
6
20
|
|
7
21
|
- Extract factory_bot cops to a separate repository, [`rubocop-factory_bot`](https://github.com/rubocop/rubocop-factory_bot). The `rubocop-factory_bot` repository is a dependency of `rubocop-rspec` and the factory_bot cops are aliased (`RSpec/FactoryBot/Foo` == `FactoryBot/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah])
|
@@ -781,6 +795,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
781
795
|
[@cfabianski]: https://github.com/cfabianski
|
782
796
|
[@clupprich]: https://github.com/clupprich
|
783
797
|
[@composerinteralia]: https://github.com/composerinteralia
|
798
|
+
[@corydiamand]: https://github.com/corydiamand
|
784
799
|
[@darhazer]: https://github.com/Darhazer
|
785
800
|
[@daveworth]: https://github.com/daveworth
|
786
801
|
[@dduugg]: https://github.com/dduugg
|
@@ -859,6 +874,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
859
874
|
[@seanpdoyle]: https://github.com/seanpdoyle
|
860
875
|
[@sl4vr]: https://github.com/sl4vr
|
861
876
|
[@smcgivern]: https://github.com/smcgivern
|
877
|
+
[@splattael]: https://github.com/splattael
|
862
878
|
[@stephannv]: https://github.com/stephannv
|
863
879
|
[@t3h2mas]: https://github.com/t3h2mas
|
864
880
|
[@tdeo]: https://github.com/tdeo
|
@@ -866,6 +882,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
866
882
|
[@telmofcosta]: https://github.com/telmofcosta
|
867
883
|
[@tietew]: https://github.com/Tietew
|
868
884
|
[@timrogers]: https://github.com/timrogers
|
885
|
+
[@tmaier]: https://github.com/tmaier
|
869
886
|
[@topalovic]: https://github.com/topalovic
|
870
887
|
[@twalpole]: https://github.com/twalpole
|
871
888
|
[@vzvu3k6k]: https://github.com/vzvu3k6k
|
data/README.md
CHANGED
data/config/default.yml
CHANGED
@@ -181,10 +181,11 @@ RSpec/BeforeAfterAll:
|
|
181
181
|
Description: Check that before/after(:all) isn't being used.
|
182
182
|
Enabled: true
|
183
183
|
Exclude:
|
184
|
-
- spec/spec_helper.rb
|
185
|
-
- spec/rails_helper.rb
|
186
|
-
- spec/support/**/*.rb
|
184
|
+
- "**/spec/spec_helper.rb"
|
185
|
+
- "**/spec/rails_helper.rb"
|
186
|
+
- "**/spec/support/**/*.rb"
|
187
187
|
VersionAdded: '1.12'
|
188
|
+
VersionChanged: '2.23'
|
188
189
|
StyleGuide: https://rspec.rubystyle.guide/#avoid-hooks-with-context-scope
|
189
190
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeforeAfterAll
|
190
191
|
|
@@ -404,8 +405,9 @@ RSpec/ExpectActual:
|
|
404
405
|
Description: Checks for `expect(...)` calls containing literal values.
|
405
406
|
Enabled: true
|
406
407
|
Exclude:
|
407
|
-
- spec/routing/**/*
|
408
|
+
- "**/spec/routing/**/*"
|
408
409
|
VersionAdded: '1.7'
|
410
|
+
VersionChanged: '2.23'
|
409
411
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectActual
|
410
412
|
|
411
413
|
RSpec/ExpectChange:
|
@@ -513,8 +515,11 @@ RSpec/IndexedLet:
|
|
513
515
|
Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`).
|
514
516
|
Enabled: pending
|
515
517
|
VersionAdded: '2.20'
|
518
|
+
VersionChanged: '2.23'
|
516
519
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IndexedLet
|
517
520
|
Max: 1
|
521
|
+
AllowedIdentifiers: []
|
522
|
+
AllowedPatterns: []
|
518
523
|
|
519
524
|
RSpec/InstanceSpy:
|
520
525
|
Description: Checks for `instance_double` used with `have_received`.
|
@@ -725,6 +730,12 @@ RSpec/ReceiveCounts:
|
|
725
730
|
VersionAdded: '1.26'
|
726
731
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts
|
727
732
|
|
733
|
+
RSpec/ReceiveMessages:
|
734
|
+
Description: Checks for multiple messages stubbed on the same object.
|
735
|
+
Enabled: pending
|
736
|
+
VersionAdded: '2.23'
|
737
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveMessages
|
738
|
+
|
728
739
|
RSpec/ReceiveNever:
|
729
740
|
Description: Prefer `not_to receive(...)` over `receive(...).never`.
|
730
741
|
Enabled: true
|
@@ -974,11 +985,11 @@ RSpec/FactoryBot/AttributeDefinedStatically:
|
|
974
985
|
Description: Always declare attribute values as blocks.
|
975
986
|
Enabled: true
|
976
987
|
Include:
|
977
|
-
- spec/factories.rb
|
978
|
-
- spec/factories/**/*.rb
|
979
|
-
- features/support/factories/**/*.rb
|
988
|
+
- "**/spec/factories.rb"
|
989
|
+
- "**/spec/factories/**/*.rb"
|
990
|
+
- "**/features/support/factories/**/*.rb"
|
980
991
|
VersionAdded: '1.28'
|
981
|
-
VersionChanged: '2.
|
992
|
+
VersionChanged: '2.23'
|
982
993
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
|
983
994
|
|
984
995
|
RSpec/FactoryBot/ConsistentParenthesesStyle:
|
@@ -997,26 +1008,26 @@ RSpec/FactoryBot/CreateList:
|
|
997
1008
|
Include:
|
998
1009
|
- "**/*_spec.rb"
|
999
1010
|
- "**/spec/**/*"
|
1000
|
-
- spec/factories.rb
|
1001
|
-
- spec/factories/**/*.rb
|
1002
|
-
- features/support/factories/**/*.rb
|
1011
|
+
- "**/spec/factories.rb"
|
1012
|
+
- "**/spec/factories/**/*.rb"
|
1013
|
+
- "**/features/support/factories/**/*.rb"
|
1003
1014
|
EnforcedStyle: create_list
|
1004
1015
|
SupportedStyles:
|
1005
1016
|
- create_list
|
1006
1017
|
- n_times
|
1007
1018
|
VersionAdded: '1.25'
|
1008
|
-
VersionChanged: '2.
|
1019
|
+
VersionChanged: '2.23'
|
1009
1020
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/CreateList
|
1010
1021
|
|
1011
1022
|
RSpec/FactoryBot/FactoryClassName:
|
1012
1023
|
Description: Use string value when setting the class attribute explicitly.
|
1013
1024
|
Enabled: true
|
1014
1025
|
Include:
|
1015
|
-
- spec/factories.rb
|
1016
|
-
- spec/factories/**/*.rb
|
1017
|
-
- features/support/factories/**/*.rb
|
1026
|
+
- "**/spec/factories.rb"
|
1027
|
+
- "**/spec/factories/**/*.rb"
|
1028
|
+
- "**/features/support/factories/**/*.rb"
|
1018
1029
|
VersionAdded: '1.37'
|
1019
|
-
VersionChanged: '2.
|
1030
|
+
VersionChanged: '2.23'
|
1020
1031
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/FactoryClassName
|
1021
1032
|
|
1022
1033
|
RSpec/FactoryBot/FactoryNameStyle:
|
@@ -1095,6 +1106,16 @@ RSpec/Rails/MinitestAssertions:
|
|
1095
1106
|
VersionAdded: '2.17'
|
1096
1107
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions
|
1097
1108
|
|
1109
|
+
RSpec/Rails/NegationBeValid:
|
1110
|
+
Description: Enforces use of `be_invalid` or `not_to` for negated be_valid.
|
1111
|
+
EnforcedStyle: not_to
|
1112
|
+
SupportedStyles:
|
1113
|
+
- not_to
|
1114
|
+
- be_invalid
|
1115
|
+
Enabled: pending
|
1116
|
+
VersionAdded: '2.23'
|
1117
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/NegationBeValid
|
1118
|
+
|
1098
1119
|
RSpec/Rails/TravelAround:
|
1099
1120
|
Description: Prefer to travel in `before` rather than `around`.
|
1100
1121
|
Enabled: pending
|
@@ -131,6 +131,7 @@ module RuboCop
|
|
131
131
|
{
|
132
132
|
#examples_directly_or_in_block?
|
133
133
|
(begin <#examples_directly_or_in_block? ...>)
|
134
|
+
(begin <#examples_in_branches? ...>)
|
134
135
|
}
|
135
136
|
PATTERN
|
136
137
|
|
@@ -169,6 +170,8 @@ module RuboCop
|
|
169
170
|
end
|
170
171
|
|
171
172
|
def examples_in_branches?(condition_node)
|
173
|
+
return if !condition_node.if_type? && !condition_node.case_type?
|
174
|
+
|
172
175
|
condition_node.branches.any? { |branch| examples?(branch) }
|
173
176
|
end
|
174
177
|
|
@@ -52,14 +52,21 @@ module RuboCop
|
|
52
52
|
|
53
53
|
# @param text [String]
|
54
54
|
def excessive_whitespace?(text)
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
text.match?(/
|
56
|
+
# Leading space
|
57
|
+
\A[[:blank:]]
|
58
|
+
|
|
59
|
+
# Trailing space
|
60
|
+
[[:blank:]]\z
|
61
|
+
|
|
62
|
+
# Two or more consecutive spaces, except if they are leading spaces
|
63
|
+
[^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]]
|
64
|
+
/x)
|
58
65
|
end
|
59
66
|
|
60
67
|
# @param text [String]
|
61
68
|
def strip_excessive_whitespace(text)
|
62
|
-
text.strip.gsub(/
|
69
|
+
text.strip.gsub(/[[:blank:]]{2,}/, ' ')
|
63
70
|
end
|
64
71
|
|
65
72
|
# @param node [RuboCop::AST::Node]
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
24
24
|
class ExpectActual < Base
|
25
25
|
extend AutoCorrector
|
26
26
|
|
27
|
-
MSG = 'Provide the actual you are testing to `expect(...)`.'
|
27
|
+
MSG = 'Provide the actual value you are testing to `expect(...)`.'
|
28
28
|
|
29
29
|
RESTRICT_ON_SEND = Runners.all
|
30
30
|
|
@@ -77,7 +77,7 @@ module RuboCop
|
|
77
77
|
|
78
78
|
private
|
79
79
|
|
80
|
-
# This is not
|
80
|
+
# This is not implemented using a NodePattern because it seems
|
81
81
|
# to not be able to match against an explicit (nil) sexp
|
82
82
|
def literal?(node)
|
83
83
|
node && (simple_literal?(node) || complex_literal?(node))
|
@@ -34,6 +34,18 @@ module RuboCop
|
|
34
34
|
# # good
|
35
35
|
# describe 'test' do; end
|
36
36
|
#
|
37
|
+
# # bad
|
38
|
+
# shared_examples 'test', focus: true do; end
|
39
|
+
#
|
40
|
+
# # good
|
41
|
+
# shared_examples 'test' do; end
|
42
|
+
#
|
43
|
+
# # bad
|
44
|
+
# shared_context 'test', focus: true do; end
|
45
|
+
#
|
46
|
+
# # good
|
47
|
+
# shared_context 'test' do; end
|
48
|
+
#
|
37
49
|
# # bad (does not support autocorrection)
|
38
50
|
# focus 'test' do; end
|
39
51
|
#
|
@@ -51,6 +63,7 @@ module RuboCop
|
|
51
63
|
#Examples.regular
|
52
64
|
#Examples.skipped
|
53
65
|
#Examples.pending
|
66
|
+
#SharedGroups.all
|
54
67
|
}
|
55
68
|
PATTERN
|
56
69
|
|
@@ -8,6 +8,9 @@ module RuboCop
|
|
8
8
|
# It makes reading the test harder because it's not clear what exactly
|
9
9
|
# is tested by this particular example.
|
10
10
|
#
|
11
|
+
# The configurable options `AllowedIdentifiers` and `AllowedPatterns`
|
12
|
+
# will also read those set in `Naming/VariableNumber`.
|
13
|
+
#
|
11
14
|
# @example `Max: 1 (default)`
|
12
15
|
# # bad
|
13
16
|
# let(:item_1) { create(:item) }
|
@@ -31,7 +34,20 @@ module RuboCop
|
|
31
34
|
# let(:item_1) { create(:item) }
|
32
35
|
# let(:item_2) { create(:item) }
|
33
36
|
#
|
37
|
+
# @example `AllowedIdentifiers: ['item_1', 'item_2']`
|
38
|
+
# # good
|
39
|
+
# let(:item_1) { create(:item) }
|
40
|
+
# let(:item_2) { create(:item) }
|
41
|
+
#
|
42
|
+
# @example `AllowedPatterns: ['item']`
|
43
|
+
# # good
|
44
|
+
# let(:item_1) { create(:item) }
|
45
|
+
# let(:item_2) { create(:item) }
|
46
|
+
#
|
34
47
|
class IndexedLet < Base
|
48
|
+
include AllowedIdentifiers
|
49
|
+
include AllowedPattern
|
50
|
+
|
35
51
|
MSG = 'This `let` statement uses index in its name. Please give it ' \
|
36
52
|
'a meaningful name.'
|
37
53
|
|
@@ -69,12 +85,27 @@ module RuboCop
|
|
69
85
|
end
|
70
86
|
|
71
87
|
def indexed_let?(node)
|
72
|
-
let?(node) &&
|
88
|
+
let?(node) &&
|
89
|
+
SUFFIX_INDEX_REGEX.match?(let_name(node)) &&
|
90
|
+
!allowed_identifier?(let_name(node).to_s) &&
|
91
|
+
!matches_allowed_pattern?(let_name(node).to_s)
|
73
92
|
end
|
74
93
|
|
75
94
|
def let_name_stripped_index(node)
|
76
95
|
let_name(node).to_s.gsub(INDEX_REGEX, '')
|
77
96
|
end
|
97
|
+
|
98
|
+
def cop_config_patterns_values
|
99
|
+
Array(config.for_cop('Naming/VariableNumber')
|
100
|
+
.fetch('AllowedPatterns', [])) +
|
101
|
+
Array(cop_config.fetch('AllowedPatterns', []))
|
102
|
+
end
|
103
|
+
|
104
|
+
def allowed_identifiers
|
105
|
+
Array(config.for_cop('Naming/VariableNumber')
|
106
|
+
.fetch('AllowedIdentifiers', [])) +
|
107
|
+
Array(cop_config.fetch('AllowedIdentifiers', []))
|
108
|
+
end
|
78
109
|
end
|
79
110
|
end
|
80
111
|
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
class InstanceVariable < Base
|
49
49
|
include TopLevelGroup
|
50
50
|
|
51
|
-
MSG = 'Avoid instance variables
|
51
|
+
MSG = 'Avoid instance variables - use let, ' \
|
52
52
|
'a method call, or a local variable (if possible).'
|
53
53
|
|
54
54
|
# @!method dynamic_class?(node)
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
17
17
|
# Anonymous classes are fine, since they don't result in global
|
18
18
|
# namespace name clashes.
|
19
19
|
#
|
20
|
-
# @see https://
|
20
|
+
# @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants
|
21
21
|
#
|
22
22
|
# @example Constants leak between examples
|
23
23
|
# # bad
|
@@ -41,10 +41,15 @@ module RuboCop
|
|
41
41
|
def_node_matcher :skippable?, <<~PATTERN
|
42
42
|
{
|
43
43
|
(send #rspec? #ExampleGroups.regular ...)
|
44
|
-
|
44
|
+
#skippable_example?
|
45
45
|
}
|
46
46
|
PATTERN
|
47
47
|
|
48
|
+
# @!method skippable_example?(node)
|
49
|
+
def_node_matcher :skippable_example?, <<~PATTERN
|
50
|
+
(send nil? #Examples.regular ...)
|
51
|
+
PATTERN
|
52
|
+
|
48
53
|
# @!method pending_block?(node)
|
49
54
|
def_node_matcher :pending_block?, <<~PATTERN
|
50
55
|
{
|
@@ -62,7 +67,12 @@ module RuboCop
|
|
62
67
|
private
|
63
68
|
|
64
69
|
def skipped?(node)
|
65
|
-
skippable?(node) && skipped_in_metadata?(node)
|
70
|
+
skippable?(node) && skipped_in_metadata?(node) ||
|
71
|
+
skipped_regular_example_without_body?(node)
|
72
|
+
end
|
73
|
+
|
74
|
+
def skipped_regular_example_without_body?(node)
|
75
|
+
skippable_example?(node) && !node.block_node
|
66
76
|
end
|
67
77
|
end
|
68
78
|
end
|
@@ -74,7 +74,7 @@ module RuboCop
|
|
74
74
|
name[0..-2]
|
75
75
|
when 'exist?', 'exists?'
|
76
76
|
'exist'
|
77
|
-
when
|
77
|
+
when /\Ahas_/
|
78
78
|
name.sub('has_', 'have_')[0..-2]
|
79
79
|
else
|
80
80
|
"be_#{name[0..-2]}"
|
@@ -240,10 +240,10 @@ module RuboCop
|
|
240
240
|
'include?'
|
241
241
|
when 'respond_to'
|
242
242
|
'respond_to?'
|
243
|
-
when
|
243
|
+
when /\Ahave_(.+)/
|
244
244
|
"has_#{Regexp.last_match(1)}?"
|
245
245
|
else
|
246
|
-
"#{matcher[
|
246
|
+
"#{matcher[/\Abe_(.+)/, 1]}?"
|
247
247
|
end
|
248
248
|
end
|
249
249
|
# rubocop:enable Metrics/MethodLength
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Rails
|
7
|
+
# Enforces use of `be_invalid` or `not_to` for negated be_valid.
|
8
|
+
#
|
9
|
+
# @example EnforcedStyle: not_to (default)
|
10
|
+
# # bad
|
11
|
+
# expect(foo).to be_invalid
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# expect(foo).not_to be_valid
|
15
|
+
#
|
16
|
+
# @example EnforcedStyle: be_invalid
|
17
|
+
# # bad
|
18
|
+
# expect(foo).not_to be_valid
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# expect(foo).to be_invalid
|
22
|
+
#
|
23
|
+
class NegationBeValid < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
include ConfigurableEnforcedStyle
|
26
|
+
|
27
|
+
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
|
28
|
+
RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
|
29
|
+
|
30
|
+
# @!method not_to?(node)
|
31
|
+
def_node_matcher :not_to?, <<~PATTERN
|
32
|
+
(send ... :not_to (send nil? :be_valid ...))
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
# @!method be_invalid?(node)
|
36
|
+
def_node_matcher :be_invalid?, <<~PATTERN
|
37
|
+
(send ... :to (send nil? :be_invalid ...))
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
return unless offense?(node.parent)
|
42
|
+
|
43
|
+
add_offense(offense_range(node),
|
44
|
+
message: message(node.method_name)) do |corrector|
|
45
|
+
corrector.replace(node.parent.loc.selector, replaced_runner)
|
46
|
+
corrector.replace(node.loc.selector, replaced_matcher)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def offense?(node)
|
53
|
+
case style
|
54
|
+
when :not_to
|
55
|
+
be_invalid?(node)
|
56
|
+
when :be_invalid
|
57
|
+
not_to?(node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def offense_range(node)
|
62
|
+
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
|
63
|
+
end
|
64
|
+
|
65
|
+
def message(_matcher)
|
66
|
+
format(MSG,
|
67
|
+
runner: replaced_runner,
|
68
|
+
matcher: replaced_matcher)
|
69
|
+
end
|
70
|
+
|
71
|
+
def replaced_runner
|
72
|
+
case style
|
73
|
+
when :not_to
|
74
|
+
'not_to'
|
75
|
+
when :be_invalid
|
76
|
+
'to'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def replaced_matcher
|
81
|
+
case style
|
82
|
+
when :not_to
|
83
|
+
'be_valid'
|
84
|
+
when :be_invalid
|
85
|
+
'be_invalid'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for multiple messages stubbed on the same object.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# before do
|
11
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
12
|
+
# allow(Service).to receive(:baz).and_return(qux)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# before do
|
17
|
+
# allow(Service).to receive_messages(foo: bar, baz: qux)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good - ignore same message
|
21
|
+
# before do
|
22
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
23
|
+
# allow(Service).to receive(:foo).and_return(qux)
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class ReceiveMessages < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
include RangeHelp
|
29
|
+
|
30
|
+
MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
|
31
|
+
'%<loc>s.'
|
32
|
+
|
33
|
+
# @!method allow_receive_message?(node)
|
34
|
+
def_node_matcher :allow_receive_message?, <<~PATTERN
|
35
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc?))
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
# @!method allow_argument(node)
|
39
|
+
def_node_matcher :allow_argument, <<~PATTERN
|
40
|
+
(send (send nil? :allow $_ ...) ...)
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
# @!method receive_node(node)
|
44
|
+
def_node_search :receive_node, <<~PATTERN
|
45
|
+
$(send (send nil? :receive ...) ...)
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
# @!method receive_arg(node)
|
49
|
+
def_node_search :receive_arg, <<~PATTERN
|
50
|
+
(send (send nil? :receive $_) ...)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
# @!method receive_and_return_argument(node)
|
54
|
+
def_node_matcher :receive_and_return_argument, <<~PATTERN
|
55
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
|
56
|
+
PATTERN
|
57
|
+
|
58
|
+
def on_begin(node)
|
59
|
+
repeated_receive_message(node).each do |item, repeated_lines, args|
|
60
|
+
next if repeated_lines.empty?
|
61
|
+
|
62
|
+
register_offense(item, repeated_lines, args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def repeated_receive_message(node)
|
69
|
+
node
|
70
|
+
.children
|
71
|
+
.select { |child| allow_receive_message?(child) }
|
72
|
+
.group_by { |child| allow_argument(child) }
|
73
|
+
.values
|
74
|
+
.reject(&:one?)
|
75
|
+
.flat_map { |items| add_repeated_lines_and_arguments(items) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_repeated_lines_and_arguments(items)
|
79
|
+
uniq_items = uniq_items(items)
|
80
|
+
repeated_lines = uniq_items.map(&:first_line)
|
81
|
+
uniq_items.map do |item|
|
82
|
+
[item, repeated_lines - [item.first_line], arguments(uniq_items)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def uniq_items(items)
|
87
|
+
items.select do |item|
|
88
|
+
items.none? do |i|
|
89
|
+
receive_arg(item).first == receive_arg(i).first &&
|
90
|
+
!same_line?(item, i)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def arguments(items)
|
96
|
+
items.map do |item|
|
97
|
+
receive_and_return_argument(item) do |receive_arg, return_arg|
|
98
|
+
"#{normalize_receive_arg(receive_arg)}: " \
|
99
|
+
"#{normalize_return_arg(return_arg)}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def normalize_receive_arg(receive_arg)
|
105
|
+
if requires_quotes?(receive_arg)
|
106
|
+
"'#{receive_arg}'"
|
107
|
+
else
|
108
|
+
receive_arg
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def normalize_return_arg(return_arg)
|
113
|
+
if return_arg.hash_type? && !return_arg.braces?
|
114
|
+
"{ #{return_arg.source} }"
|
115
|
+
else
|
116
|
+
return_arg.source
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def register_offense(item, repeated_lines, args)
|
121
|
+
add_offense(item, message: message(repeated_lines)) do |corrector|
|
122
|
+
if item.loc.line < repeated_lines.min
|
123
|
+
replace_to_receive_messages(corrector, item, args)
|
124
|
+
else
|
125
|
+
corrector.remove(item_range_by_whole_lines(item))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def message(repeated_lines)
|
131
|
+
format(MSG, loc: repeated_lines)
|
132
|
+
end
|
133
|
+
|
134
|
+
def replace_to_receive_messages(corrector, item, args)
|
135
|
+
receive_node(item) do |node|
|
136
|
+
corrector.replace(node,
|
137
|
+
"receive_messages(#{args.join(', ')})")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def item_range_by_whole_lines(item)
|
142
|
+
range_by_whole_lines(item.source_range, include_final_newline: true)
|
143
|
+
end
|
144
|
+
|
145
|
+
def heredoc?(node)
|
146
|
+
(node.str_type? || node.dstr_type?) && node.heredoc?
|
147
|
+
end
|
148
|
+
|
149
|
+
def requires_quotes?(value)
|
150
|
+
value.match?(/^:".*?"|=$/)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# Only investigates references that are one of the supported styles.
|
9
9
|
#
|
10
|
-
# @see https://
|
10
|
+
# @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
|
11
11
|
#
|
12
12
|
# This cop can be configured in your configuration using the
|
13
13
|
# `EnforcedStyle` option and supports `--auto-gen-config`.
|
@@ -18,6 +18,7 @@ require_relative 'rspec/factory_bot/syntax_methods'
|
|
18
18
|
|
19
19
|
require_relative 'rspec/rails/avoid_setup_hook'
|
20
20
|
require_relative 'rspec/rails/have_http_status'
|
21
|
+
require_relative 'rspec/rails/negation_be_valid'
|
21
22
|
begin
|
22
23
|
require_relative 'rspec/rails/http_status'
|
23
24
|
rescue LoadError
|
@@ -99,6 +100,7 @@ require_relative 'rspec/pending'
|
|
99
100
|
require_relative 'rspec/pending_without_reason'
|
100
101
|
require_relative 'rspec/predicate_matcher'
|
101
102
|
require_relative 'rspec/receive_counts'
|
103
|
+
require_relative 'rspec/receive_messages'
|
102
104
|
require_relative 'rspec/receive_never'
|
103
105
|
require_relative 'rspec/redundant_around'
|
104
106
|
require_relative 'rspec/repeated_description'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.23.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Backus
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2023-
|
13
|
+
date: 2023-07-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubocop
|
@@ -175,8 +175,10 @@ files:
|
|
175
175
|
- lib/rubocop/cop/rspec/rails/http_status.rb
|
176
176
|
- lib/rubocop/cop/rspec/rails/inferred_spec_type.rb
|
177
177
|
- lib/rubocop/cop/rspec/rails/minitest_assertions.rb
|
178
|
+
- lib/rubocop/cop/rspec/rails/negation_be_valid.rb
|
178
179
|
- lib/rubocop/cop/rspec/rails/travel_around.rb
|
179
180
|
- lib/rubocop/cop/rspec/receive_counts.rb
|
181
|
+
- lib/rubocop/cop/rspec/receive_messages.rb
|
180
182
|
- lib/rubocop/cop/rspec/receive_never.rb
|
181
183
|
- lib/rubocop/cop/rspec/redundant_around.rb
|
182
184
|
- lib/rubocop/cop/rspec/repeated_description.rb
|