rubocop-rspec 1.33.0 → 1.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -2
- data/README.md +10 -2
- data/config/default.yml +6 -1
- data/lib/rubocop/cop/rspec/any_instance.rb +0 -1
- data/lib/rubocop/cop/rspec/around_block.rb +1 -2
- data/lib/rubocop/cop/rspec/before_after_all.rb +0 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +18 -17
- data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
- data/lib/rubocop/cop/rspec/describe_method.rb +1 -1
- data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
- data/lib/rubocop/cop/rspec/described_class.rb +79 -13
- data/lib/rubocop/cop/rspec/empty_example_group.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/example_length.rb +1 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +6 -4
- data/lib/rubocop/cop/rspec/expect_actual.rb +1 -1
- data/lib/rubocop/cop/rspec/expect_output.rb +2 -0
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +1 -1
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +1 -2
- data/lib/rubocop/cop/rspec/file_path.rb +0 -1
- data/lib/rubocop/cop/rspec/focus.rb +1 -1
- data/lib/rubocop/cop/rspec/hook_argument.rb +0 -1
- data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
- data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +1 -1
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -1
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
- data/lib/rubocop/cop/rspec/leading_subject.rb +0 -1
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +128 -0
- data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -1
- data/lib/rubocop/cop/rspec/let_setup.rb +2 -4
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -2
- data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -1
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +32 -16
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +1 -1
- data/lib/rubocop/cop/rspec/nested_groups.rb +0 -1
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +0 -1
- data/lib/rubocop/cop/rspec/pending.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +0 -3
- data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
- data/lib/rubocop/cop/rspec/scattered_let.rb +1 -1
- data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +0 -1
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +17 -20
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +1 -4
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
- data/lib/rubocop/cop/rspec/void_expect.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +1 -0
- data/lib/rubocop/rspec/language.rb +1 -1
- data/lib/rubocop/rspec/top_level_describe.rb +0 -4
- data/lib/rubocop/rspec/version.rb +1 -1
- data/spec/rubocop/cop/rspec/cop_spec.rb +3 -3
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +7 -0
- data/spec/rubocop/cop/rspec/described_class_spec.rb +113 -80
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +33 -0
- data/spec/rubocop/cop/rspec/leaky_constant_declaration_spec.rb +91 -0
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +64 -37
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +113 -14
- data/spec/rubocop/rspec/language/selector_set_spec.rb +2 -2
- metadata +5 -2
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that no class, module, or constant is declared.
|
7
|
+
#
|
8
|
+
# Constants, including classes and modules, when declared in a block
|
9
|
+
# scope, are defined in global namespace, and leak between examples.
|
10
|
+
#
|
11
|
+
# If several examples may define a `DummyClass`, instead of being a
|
12
|
+
# blank slate class as it will be in the first example, subsequent
|
13
|
+
# examples will be reopening it and modifying its behaviour in
|
14
|
+
# unpredictable ways.
|
15
|
+
# Even worse when a class that exists in the codebase is reopened.
|
16
|
+
#
|
17
|
+
# Anonymous classes are fine, since they don't result in global
|
18
|
+
# namespace name clashes.
|
19
|
+
#
|
20
|
+
# @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
|
21
|
+
#
|
22
|
+
# @example Constants leak between examples
|
23
|
+
# # bad
|
24
|
+
# describe SomeClass do
|
25
|
+
# OtherClass = Struct.new
|
26
|
+
# CONSTANT_HERE = 'I leak into global namespace'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# describe SomeClass do
|
31
|
+
# before do
|
32
|
+
# stub_const('OtherClass', Struct.new)
|
33
|
+
# stub_const('CONSTANT_HERE', 'I only exist during this example')
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# # bad
|
39
|
+
# describe SomeClass do
|
40
|
+
# class FooClass < described_class
|
41
|
+
# def double_that
|
42
|
+
# some_base_method * 2
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# it { expect(FooClass.new.double_that).to eq(4) }
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # good - anonymous class, no constant needs to be defined
|
50
|
+
# let(:foo_class) do
|
51
|
+
# Class.new(described_class) do
|
52
|
+
# def double_that
|
53
|
+
# some_base_method * 2
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# it { expect(foo_class.new.double_that).to eq(4) }
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # good - constant is stubbed
|
61
|
+
# describe SomeClass do
|
62
|
+
# before do
|
63
|
+
# foo_class = Class.new(described_class) do
|
64
|
+
# def do_something
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
# stub_const('FooClass', foo_class)
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# it { expect(FooClass.new.double_that).to eq(4) }
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# # bad
|
75
|
+
# describe SomeClass do
|
76
|
+
# module SomeModule
|
77
|
+
# class SomeClass
|
78
|
+
# def do_something
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# # good
|
85
|
+
# describe SomeClass do
|
86
|
+
# before do
|
87
|
+
# foo_class = Class.new(described_class) do
|
88
|
+
# def do_something
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
# stub_const('SomeModule::SomeClass', foo_class)
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
class LeakyConstantDeclaration < Cop
|
95
|
+
MSG_CONST = 'Stub constant instead of declaring explicitly.'
|
96
|
+
MSG_CLASS = 'Stub class constant instead of declaring explicitly.'
|
97
|
+
MSG_MODULE = 'Stub module constant instead of declaring explicitly.'
|
98
|
+
|
99
|
+
def on_casgn(node)
|
100
|
+
return unless inside_describe_block?(node)
|
101
|
+
|
102
|
+
add_offense(node, message: MSG_CONST)
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_class(node)
|
106
|
+
return unless inside_describe_block?(node)
|
107
|
+
|
108
|
+
add_offense(node, message: MSG_CLASS)
|
109
|
+
end
|
110
|
+
|
111
|
+
def on_module(node)
|
112
|
+
return unless inside_describe_block?(node)
|
113
|
+
|
114
|
+
add_offense(node, message: MSG_MODULE)
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def inside_describe_block?(node)
|
120
|
+
node.each_ancestor(:block).any?(&method(:in_example_or_shared_group?))
|
121
|
+
end
|
122
|
+
|
123
|
+
def_node_matcher :in_example_or_shared_group?,
|
124
|
+
(ExampleGroups::ALL + SharedGroups::ALL).block_pattern
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -26,9 +26,7 @@ module RuboCop
|
|
26
26
|
# expect(Widget.count).to eq(1)
|
27
27
|
# end
|
28
28
|
class LetSetup < Cop
|
29
|
-
|
30
|
-
|
31
|
-
MSG = 'Do not use `let!` for test setup.'
|
29
|
+
MSG = 'Do not use `let!` to setup objects not referenced in tests.'
|
32
30
|
|
33
31
|
def_node_search :let_bang, <<-PATTERN
|
34
32
|
(block $(send nil? :let! (sym $_)) args ...)
|
@@ -40,7 +38,7 @@ module RuboCop
|
|
40
38
|
return unless example_group?(node)
|
41
39
|
|
42
40
|
unused_let_bang(node) do |let|
|
43
|
-
add_offense(let
|
41
|
+
add_offense(let)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
@@ -26,8 +26,7 @@ module RuboCop
|
|
26
26
|
return unless example_group?(node)
|
27
27
|
return if node.send_node.arguments?
|
28
28
|
|
29
|
-
add_offense(node,
|
30
|
-
message: format(MSG, method: node.method_name))
|
29
|
+
add_offense(node, message: format(MSG, method: node.method_name))
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
@@ -50,20 +50,20 @@ module RuboCop
|
|
50
50
|
|
51
51
|
MSG = 'Example has too many expectations [%<total>d/%<max>d].'
|
52
52
|
|
53
|
-
def_node_search :
|
54
|
-
def_node_search :
|
53
|
+
def_node_search :with_aggregate_failures?, '(sym :aggregate_failures)'
|
54
|
+
def_node_search :disabled_aggregate_failures?, <<-PATTERN
|
55
55
|
(pair (sym :aggregate_failures) (false))
|
56
56
|
PATTERN
|
57
57
|
|
58
58
|
def_node_matcher :expect?, Expectations::ALL.send_pattern
|
59
|
-
def_node_matcher :
|
59
|
+
def_node_matcher :aggregate_failures_block?, <<-PATTERN
|
60
60
|
(block (send _ :aggregate_failures ...) ...)
|
61
61
|
PATTERN
|
62
62
|
|
63
63
|
def on_block(node)
|
64
64
|
return unless example?(node)
|
65
65
|
|
66
|
-
return if
|
66
|
+
return if example_with_aggregate_failures?(node)
|
67
67
|
|
68
68
|
expectations_count = to_enum(:find_expectation, node).count
|
69
69
|
|
@@ -76,19 +76,40 @@ module RuboCop
|
|
76
76
|
|
77
77
|
private
|
78
78
|
|
79
|
-
def
|
80
|
-
|
79
|
+
def example_with_aggregate_failures?(example_node)
|
80
|
+
node_with_aggregate_failures = find_aggregate_failures(example_node)
|
81
|
+
return false unless node_with_aggregate_failures
|
81
82
|
|
82
|
-
(
|
83
|
-
|
84
|
-
|
83
|
+
aggregate_failures?(node_with_aggregate_failures)
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_aggregate_failures(example_node)
|
87
|
+
example_node.send_node.each_ancestor(:block)
|
88
|
+
.find { |block_node| aggregate_failures_present?(block_node) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def aggregate_failures_present?(node)
|
92
|
+
metadata(node)&.any?(&method(:with_aggregate_failures?))
|
93
|
+
end
|
94
|
+
|
95
|
+
def aggregate_failures?(example_or_group_node)
|
96
|
+
metadata(example_or_group_node)&.any? do |metadata|
|
97
|
+
with_aggregate_failures?(metadata) &&
|
98
|
+
!disabled_aggregate_failures?(metadata)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def metadata(example_or_group_node)
|
103
|
+
RuboCop::RSpec::Example
|
104
|
+
.new(example_or_group_node)
|
105
|
+
.metadata
|
85
106
|
end
|
86
107
|
|
87
108
|
def find_expectation(node, &block)
|
88
|
-
yield if expect?(node) ||
|
109
|
+
yield if expect?(node) || aggregate_failures_block?(node)
|
89
110
|
|
90
111
|
# do not search inside of aggregate_failures block
|
91
|
-
return if
|
112
|
+
return if aggregate_failures_block?(node)
|
92
113
|
|
93
114
|
node.each_child_node do |child|
|
94
115
|
find_expectation(child, &block)
|
@@ -98,7 +119,6 @@ module RuboCop
|
|
98
119
|
def flag_example(node, expectation_count:)
|
99
120
|
add_offense(
|
100
121
|
node.send_node,
|
101
|
-
location: :expression,
|
102
122
|
message: format(
|
103
123
|
MSG,
|
104
124
|
total: expectation_count,
|
@@ -110,10 +130,6 @@ module RuboCop
|
|
110
130
|
def max_expectations
|
111
131
|
Integer(cop_config.fetch('Max', 1))
|
112
132
|
end
|
113
|
-
|
114
|
-
def aggregated_failures_by_default?
|
115
|
-
cop_config.fetch('AggregateFailuresByDefault', false)
|
116
|
-
end
|
117
133
|
end
|
118
134
|
end
|
119
135
|
end
|
@@ -17,7 +17,6 @@ module RuboCop
|
|
17
17
|
predicate_in_actual?(node) do |predicate|
|
18
18
|
add_offense(
|
19
19
|
node,
|
20
|
-
location: :expression,
|
21
20
|
message: message_inflected(predicate)
|
22
21
|
)
|
23
22
|
end
|
@@ -143,7 +142,6 @@ module RuboCop
|
|
143
142
|
predicate_matcher_block?(node) do |_actual, matcher|
|
144
143
|
add_offense(
|
145
144
|
node,
|
146
|
-
location: :expression,
|
147
145
|
message: message_explicit(matcher)
|
148
146
|
)
|
149
147
|
ignore_node(node.children.first)
|
@@ -155,7 +153,6 @@ module RuboCop
|
|
155
153
|
predicate_matcher?(node) do |_actual, matcher|
|
156
154
|
add_offense(
|
157
155
|
node,
|
158
|
-
location: :expression,
|
159
156
|
message: message_explicit(matcher)
|
160
157
|
)
|
161
158
|
end
|