rubocop-rspec 3.7.0 → 3.8.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 +11 -0
- data/config/default.yml +12 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +13 -2
- data/lib/rubocop/cop/rspec/leaky_local_variable.rb +138 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +30 -0
- data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +1 -1
- data/lib/rubocop/cop/rspec/mixin/variable.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +1 -3
- data/lib/rubocop/cop/rspec/receive_never.rb +26 -0
- data/lib/rubocop/cop/rspec/repeated_example.rb +34 -15
- data/lib/rubocop/cop/rspec/sort_metadata.rb +1 -1
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +54 -4
- data/lib/rubocop/cop/rspec/variable_definition.rb +2 -10
- data/lib/rubocop/cop/rspec/verified_doubles.rb +51 -3
- data/lib/rubocop/cop/rspec_cops.rb +1 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +4 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abe85fc893e4ec59cb9b5f1e999d35bb41692feacc4dba579d135b3eefd6e4c7
|
|
4
|
+
data.tar.gz: 953ba29bf3fce28b79943e69039b638574d74432dac8b896f3dc8e589a20581c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: acdf813781e2081ec34540e83a9a48fc289dab5cc8bff69d9610beb4a3f21c30955794eb4226d40b9f0460a04d38665c2aa4ce0857f35d8bd3a7325d91a861be
|
|
7
|
+
data.tar.gz: 752074b0fdce204596efc71e2358982c73c2fccff07da5da364c88b8ad5e99d5a8bfdbc855a9dd422169adeddf278266352f732092b5aa86d14502016ca77837
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Master (Unreleased)
|
|
4
4
|
|
|
5
|
+
## 3.8.0 (2025-11-12)
|
|
6
|
+
|
|
7
|
+
- Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic])
|
|
8
|
+
- Bump RuboCop requirement to +1.81. ([@ydah])
|
|
9
|
+
- Fix a false positive for `RSpec/LetSetup` when `let!` used in outer scope. ([@ydah])
|
|
10
|
+
- Fix a false positive for `RSpec/ReceiveNever` cop when `allow(...).to receive(...).never`. ([@ydah])
|
|
11
|
+
- Fix detection of nameless doubles with methods in `RSpec/VerifiedDoubles`. ([@ushi-as])
|
|
12
|
+
- Improve an offense message for `RSpec/RepeatedExample` cop. ([@ydah])
|
|
13
|
+
- Let `RSpec/SpecFilePathFormat` leverage ActiveSupport inflections when configured. ([@corsonknowles], [@bquorning])
|
|
14
|
+
|
|
5
15
|
## 3.7.0 (2025-09-01)
|
|
6
16
|
|
|
7
17
|
- Mark `RSpec/IncludeExamples` as `SafeAutoCorrect: false`. ([@yujideveloper])
|
|
@@ -1077,6 +1087,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
|
1077
1087
|
[@tmaier]: https://github.com/tmaier
|
|
1078
1088
|
[@topalovic]: https://github.com/topalovic
|
|
1079
1089
|
[@twalpole]: https://github.com/twalpole
|
|
1090
|
+
[@ushi-as]: https://github.com/ushi-as
|
|
1080
1091
|
[@vzvu3k6k]: https://github.com/vzvu3k6k
|
|
1081
1092
|
[@walf443]: https://github.com/walf443
|
|
1082
1093
|
[@yasu551]: https://github.com/yasu551
|
data/config/default.yml
CHANGED
|
@@ -610,6 +610,12 @@ RSpec/LeakyConstantDeclaration:
|
|
|
610
610
|
StyleGuide: https://rspec.rubystyle.guide/#declare-constants
|
|
611
611
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration
|
|
612
612
|
|
|
613
|
+
RSpec/LeakyLocalVariable:
|
|
614
|
+
Description: Checks for local variables from outer scopes used inside examples.
|
|
615
|
+
Enabled: pending
|
|
616
|
+
VersionAdded: '3.8'
|
|
617
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyLocalVariable
|
|
618
|
+
|
|
613
619
|
RSpec/LetBeforeExamples:
|
|
614
620
|
Description: Checks for `let` definitions that come after an example.
|
|
615
621
|
Enabled: true
|
|
@@ -933,7 +939,13 @@ RSpec/SpecFilePathFormat:
|
|
|
933
939
|
IgnoreMethods: false
|
|
934
940
|
IgnoreMetadata:
|
|
935
941
|
type: routing
|
|
942
|
+
InflectorPath: "./config/initializers/inflections.rb"
|
|
943
|
+
SupportedInflectors:
|
|
944
|
+
- default
|
|
945
|
+
- active_support
|
|
946
|
+
EnforcedInflector: default
|
|
936
947
|
VersionAdded: '2.24'
|
|
948
|
+
VersionChanged: '3.8'
|
|
937
949
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathFormat
|
|
938
950
|
|
|
939
951
|
RSpec/SpecFilePathSuffix:
|
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
|
67
67
|
|
|
68
68
|
# @!method context_wording(node)
|
|
69
69
|
def_node_matcher :context_wording, <<~PATTERN
|
|
70
|
-
(block (send #rspec? { :context :shared_context } $(
|
|
70
|
+
(block (send #rspec? { :context :shared_context } $(any_str ...) ...) ...)
|
|
71
71
|
PATTERN
|
|
72
72
|
|
|
73
73
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
@@ -10,8 +10,19 @@ module RuboCop
|
|
|
10
10
|
#
|
|
11
11
|
# @see http://betterspecs.org/#should
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
13
|
+
# @safety
|
|
14
|
+
# The autocorrect is experimental - use with care! It can be configured
|
|
15
|
+
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
|
|
16
|
+
#
|
|
17
|
+
# While the autocorrect will not break your code (it only modifies test
|
|
18
|
+
# description strings, not the actual test logic), it may produce
|
|
19
|
+
# grammatically incorrect English in some cases. Always review the diff
|
|
20
|
+
# when using autocorrect to ensure the descriptions remain natural and
|
|
21
|
+
# accurate.
|
|
22
|
+
#
|
|
23
|
+
# This is not classified as an unsafe autocorrect because it does not
|
|
24
|
+
# affect code behavior, but manual review of changes is strongly
|
|
25
|
+
# recommended.
|
|
15
26
|
#
|
|
16
27
|
# Use the DisallowedExamples setting to prevent unclear or insufficient
|
|
17
28
|
# descriptions. Please note that this config will not be treated as
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Checks for local variables from outer scopes used inside examples.
|
|
7
|
+
#
|
|
8
|
+
# Local variables assigned outside an example but used within it act
|
|
9
|
+
# as shared state, which can make tests non-deterministic.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad - outside variable used in a hook
|
|
13
|
+
# user = create(:user)
|
|
14
|
+
#
|
|
15
|
+
# before { user.update(admin: true) }
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# let(:user) { create(:user) }
|
|
19
|
+
#
|
|
20
|
+
# before { user.update(admin: true) }
|
|
21
|
+
#
|
|
22
|
+
# # bad - outside variable used in an example
|
|
23
|
+
# user = create(:user)
|
|
24
|
+
#
|
|
25
|
+
# it 'is persisted' do
|
|
26
|
+
# expect(user).to be_persisted
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # good
|
|
30
|
+
# let(:user) { create(:user) }
|
|
31
|
+
#
|
|
32
|
+
# it 'is persisted' do
|
|
33
|
+
# expect(user).to be_persisted
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # also good - assigning the variable within the example
|
|
37
|
+
# it 'is persisted' do
|
|
38
|
+
# user = create(:user)
|
|
39
|
+
#
|
|
40
|
+
# expect(user).to be_persisted
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# # bad - outside variable passed to included examples
|
|
44
|
+
# attrs = ['foo', 'bar']
|
|
45
|
+
#
|
|
46
|
+
# it_behaves_like 'some examples', attrs
|
|
47
|
+
#
|
|
48
|
+
# # good
|
|
49
|
+
# it_behaves_like 'some examples' do
|
|
50
|
+
# let(:attrs) { ['foo', 'bar'] }
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# # good - when variable is used only as example description
|
|
54
|
+
# attribute = 'foo'
|
|
55
|
+
#
|
|
56
|
+
# it "#{attribute} is persisted" do
|
|
57
|
+
# expectations
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# # good - when variable is used only to include other examples
|
|
61
|
+
# examples = foo ? 'some examples' : 'other examples'
|
|
62
|
+
#
|
|
63
|
+
# it_behaves_like examples, another_argument
|
|
64
|
+
#
|
|
65
|
+
class LeakyLocalVariable < Base
|
|
66
|
+
MSG = 'Do not use local variables defined outside of ' \
|
|
67
|
+
'examples inside of them.'
|
|
68
|
+
|
|
69
|
+
# @!method example_method?(node)
|
|
70
|
+
def_node_matcher :example_method?, <<~PATTERN
|
|
71
|
+
(send nil? #Examples.all _)
|
|
72
|
+
PATTERN
|
|
73
|
+
|
|
74
|
+
# @!method includes_method?(node)
|
|
75
|
+
def_node_matcher :includes_method?, <<~PATTERN
|
|
76
|
+
(send nil? #Includes.all ...)
|
|
77
|
+
PATTERN
|
|
78
|
+
|
|
79
|
+
def self.joining_forces
|
|
80
|
+
VariableForce
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def after_leaving_scope(scope, _variable_table)
|
|
84
|
+
scope.variables.each_value { |variable| check_references(variable) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def check_references(variable)
|
|
90
|
+
variable.assignments.each do |assignment|
|
|
91
|
+
next if part_of_example_scope?(assignment.node)
|
|
92
|
+
|
|
93
|
+
assignment.references.each do |reference|
|
|
94
|
+
next unless inside_describe_block?(reference)
|
|
95
|
+
next unless part_of_example_scope?(reference)
|
|
96
|
+
next if allowed_reference?(reference)
|
|
97
|
+
|
|
98
|
+
add_offense(assignment.node)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def allowed_reference?(node)
|
|
104
|
+
node.each_ancestor.any? do |ancestor|
|
|
105
|
+
next true if example_method?(ancestor)
|
|
106
|
+
if includes_method?(ancestor)
|
|
107
|
+
next allowed_includes_arguments?(ancestor, node)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def allowed_includes_arguments?(node, argument)
|
|
115
|
+
node.arguments[1..].all? do |argument_node|
|
|
116
|
+
next true if argument_node.type?(:dstr, :dsym)
|
|
117
|
+
|
|
118
|
+
argument_node != argument &&
|
|
119
|
+
argument_node.each_descendant.none?(argument)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def part_of_example_scope?(node)
|
|
124
|
+
node.each_ancestor.any? { |ancestor| example_scope?(ancestor) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def example_scope?(node)
|
|
128
|
+
subject?(node) || let?(node) || hook?(node) || example?(node) ||
|
|
129
|
+
include?(node)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def inside_describe_block?(node)
|
|
133
|
+
node.each_ancestor(:block).any? { |ancestor| spec_group?(ancestor) }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -25,6 +25,18 @@ module RuboCop
|
|
|
25
25
|
# it 'counts widgets' do
|
|
26
26
|
# expect(Widget.count).to eq(1)
|
|
27
27
|
# end
|
|
28
|
+
#
|
|
29
|
+
# # good
|
|
30
|
+
# describe 'a widget' do
|
|
31
|
+
# let!(:my_widget) { create(:widget) }
|
|
32
|
+
# context 'when visiting its page' do
|
|
33
|
+
# let!(:my_widget) { create(:widget, name: 'Special') }
|
|
34
|
+
# it 'counts widgets' do
|
|
35
|
+
# expect(Widget.count).to eq(1)
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
28
40
|
class LetSetup < Base
|
|
29
41
|
MSG = 'Do not use `let!` to setup objects not referenced in tests.'
|
|
30
42
|
|
|
@@ -59,6 +71,8 @@ module RuboCop
|
|
|
59
71
|
|
|
60
72
|
def unused_let_bang(node)
|
|
61
73
|
child_let_bang(node) do |method_send, method_name|
|
|
74
|
+
next if overrides_outer_let_bang?(node, method_name)
|
|
75
|
+
|
|
62
76
|
yield(method_send) unless method_called?(node, method_name.to_sym)
|
|
63
77
|
end
|
|
64
78
|
end
|
|
@@ -68,6 +82,22 @@ module RuboCop
|
|
|
68
82
|
let_bang(let, &block)
|
|
69
83
|
end
|
|
70
84
|
end
|
|
85
|
+
|
|
86
|
+
def overrides_outer_let_bang?(node, method_name)
|
|
87
|
+
node.each_ancestor(:block).any? do |ancestor|
|
|
88
|
+
next unless example_or_shared_group_or_including?(ancestor)
|
|
89
|
+
|
|
90
|
+
outer_let_bang?(ancestor, method_name)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def outer_let_bang?(ancestor_node, method_name)
|
|
95
|
+
RuboCop::RSpec::ExampleGroup.new(ancestor_node).lets.any? do |let|
|
|
96
|
+
let_bang(let) do |_send, name|
|
|
97
|
+
name == method_name
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
71
101
|
end
|
|
72
102
|
end
|
|
73
103
|
end
|
|
@@ -181,9 +181,7 @@ module RuboCop
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
def heredoc_argument?(matcher)
|
|
184
|
-
matcher.arguments.select
|
|
185
|
-
arg.type?(:str, :dstr, :xstr)
|
|
186
|
-
end.any?(&:heredoc?)
|
|
184
|
+
matcher.arguments.select(&:any_str_type?).any?(&:heredoc?)
|
|
187
185
|
end
|
|
188
186
|
|
|
189
187
|
# @!method predicate_matcher?(node)
|
|
@@ -5,6 +5,11 @@ module RuboCop
|
|
|
5
5
|
module RSpec
|
|
6
6
|
# Prefer `not_to receive(...)` over `receive(...).never`.
|
|
7
7
|
#
|
|
8
|
+
# This cop only flags usage with `expect`. It ignores `allow` because
|
|
9
|
+
# `allow(...).to receive(...).never` is a valid way to ensure a method
|
|
10
|
+
# is not called, while `allow(...).not_to receive(...)` would have
|
|
11
|
+
# different semantics.
|
|
12
|
+
#
|
|
8
13
|
# @example
|
|
9
14
|
# # bad
|
|
10
15
|
# expect(foo).to receive(:bar).never
|
|
@@ -12,6 +17,9 @@ module RuboCop
|
|
|
12
17
|
# # good
|
|
13
18
|
# expect(foo).not_to receive(:bar)
|
|
14
19
|
#
|
|
20
|
+
# # not flagged by this cop
|
|
21
|
+
# allow(foo).to receive(:bar).never
|
|
22
|
+
#
|
|
15
23
|
class ReceiveNever < Base
|
|
16
24
|
extend AutoCorrector
|
|
17
25
|
MSG = 'Use `not_to receive` instead of `never`.'
|
|
@@ -20,8 +28,20 @@ module RuboCop
|
|
|
20
28
|
# @!method method_on_stub?(node)
|
|
21
29
|
def_node_search :method_on_stub?, '(send nil? :receive ...)'
|
|
22
30
|
|
|
31
|
+
# @!method expect_to_receive?(node)
|
|
32
|
+
def_node_matcher :expect_to_receive?, <<~PATTERN
|
|
33
|
+
(send
|
|
34
|
+
{
|
|
35
|
+
(send #rspec? {:expect :expect_any_instance_of} ...)
|
|
36
|
+
(block (send #rspec? :expect) ...)
|
|
37
|
+
(send nil? :is_expected)
|
|
38
|
+
}
|
|
39
|
+
:to ...)
|
|
40
|
+
PATTERN
|
|
41
|
+
|
|
23
42
|
def on_send(node)
|
|
24
43
|
return unless node.method?(:never) && method_on_stub?(node)
|
|
44
|
+
return unless used_with_expect?(node)
|
|
25
45
|
|
|
26
46
|
add_offense(node.loc.selector) do |corrector|
|
|
27
47
|
autocorrect(corrector, node)
|
|
@@ -30,6 +50,12 @@ module RuboCop
|
|
|
30
50
|
|
|
31
51
|
private
|
|
32
52
|
|
|
53
|
+
def used_with_expect?(node)
|
|
54
|
+
node.each_ancestor(:send).any? do |ancestor|
|
|
55
|
+
expect_to_receive?(ancestor)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
33
59
|
def autocorrect(corrector, node)
|
|
34
60
|
corrector.replace(node.parent.loc.selector, 'not_to')
|
|
35
61
|
range = node.loc.dot.with(end_pos: node.loc.selector.end_pos)
|
|
@@ -16,36 +16,55 @@ module RuboCop
|
|
|
16
16
|
# end
|
|
17
17
|
#
|
|
18
18
|
class RepeatedExample < Base
|
|
19
|
-
MSG = "Don't repeat examples within an example group."
|
|
19
|
+
MSG = "Don't repeat examples within an example group. " \
|
|
20
|
+
'Repeated on line(s) %<lines>s.'
|
|
20
21
|
|
|
21
22
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
22
23
|
return unless example_group?(node)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
find_repeated_examples(node).each do |repeated_examples|
|
|
26
|
+
add_offenses_for_repeated_group(repeated_examples)
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
|
-
def
|
|
32
|
-
RuboCop::RSpec::ExampleGroup.new(node)
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
def find_repeated_examples(node)
|
|
33
|
+
examples = RuboCop::RSpec::ExampleGroup.new(node).examples
|
|
34
|
+
|
|
35
|
+
examples
|
|
36
|
+
.group_by { |example| build_example_signature(example) }
|
|
35
37
|
.values
|
|
36
|
-
.
|
|
37
|
-
.flatten
|
|
38
|
-
.map(&:to_node)
|
|
38
|
+
.select { |group| group.size > 1 }
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
def build_example_signature(example)
|
|
42
|
+
signature = [example.metadata, example.implementation]
|
|
44
43
|
if example.definition.method?(:its)
|
|
45
|
-
|
|
44
|
+
signature << example.definition.arguments
|
|
45
|
+
end
|
|
46
|
+
signature
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add_offenses_for_repeated_group(repeated_examples)
|
|
50
|
+
repeated_examples.each do |example|
|
|
51
|
+
other_lines = extract_other_lines(repeated_examples, example)
|
|
52
|
+
add_offense(example.to_node, message: message(other_lines))
|
|
46
53
|
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extract_other_lines(examples_group, current_example)
|
|
57
|
+
current_node = current_example.to_node
|
|
58
|
+
|
|
59
|
+
examples_group
|
|
60
|
+
.reject { |ex| ex.to_node.equal?(current_node) }
|
|
61
|
+
.map { |ex| ex.to_node.first_line }
|
|
62
|
+
.uniq
|
|
63
|
+
.sort
|
|
64
|
+
end
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
def message(other_lines)
|
|
67
|
+
format(MSG, lines: other_lines.join(', '))
|
|
49
68
|
end
|
|
50
69
|
end
|
|
51
70
|
end
|
|
@@ -30,7 +30,7 @@ module RuboCop
|
|
|
30
30
|
|
|
31
31
|
# @!method match_ambiguous_trailing_metadata?(node)
|
|
32
32
|
def_node_matcher :match_ambiguous_trailing_metadata?, <<~PATTERN
|
|
33
|
-
(send _ _ _ ... !{hash sym
|
|
33
|
+
(send _ _ _ ... !{hash sym any_str})
|
|
34
34
|
PATTERN
|
|
35
35
|
|
|
36
36
|
def on_metadata(args, hash)
|
|
@@ -32,6 +32,12 @@ module RuboCop
|
|
|
32
32
|
# # good
|
|
33
33
|
# whatever_spec.rb # describe MyClass, type: :routing do; end
|
|
34
34
|
#
|
|
35
|
+
# @example `EnforcedInflector: active_support`
|
|
36
|
+
# # Enable to use ActiveSupport's inflector for custom acronyms
|
|
37
|
+
# # like HTTP, etc. Set to "default" by default.
|
|
38
|
+
# # Configure `InflectorPath` with the path to the inflector file.
|
|
39
|
+
# # The default is ./config/initializers/inflections.rb.
|
|
40
|
+
#
|
|
35
41
|
class SpecFilePathFormat < Base
|
|
36
42
|
include TopLevelGroup
|
|
37
43
|
include Namespace
|
|
@@ -59,6 +65,53 @@ module RuboCop
|
|
|
59
65
|
|
|
60
66
|
private
|
|
61
67
|
|
|
68
|
+
# Inflector module that uses ActiveSupport for advanced inflection rules
|
|
69
|
+
module ActiveSupportInflector
|
|
70
|
+
def self.call(string)
|
|
71
|
+
ActiveSupport::Inflector.underscore(string)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.prepare_availability(config)
|
|
75
|
+
return if @prepared
|
|
76
|
+
|
|
77
|
+
@prepared = true
|
|
78
|
+
|
|
79
|
+
inflector_path = config.fetch('InflectorPath')
|
|
80
|
+
|
|
81
|
+
unless File.exist?(inflector_path)
|
|
82
|
+
raise "The configured `InflectorPath` #{inflector_path} does " \
|
|
83
|
+
'not exist.'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
require 'active_support/inflector'
|
|
87
|
+
require inflector_path
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Inflector module that uses basic regex-based conversion
|
|
92
|
+
module DefaultInflector
|
|
93
|
+
def self.call(string)
|
|
94
|
+
string
|
|
95
|
+
.gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
|
|
96
|
+
.gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
|
|
97
|
+
.downcase
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def inflector
|
|
102
|
+
case cop_config.fetch('EnforcedInflector')
|
|
103
|
+
when 'active_support'
|
|
104
|
+
ActiveSupportInflector.prepare_availability(cop_config)
|
|
105
|
+
ActiveSupportInflector
|
|
106
|
+
when 'default'
|
|
107
|
+
DefaultInflector
|
|
108
|
+
else
|
|
109
|
+
# :nocov:
|
|
110
|
+
:noop
|
|
111
|
+
# :nocov:
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
62
115
|
def ensure_correct_file_path(send_node, class_name, arguments)
|
|
63
116
|
pattern = correct_path_pattern(class_name, arguments)
|
|
64
117
|
return if filename_ends_with?(pattern)
|
|
@@ -106,10 +159,7 @@ module RuboCop
|
|
|
106
159
|
end
|
|
107
160
|
|
|
108
161
|
def camel_to_snake_case(string)
|
|
109
|
-
string
|
|
110
|
-
.gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
|
|
111
|
-
.gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
|
|
112
|
-
.downcase
|
|
162
|
+
inflector.call(string)
|
|
113
163
|
end
|
|
114
164
|
|
|
115
165
|
def custom_transform
|
|
@@ -60,16 +60,8 @@ module RuboCop
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def style_offense?(variable)
|
|
63
|
-
(style == :symbols &&
|
|
64
|
-
(style == :strings &&
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def string?(node)
|
|
68
|
-
node.str_type?
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def symbol?(node)
|
|
72
|
-
node.type?(:sym, :dsym)
|
|
63
|
+
(style == :symbols && variable.str_type?) ||
|
|
64
|
+
(style == :strings && variable.any_sym_type?)
|
|
73
65
|
end
|
|
74
66
|
end
|
|
75
67
|
end
|
|
@@ -10,12 +10,12 @@ module RuboCop
|
|
|
10
10
|
# @example
|
|
11
11
|
# # bad
|
|
12
12
|
# let(:foo) do
|
|
13
|
-
# double(method_name: 'returned value')
|
|
13
|
+
# double("ClassName", method_name: 'returned value')
|
|
14
14
|
# end
|
|
15
15
|
#
|
|
16
16
|
# # bad
|
|
17
17
|
# let(:foo) do
|
|
18
|
-
#
|
|
18
|
+
# spy("ClassName", method_name: 'returned value')
|
|
19
19
|
# end
|
|
20
20
|
#
|
|
21
21
|
# # good
|
|
@@ -23,6 +23,50 @@ module RuboCop
|
|
|
23
23
|
# instance_double("ClassName", method_name: 'returned value')
|
|
24
24
|
# end
|
|
25
25
|
#
|
|
26
|
+
# # good
|
|
27
|
+
# let(:foo) do
|
|
28
|
+
# class_double("ClassName", method_name: 'returned value')
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # good
|
|
32
|
+
# let(:foo) do
|
|
33
|
+
# object_double("some object", method_name: 'returned value')
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @example `IgnoreNameless: true (default)`
|
|
37
|
+
# # good
|
|
38
|
+
# let(:foo) do
|
|
39
|
+
# double(method_name: 'returned value')
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # good
|
|
43
|
+
# let(:foo) do
|
|
44
|
+
# double
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @example `IgnoreNameless: false`
|
|
48
|
+
# # bad
|
|
49
|
+
# let(:foo) do
|
|
50
|
+
# double(method_name: 'returned value')
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# # bad
|
|
54
|
+
# let(:foo) do
|
|
55
|
+
# double
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# @example `IgnoreSymbolicNames: false (default)`
|
|
59
|
+
# # bad
|
|
60
|
+
# let(:foo) do
|
|
61
|
+
# double(:foo)
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# @example `IgnoreSymbolicNames: true`
|
|
65
|
+
# # good
|
|
66
|
+
# let(:foo) do
|
|
67
|
+
# double(:foo)
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
26
70
|
class VerifiedDoubles < Base
|
|
27
71
|
MSG = 'Prefer using verifying doubles over normal doubles.'
|
|
28
72
|
RESTRICT_ON_SEND = %i[double spy].freeze
|
|
@@ -34,7 +78,7 @@ module RuboCop
|
|
|
34
78
|
|
|
35
79
|
def on_send(node)
|
|
36
80
|
unverified_double(node) do |name, *_args|
|
|
37
|
-
return if name.nil? && cop_config['IgnoreNameless']
|
|
81
|
+
return if (name.nil? || hash?(name)) && cop_config['IgnoreNameless']
|
|
38
82
|
return if symbol?(name) && cop_config['IgnoreSymbolicNames']
|
|
39
83
|
|
|
40
84
|
add_offense(node)
|
|
@@ -46,6 +90,10 @@ module RuboCop
|
|
|
46
90
|
def symbol?(name)
|
|
47
91
|
name&.sym_type?
|
|
48
92
|
end
|
|
93
|
+
|
|
94
|
+
def hash?(arg)
|
|
95
|
+
arg.hash_type?
|
|
96
|
+
end
|
|
49
97
|
end
|
|
50
98
|
end
|
|
51
99
|
end
|
|
@@ -57,6 +57,7 @@ require_relative 'rspec/it_behaves_like'
|
|
|
57
57
|
require_relative 'rspec/iterated_expectation'
|
|
58
58
|
require_relative 'rspec/leading_subject'
|
|
59
59
|
require_relative 'rspec/leaky_constant_declaration'
|
|
60
|
+
require_relative 'rspec/leaky_local_variable'
|
|
60
61
|
require_relative 'rspec/let_before_examples'
|
|
61
62
|
require_relative 'rspec/let_setup'
|
|
62
63
|
require_relative 'rspec/match_array'
|
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: 3.
|
|
4
|
+
version: 3.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Backus
|
|
@@ -31,20 +31,14 @@ dependencies:
|
|
|
31
31
|
requirements:
|
|
32
32
|
- - "~>"
|
|
33
33
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '1.
|
|
35
|
-
- - ">="
|
|
36
|
-
- !ruby/object:Gem::Version
|
|
37
|
-
version: 1.72.1
|
|
34
|
+
version: '1.81'
|
|
38
35
|
type: :runtime
|
|
39
36
|
prerelease: false
|
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
41
38
|
requirements:
|
|
42
39
|
- - "~>"
|
|
43
40
|
- !ruby/object:Gem::Version
|
|
44
|
-
version: '1.
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: 1.72.1
|
|
41
|
+
version: '1.81'
|
|
48
42
|
description: |
|
|
49
43
|
Code style checking for RSpec files.
|
|
50
44
|
A plugin for the RuboCop code style enforcing & linting tool.
|
|
@@ -123,6 +117,7 @@ files:
|
|
|
123
117
|
- lib/rubocop/cop/rspec/iterated_expectation.rb
|
|
124
118
|
- lib/rubocop/cop/rspec/leading_subject.rb
|
|
125
119
|
- lib/rubocop/cop/rspec/leaky_constant_declaration.rb
|
|
120
|
+
- lib/rubocop/cop/rspec/leaky_local_variable.rb
|
|
126
121
|
- lib/rubocop/cop/rspec/let_before_examples.rb
|
|
127
122
|
- lib/rubocop/cop/rspec/let_setup.rb
|
|
128
123
|
- lib/rubocop/cop/rspec/match_array.rb
|