rubocop-rspec 2.22.0 → 2.23.1
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 +22 -0
- data/README.md +1 -1
- data/config/default.yml +39 -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 +96 -0
- data/lib/rubocop/cop/rspec/receive_messages.rb +160 -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: 2d8e118064ce45e0670b72b711b2e680f511f645997f86c9d22dcf9f0eb564d7
|
4
|
+
data.tar.gz: 8a0a510f390617e4e83e99bdbec1bc0146eb06b1b61c51aada05721154a6be43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a1f937488b43fe96496ec87d1a6fcc1d53385248937de31d1fb4a7923dd98a2769d225e23b7e8d5bd66c7c6e2c38fb5b3652c2792087f00999464a7161561fb
|
7
|
+
data.tar.gz: 9b2fed4ef2b93e9ac867133aaa11efdfd66cea642416cd82cc2ca322875154c6a3f174991f1a2c49cf762cfcf9c0aa17635348ce5e8ff8c300103b8c727d33c1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,25 @@
|
|
2
2
|
|
3
3
|
## Master (Unreleased)
|
4
4
|
|
5
|
+
## 2.23.1 (2023-08-07)
|
6
|
+
|
7
|
+
- Mark to `Safe: false` for `RSpec/Rails/NegationBeValid` cop. ([@ydah])
|
8
|
+
- Declare autocorrect as unsafe for `RSpec/ReceiveMessages`. ([@bquorning])
|
9
|
+
|
10
|
+
## 2.23.0 (2023-07-30)
|
11
|
+
|
12
|
+
- Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah])
|
13
|
+
- Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah])
|
14
|
+
- Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah])
|
15
|
+
- Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand])
|
16
|
+
- Fix a false negative for `RSpec/Pending` when `it` without body. ([@ydah])
|
17
|
+
- Add new `RSpec/ReceiveMessages` cop. ([@ydah])
|
18
|
+
- Change default.yml path to use `**/spec/*` instead of `spec/*`. ([@ydah])
|
19
|
+
- Add `AllowedIdentifiers` and `AllowedPatterns` configuration option to `RSpec/IndexedLet`. ([@ydah])
|
20
|
+
- Fix `RSpec/NamedSubject` when block has no body. ([@splattael])
|
21
|
+
- Fix `RSpec/LetBeforeExamples` autocorrect incompatible with `RSpec/ScatteredLet` autocorrect. ([@ydah])
|
22
|
+
- Update `RSpec/Focus` to support `shared_context` and `shared_examples` ([@tmaier])
|
23
|
+
|
5
24
|
## 2.22.0 (2023-05-06)
|
6
25
|
|
7
26
|
- 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 +800,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
781
800
|
[@cfabianski]: https://github.com/cfabianski
|
782
801
|
[@clupprich]: https://github.com/clupprich
|
783
802
|
[@composerinteralia]: https://github.com/composerinteralia
|
803
|
+
[@corydiamand]: https://github.com/corydiamand
|
784
804
|
[@darhazer]: https://github.com/Darhazer
|
785
805
|
[@daveworth]: https://github.com/daveworth
|
786
806
|
[@dduugg]: https://github.com/dduugg
|
@@ -859,6 +879,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
859
879
|
[@seanpdoyle]: https://github.com/seanpdoyle
|
860
880
|
[@sl4vr]: https://github.com/sl4vr
|
861
881
|
[@smcgivern]: https://github.com/smcgivern
|
882
|
+
[@splattael]: https://github.com/splattael
|
862
883
|
[@stephannv]: https://github.com/stephannv
|
863
884
|
[@t3h2mas]: https://github.com/t3h2mas
|
864
885
|
[@tdeo]: https://github.com/tdeo
|
@@ -866,6 +887,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
866
887
|
[@telmofcosta]: https://github.com/telmofcosta
|
867
888
|
[@tietew]: https://github.com/Tietew
|
868
889
|
[@timrogers]: https://github.com/timrogers
|
890
|
+
[@tmaier]: https://github.com/tmaier
|
869
891
|
[@topalovic]: https://github.com/topalovic
|
870
892
|
[@twalpole]: https://github.com/twalpole
|
871
893
|
[@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,13 @@ 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
|
+
SafeAutoCorrect: false
|
737
|
+
VersionAdded: '2.23'
|
738
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveMessages
|
739
|
+
|
728
740
|
RSpec/ReceiveNever:
|
729
741
|
Description: Prefer `not_to receive(...)` over `receive(...).never`.
|
730
742
|
Enabled: true
|
@@ -974,11 +986,11 @@ RSpec/FactoryBot/AttributeDefinedStatically:
|
|
974
986
|
Description: Always declare attribute values as blocks.
|
975
987
|
Enabled: true
|
976
988
|
Include:
|
977
|
-
- spec/factories.rb
|
978
|
-
- spec/factories/**/*.rb
|
979
|
-
- features/support/factories/**/*.rb
|
989
|
+
- "**/spec/factories.rb"
|
990
|
+
- "**/spec/factories/**/*.rb"
|
991
|
+
- "**/features/support/factories/**/*.rb"
|
980
992
|
VersionAdded: '1.28'
|
981
|
-
VersionChanged: '2.
|
993
|
+
VersionChanged: '2.23'
|
982
994
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
|
983
995
|
|
984
996
|
RSpec/FactoryBot/ConsistentParenthesesStyle:
|
@@ -997,26 +1009,26 @@ RSpec/FactoryBot/CreateList:
|
|
997
1009
|
Include:
|
998
1010
|
- "**/*_spec.rb"
|
999
1011
|
- "**/spec/**/*"
|
1000
|
-
- spec/factories.rb
|
1001
|
-
- spec/factories/**/*.rb
|
1002
|
-
- features/support/factories/**/*.rb
|
1012
|
+
- "**/spec/factories.rb"
|
1013
|
+
- "**/spec/factories/**/*.rb"
|
1014
|
+
- "**/features/support/factories/**/*.rb"
|
1003
1015
|
EnforcedStyle: create_list
|
1004
1016
|
SupportedStyles:
|
1005
1017
|
- create_list
|
1006
1018
|
- n_times
|
1007
1019
|
VersionAdded: '1.25'
|
1008
|
-
VersionChanged: '2.
|
1020
|
+
VersionChanged: '2.23'
|
1009
1021
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/CreateList
|
1010
1022
|
|
1011
1023
|
RSpec/FactoryBot/FactoryClassName:
|
1012
1024
|
Description: Use string value when setting the class attribute explicitly.
|
1013
1025
|
Enabled: true
|
1014
1026
|
Include:
|
1015
|
-
- spec/factories.rb
|
1016
|
-
- spec/factories/**/*.rb
|
1017
|
-
- features/support/factories/**/*.rb
|
1027
|
+
- "**/spec/factories.rb"
|
1028
|
+
- "**/spec/factories/**/*.rb"
|
1029
|
+
- "**/features/support/factories/**/*.rb"
|
1018
1030
|
VersionAdded: '1.37'
|
1019
|
-
VersionChanged: '2.
|
1031
|
+
VersionChanged: '2.23'
|
1020
1032
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/FactoryClassName
|
1021
1033
|
|
1022
1034
|
RSpec/FactoryBot/FactoryNameStyle:
|
@@ -1095,6 +1107,17 @@ RSpec/Rails/MinitestAssertions:
|
|
1095
1107
|
VersionAdded: '2.17'
|
1096
1108
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions
|
1097
1109
|
|
1110
|
+
RSpec/Rails/NegationBeValid:
|
1111
|
+
Description: Enforces use of `be_invalid` or `not_to` for negated be_valid.
|
1112
|
+
Safe: false
|
1113
|
+
EnforcedStyle: not_to
|
1114
|
+
SupportedStyles:
|
1115
|
+
- not_to
|
1116
|
+
- be_invalid
|
1117
|
+
Enabled: pending
|
1118
|
+
VersionAdded: '2.23'
|
1119
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/NegationBeValid
|
1120
|
+
|
1098
1121
|
RSpec/Rails/TravelAround:
|
1099
1122
|
Description: Prefer to travel in `before` rather than `around`.
|
1100
1123
|
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,96 @@
|
|
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
|
+
# @safety
|
10
|
+
# This cop is unsafe because it cannot guarantee that
|
11
|
+
# the test target is an instance of `ActiveModel::Validations``.
|
12
|
+
#
|
13
|
+
# @example EnforcedStyle: not_to (default)
|
14
|
+
# # bad
|
15
|
+
# expect(foo).to be_invalid
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# expect(foo).not_to be_valid
|
19
|
+
#
|
20
|
+
# @example EnforcedStyle: be_invalid
|
21
|
+
# # bad
|
22
|
+
# expect(foo).not_to be_valid
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# expect(foo).to be_invalid
|
26
|
+
#
|
27
|
+
class NegationBeValid < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
include ConfigurableEnforcedStyle
|
30
|
+
|
31
|
+
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
|
32
|
+
RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
|
33
|
+
|
34
|
+
# @!method not_to?(node)
|
35
|
+
def_node_matcher :not_to?, <<~PATTERN
|
36
|
+
(send ... :not_to (send nil? :be_valid ...))
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
# @!method be_invalid?(node)
|
40
|
+
def_node_matcher :be_invalid?, <<~PATTERN
|
41
|
+
(send ... :to (send nil? :be_invalid ...))
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
def on_send(node)
|
45
|
+
return unless offense?(node.parent)
|
46
|
+
|
47
|
+
add_offense(offense_range(node),
|
48
|
+
message: message(node.method_name)) do |corrector|
|
49
|
+
corrector.replace(node.parent.loc.selector, replaced_runner)
|
50
|
+
corrector.replace(node.loc.selector, replaced_matcher)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def offense?(node)
|
57
|
+
case style
|
58
|
+
when :not_to
|
59
|
+
be_invalid?(node)
|
60
|
+
when :be_invalid
|
61
|
+
not_to?(node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def offense_range(node)
|
66
|
+
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
|
67
|
+
end
|
68
|
+
|
69
|
+
def message(_matcher)
|
70
|
+
format(MSG,
|
71
|
+
runner: replaced_runner,
|
72
|
+
matcher: replaced_matcher)
|
73
|
+
end
|
74
|
+
|
75
|
+
def replaced_runner
|
76
|
+
case style
|
77
|
+
when :not_to
|
78
|
+
'not_to'
|
79
|
+
when :be_invalid
|
80
|
+
'to'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def replaced_matcher
|
85
|
+
case style
|
86
|
+
when :not_to
|
87
|
+
'be_valid'
|
88
|
+
when :be_invalid
|
89
|
+
'be_invalid'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,160 @@
|
|
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
|
+
# @safety
|
9
|
+
# The autocorrection is marked as unsafe, because it may change the
|
10
|
+
# order of stubs. This in turn may cause e.g. variables to be called
|
11
|
+
# before they are defined.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# before do
|
16
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
17
|
+
# allow(Service).to receive(:baz).and_return(qux)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# before do
|
22
|
+
# allow(Service).to receive_messages(foo: bar, baz: qux)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # good - ignore same message
|
26
|
+
# before do
|
27
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
28
|
+
# allow(Service).to receive(:foo).and_return(qux)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class ReceiveMessages < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
include RangeHelp
|
34
|
+
|
35
|
+
MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
|
36
|
+
'%<loc>s.'
|
37
|
+
|
38
|
+
# @!method allow_receive_message?(node)
|
39
|
+
def_node_matcher :allow_receive_message?, <<~PATTERN
|
40
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc?))
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
# @!method allow_argument(node)
|
44
|
+
def_node_matcher :allow_argument, <<~PATTERN
|
45
|
+
(send (send nil? :allow $_ ...) ...)
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
# @!method receive_node(node)
|
49
|
+
def_node_search :receive_node, <<~PATTERN
|
50
|
+
$(send (send nil? :receive ...) ...)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
# @!method receive_arg(node)
|
54
|
+
def_node_search :receive_arg, <<~PATTERN
|
55
|
+
(send (send nil? :receive $_) ...)
|
56
|
+
PATTERN
|
57
|
+
|
58
|
+
# @!method receive_and_return_argument(node)
|
59
|
+
def_node_matcher :receive_and_return_argument, <<~PATTERN
|
60
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
|
61
|
+
PATTERN
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
repeated_receive_message(node).each do |item, repeated_lines, args|
|
65
|
+
next if repeated_lines.empty?
|
66
|
+
|
67
|
+
register_offense(item, repeated_lines, args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_receive_message(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| allow_receive_message?(child) }
|
77
|
+
.group_by { |child| allow_argument(child) }
|
78
|
+
.values
|
79
|
+
.reject(&:one?)
|
80
|
+
.flat_map { |items| add_repeated_lines_and_arguments(items) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_repeated_lines_and_arguments(items)
|
84
|
+
uniq_items = uniq_items(items)
|
85
|
+
repeated_lines = uniq_items.map(&:first_line)
|
86
|
+
uniq_items.map do |item|
|
87
|
+
[item, repeated_lines - [item.first_line], arguments(uniq_items)]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def uniq_items(items)
|
92
|
+
items.select do |item|
|
93
|
+
items.none? do |i|
|
94
|
+
receive_arg(item).first == receive_arg(i).first &&
|
95
|
+
!same_line?(item, i)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def arguments(items)
|
101
|
+
items.map do |item|
|
102
|
+
receive_and_return_argument(item) do |receive_arg, return_arg|
|
103
|
+
"#{normalize_receive_arg(receive_arg)}: " \
|
104
|
+
"#{normalize_return_arg(return_arg)}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def normalize_receive_arg(receive_arg)
|
110
|
+
if requires_quotes?(receive_arg)
|
111
|
+
"'#{receive_arg}'"
|
112
|
+
else
|
113
|
+
receive_arg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def normalize_return_arg(return_arg)
|
118
|
+
if return_arg.hash_type? && !return_arg.braces?
|
119
|
+
"{ #{return_arg.source} }"
|
120
|
+
else
|
121
|
+
return_arg.source
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def register_offense(item, repeated_lines, args)
|
126
|
+
add_offense(item, message: message(repeated_lines)) do |corrector|
|
127
|
+
if item.loc.line < repeated_lines.min
|
128
|
+
replace_to_receive_messages(corrector, item, args)
|
129
|
+
else
|
130
|
+
corrector.remove(item_range_by_whole_lines(item))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def message(repeated_lines)
|
136
|
+
format(MSG, loc: repeated_lines)
|
137
|
+
end
|
138
|
+
|
139
|
+
def replace_to_receive_messages(corrector, item, args)
|
140
|
+
receive_node(item) do |node|
|
141
|
+
corrector.replace(node,
|
142
|
+
"receive_messages(#{args.join(', ')})")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def item_range_by_whole_lines(item)
|
147
|
+
range_by_whole_lines(item.source_range, include_final_newline: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
def heredoc?(node)
|
151
|
+
(node.str_type? || node.dstr_type?) && node.heredoc?
|
152
|
+
end
|
153
|
+
|
154
|
+
def requires_quotes?(value)
|
155
|
+
value.match?(/^:".*?"|=$/)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
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.1
|
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-08-07 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
|