rubocop-rspec 1.7.0 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +955 -79
- data/CODE_OF_CONDUCT.md +17 -0
- data/MIT-LICENSE.md +1 -2
- data/README.md +35 -35
- data/config/default.yml +940 -52
- data/config/obsoletion.yml +30 -0
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
- data/lib/rubocop/cop/rspec/around_block.rb +97 -0
- data/lib/rubocop/cop/rspec/base.rb +26 -0
- data/lib/rubocop/cop/rspec/be.rb +39 -0
- data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
- data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
- data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
- data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
- data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
- data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
- data/lib/rubocop/cop/rspec/class_check.rb +101 -0
- data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
- data/lib/rubocop/cop/rspec/context_method.rb +57 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
- data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
- data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
- data/lib/rubocop/cop/rspec/described_class.rb +181 -34
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
- data/lib/rubocop/cop/rspec/dialect.rb +84 -0
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
- data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
- data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_length.rb +38 -20
- data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
- data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
- data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
- data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
- data/lib/rubocop/cop/rspec/focus.rb +79 -25
- data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
- data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
- data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
- data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
- data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
- data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
- data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
- data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
- data/lib/rubocop/cop/rspec/match_array.rb +59 -0
- data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
- data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
- data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
- data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
- data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
- data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
- data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
- data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
- data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
- data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
- data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
- data/lib/rubocop/cop/rspec/pending.rb +80 -0
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
- data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
- data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
- data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
- data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
- data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
- data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
- data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
- data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
- data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
- data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
- data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
- data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
- data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
- data/lib/rubocop/cop/rspec/yield.rb +82 -0
- data/lib/rubocop/cop/rspec_cops.rb +112 -0
- data/lib/rubocop/rspec/align_let_brace.rb +63 -0
- data/lib/rubocop/rspec/concept.rb +33 -0
- data/lib/rubocop/rspec/config_formatter.rb +27 -4
- data/lib/rubocop/rspec/cop/generator.rb +25 -0
- data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
- data/lib/rubocop/rspec/description_extractor.rb +60 -18
- data/lib/rubocop/rspec/example.rb +37 -0
- data/lib/rubocop/rspec/example_group.rb +67 -0
- data/lib/rubocop/rspec/hook.rb +79 -0
- data/lib/rubocop/rspec/inject.rb +3 -1
- data/lib/rubocop/rspec/language.rb +184 -41
- data/lib/rubocop/rspec/node.rb +19 -0
- data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +61 -19
- data/lib/rubocop/rspec.rb +6 -2
- data/lib/rubocop-rspec.rb +45 -34
- metadata +130 -195
- data/Gemfile +0 -13
- data/Rakefile +0 -48
- data/lib/rubocop/cop/rspec/file_path.rb +0 -83
- data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
- data/lib/rubocop/rspec/spec_only.rb +0 -61
- data/lib/rubocop/rspec/top_level_describe.rb +0 -61
- data/lib/rubocop/rspec/util.rb +0 -19
- data/rubocop-rspec.gemspec +0 -42
- data/spec/expect_violation/expectation_spec.rb +0 -85
- data/spec/project/changelog_spec.rb +0 -81
- data/spec/project/default_config_spec.rb +0 -52
- data/spec/project/project_requires_spec.rb +0 -8
- data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
- data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
- data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
- data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
- data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
- data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
- data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
- data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
- data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
- data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
- data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
- data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
- data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
- data/spec/rubocop/rspec/util/one_spec.rb +0 -21
- data/spec/rubocop/rspec/wording_spec.rb +0 -44
- data/spec/shared/rspec_only_cop_behavior.rb +0 -68
- data/spec/spec_helper.rb +0 -41
- data/spec/support/expect_violation.rb +0 -166
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
# Checks for multiple top
|
6
|
+
# Checks for multiple top-level example groups.
|
7
7
|
#
|
8
8
|
# Multiple descriptions for the same class or module should either
|
9
9
|
# be nested or separated into different test files.
|
@@ -15,25 +15,27 @@ module RuboCop
|
|
15
15
|
# describe MyClass, '.do_something_else' do
|
16
16
|
# end
|
17
17
|
#
|
18
|
-
# #good
|
19
|
-
# describe MyClass
|
18
|
+
# # good
|
19
|
+
# describe MyClass do
|
20
20
|
# describe '.do_something' do
|
21
21
|
# end
|
22
22
|
# describe '.do_something_else' do
|
23
23
|
# end
|
24
24
|
# end
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
#
|
26
|
+
class MultipleDescribes < Base
|
27
|
+
include TopLevelGroup
|
28
|
+
|
29
|
+
MSG = 'Do not use multiple top-level example groups - try to nest them.'
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
def on_top_level_group(node)
|
32
|
+
top_level_example_groups =
|
33
|
+
top_level_groups.select { |group| example_group?(group) }
|
31
34
|
|
32
|
-
|
33
|
-
return
|
34
|
-
return unless top_level_nodes.first.equal?(node)
|
35
|
+
return if top_level_example_groups.one?
|
36
|
+
return unless top_level_example_groups.first.equal?(node)
|
35
37
|
|
36
|
-
add_offense(node
|
38
|
+
add_offense(node.send_node)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
@@ -11,7 +11,6 @@ module RuboCop
|
|
11
11
|
# and works with `--auto-gen-config`.
|
12
12
|
#
|
13
13
|
# @example
|
14
|
-
#
|
15
14
|
# # bad
|
16
15
|
# describe UserCreator do
|
17
16
|
# it 'builds a user' do
|
@@ -26,18 +25,31 @@ module RuboCop
|
|
26
25
|
# expect(user.name).to eq("John")
|
27
26
|
# end
|
28
27
|
#
|
29
|
-
# it 'sets the users age'
|
28
|
+
# it 'sets the users age' do
|
30
29
|
# expect(user.age).to eq(22)
|
31
30
|
# end
|
32
31
|
# end
|
33
32
|
#
|
34
|
-
# @example
|
33
|
+
# @example `aggregate_failures: true` (default)
|
34
|
+
# # good - the cop ignores when RSpec aggregates failures
|
35
|
+
# describe UserCreator do
|
36
|
+
# it 'builds a user', :aggregate_failures do
|
37
|
+
# expect(user.name).to eq("John")
|
38
|
+
# expect(user.age).to eq(22)
|
39
|
+
# end
|
40
|
+
# end
|
35
41
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
42
|
+
# @example `aggregate_failures: false`
|
43
|
+
# # Detected as an offense
|
44
|
+
# describe UserCreator do
|
45
|
+
# it 'builds a user', aggregate_failures: false do
|
46
|
+
# expect(user.name).to eq("John")
|
47
|
+
# expect(user.age).to eq(22)
|
48
|
+
# end
|
49
|
+
# end
|
39
50
|
#
|
40
|
-
#
|
51
|
+
# @example `Max: 1` (default)
|
52
|
+
# # bad
|
41
53
|
# describe UserCreator do
|
42
54
|
# it 'builds a user' do
|
43
55
|
# expect(user.name).to eq("John")
|
@@ -45,43 +57,91 @@ module RuboCop
|
|
45
57
|
# end
|
46
58
|
# end
|
47
59
|
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
60
|
+
# @example `Max: 2`
|
61
|
+
# # good
|
62
|
+
# describe UserCreator do
|
63
|
+
# it 'builds a user' do
|
64
|
+
# expect(user.name).to eq("John")
|
65
|
+
# expect(user.age).to eq(22)
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
class MultipleExpectations < Base
|
70
|
+
include ConfigurableMax
|
71
|
+
|
72
|
+
MSG = 'Example has too many expectations [%<total>d/%<max>d].'
|
52
73
|
|
53
|
-
|
74
|
+
ANYTHING = ->(_node) { true }
|
75
|
+
TRUE_NODE = lambda(&:true_type?)
|
54
76
|
|
55
|
-
|
56
|
-
|
77
|
+
# @!method aggregate_failures?(node)
|
78
|
+
def_node_matcher :aggregate_failures?, <<~PATTERN
|
79
|
+
(block {
|
80
|
+
(send _ _ <(sym :aggregate_failures) ...>)
|
81
|
+
(send _ _ ... (hash <(pair (sym :aggregate_failures) %1) ...>))
|
82
|
+
} ...)
|
57
83
|
PATTERN
|
58
84
|
|
59
|
-
|
85
|
+
# @!method expect?(node)
|
86
|
+
def_node_matcher :expect?, '(send nil? #Expectations.all ...)'
|
87
|
+
|
88
|
+
# @!method aggregate_failures_block?(node)
|
89
|
+
def_node_matcher :aggregate_failures_block?, <<~PATTERN
|
90
|
+
(block (send nil? :aggregate_failures ...) ...)
|
91
|
+
PATTERN
|
60
92
|
|
61
|
-
def on_block(node)
|
62
|
-
return unless example?(node)
|
93
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
94
|
+
return unless example?(node)
|
63
95
|
|
64
|
-
return if
|
96
|
+
return if example_with_aggregate_failures?(node)
|
65
97
|
|
66
|
-
|
98
|
+
expectations_count = to_enum(:find_expectation, node).count
|
67
99
|
|
68
|
-
|
100
|
+
return if expectations_count <= max_expectations
|
101
|
+
|
102
|
+
self.max = expectations_count
|
103
|
+
|
104
|
+
flag_example(node, expectation_count: expectations_count)
|
69
105
|
end
|
70
106
|
|
71
107
|
private
|
72
108
|
|
73
|
-
def
|
74
|
-
|
109
|
+
def example_with_aggregate_failures?(example_node)
|
110
|
+
node_with_aggregate_failures = find_aggregate_failures(example_node)
|
111
|
+
return false unless node_with_aggregate_failures
|
75
112
|
|
113
|
+
aggregate_failures?(node_with_aggregate_failures, TRUE_NODE)
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_aggregate_failures(example_node)
|
117
|
+
example_node.send_node.each_ancestor(:block)
|
118
|
+
.find { |block_node| aggregate_failures?(block_node, ANYTHING) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def find_expectation(node, &block)
|
122
|
+
yield if expect?(node) || aggregate_failures_block?(node)
|
123
|
+
|
124
|
+
# do not search inside of aggregate_failures block
|
125
|
+
return if aggregate_failures_block?(node)
|
126
|
+
|
127
|
+
node.each_child_node do |child|
|
128
|
+
find_expectation(child, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def flag_example(node, expectation_count:)
|
76
133
|
add_offense(
|
77
|
-
|
78
|
-
:
|
79
|
-
|
134
|
+
node.send_node,
|
135
|
+
message: format(
|
136
|
+
MSG,
|
137
|
+
total: expectation_count,
|
138
|
+
max: max_expectations
|
139
|
+
)
|
80
140
|
)
|
81
141
|
end
|
82
142
|
|
83
143
|
def max_expectations
|
84
|
-
Integer(cop_config.fetch(
|
144
|
+
Integer(cop_config.fetch('Max', 1))
|
85
145
|
end
|
86
146
|
end
|
87
147
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks if example groups contain too many `let` and `subject` calls.
|
7
|
+
#
|
8
|
+
# This cop is configurable using the `Max` option and the `AllowSubject`
|
9
|
+
# which will configure the cop to only register offenses on calls to
|
10
|
+
# `let` and not calls to `subject`.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# describe MyClass do
|
15
|
+
# let(:foo) { [] }
|
16
|
+
# let(:bar) { [] }
|
17
|
+
# let!(:baz) { [] }
|
18
|
+
# let(:qux) { [] }
|
19
|
+
# let(:quux) { [] }
|
20
|
+
# let(:quuz) { {} }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# describe MyClass do
|
24
|
+
# let(:foo) { [] }
|
25
|
+
# let(:bar) { [] }
|
26
|
+
# let!(:baz) { [] }
|
27
|
+
#
|
28
|
+
# context 'when stuff' do
|
29
|
+
# let(:qux) { [] }
|
30
|
+
# let(:quux) { [] }
|
31
|
+
# let(:quuz) { {} }
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# describe MyClass do
|
37
|
+
# let(:bar) { [] }
|
38
|
+
# let!(:baz) { [] }
|
39
|
+
# let(:qux) { [] }
|
40
|
+
# let(:quux) { [] }
|
41
|
+
# let(:quuz) { {} }
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# describe MyClass do
|
45
|
+
# context 'when stuff' do
|
46
|
+
# let(:foo) { [] }
|
47
|
+
# let(:bar) { [] }
|
48
|
+
# let!(:booger) { [] }
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# context 'when other stuff' do
|
52
|
+
# let(:qux) { [] }
|
53
|
+
# let(:quux) { [] }
|
54
|
+
# let(:quuz) { {} }
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# @example when disabling AllowSubject configuration
|
59
|
+
# # rubocop.yml
|
60
|
+
# # RSpec/MultipleMemoizedHelpers:
|
61
|
+
# # AllowSubject: false
|
62
|
+
#
|
63
|
+
# # bad - `subject` counts towards memoized helpers
|
64
|
+
# describe MyClass do
|
65
|
+
# subject { {} }
|
66
|
+
# let(:foo) { [] }
|
67
|
+
# let(:bar) { [] }
|
68
|
+
# let!(:baz) { [] }
|
69
|
+
# let(:qux) { [] }
|
70
|
+
# let(:quux) { [] }
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @example with Max configuration
|
74
|
+
# # rubocop.yml
|
75
|
+
# # RSpec/MultipleMemoizedHelpers:
|
76
|
+
# # Max: 1
|
77
|
+
#
|
78
|
+
# # bad
|
79
|
+
# describe MyClass do
|
80
|
+
# let(:foo) { [] }
|
81
|
+
# let(:bar) { [] }
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
class MultipleMemoizedHelpers < Base
|
85
|
+
include ConfigurableMax
|
86
|
+
include Variable
|
87
|
+
|
88
|
+
MSG = 'Example group has too many memoized helpers [%<count>d/%<max>d]'
|
89
|
+
|
90
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
91
|
+
return unless spec_group?(node)
|
92
|
+
|
93
|
+
count = all_helpers(node).uniq.count
|
94
|
+
|
95
|
+
return if count <= max
|
96
|
+
|
97
|
+
self.max = count
|
98
|
+
add_offense(node, message: format(MSG, count: count, max: max))
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_new_investigation
|
102
|
+
super
|
103
|
+
@example_group_memoized_helpers = {}
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
attr_reader :example_group_memoized_helpers
|
109
|
+
|
110
|
+
def all_helpers(node)
|
111
|
+
helpers(node) +
|
112
|
+
node.each_ancestor(:block).flat_map { |ancestor| helpers(ancestor) }
|
113
|
+
end
|
114
|
+
|
115
|
+
def helpers(node)
|
116
|
+
@example_group_memoized_helpers[node] ||=
|
117
|
+
variable_nodes(node).map do |variable_node|
|
118
|
+
if variable_node.block_type?
|
119
|
+
variable_definition?(variable_node.send_node)
|
120
|
+
else # block-pass (`let(:foo, &bar)`)
|
121
|
+
variable_definition?(variable_node)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def variable_nodes(node)
|
127
|
+
example_group = RuboCop::RSpec::ExampleGroup.new(node)
|
128
|
+
|
129
|
+
if allow_subject?
|
130
|
+
example_group.lets
|
131
|
+
else
|
132
|
+
example_group.lets + example_group.subjects
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def max
|
137
|
+
cop_config['Max']
|
138
|
+
end
|
139
|
+
|
140
|
+
def allow_subject?
|
141
|
+
cop_config['AllowSubject']
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks if an example group defines `subject` multiple times.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe Foo do
|
11
|
+
# subject(:user) { User.new }
|
12
|
+
# subject(:post) { Post.new }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# describe Foo do
|
17
|
+
# let(:user) { User.new }
|
18
|
+
# subject(:post) { Post.new }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # bad (does not support autocorrection)
|
22
|
+
# describe Foo do
|
23
|
+
# subject!(:user) { User.new }
|
24
|
+
# subject!(:post) { Post.new }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# describe Foo do
|
29
|
+
# before do
|
30
|
+
# User.new
|
31
|
+
# Post.new
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# This cop does not support autocorrection in some cases.
|
36
|
+
# The autocorrect behavior for this cop depends on the type of
|
37
|
+
# duplication:
|
38
|
+
#
|
39
|
+
# - If multiple named subjects are defined then this probably indicates
|
40
|
+
# that the overwritten subjects (all subjects except the last
|
41
|
+
# definition) are effectively being used to define helpers. In this
|
42
|
+
# case they are replaced with `let`.
|
43
|
+
#
|
44
|
+
# - If multiple unnamed subjects are defined though then this can *only*
|
45
|
+
# be dead code and we remove the overwritten subject definitions.
|
46
|
+
#
|
47
|
+
# - If subjects are defined with `subject!` then we don't autocorrect.
|
48
|
+
# This is enough of an edge case that people can just move this to
|
49
|
+
# a `before` hook on their own
|
50
|
+
#
|
51
|
+
class MultipleSubjects < Base
|
52
|
+
extend AutoCorrector
|
53
|
+
include RangeHelp
|
54
|
+
|
55
|
+
MSG = 'Do not set more than one subject per example group'
|
56
|
+
|
57
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
58
|
+
return unless example_group?(node)
|
59
|
+
|
60
|
+
subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
|
61
|
+
|
62
|
+
subjects[0...-1].each do |subject|
|
63
|
+
add_offense(subject) do |corrector|
|
64
|
+
autocorrect(corrector, subject)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def autocorrect(corrector, subject)
|
72
|
+
return unless subject.method_name.equal?(:subject) # Ignore `subject!`
|
73
|
+
|
74
|
+
if named_subject?(subject)
|
75
|
+
rename_autocorrect(corrector, subject)
|
76
|
+
else
|
77
|
+
remove_autocorrect(corrector, subject)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def named_subject?(node)
|
82
|
+
node.send_node.arguments?
|
83
|
+
end
|
84
|
+
|
85
|
+
def rename_autocorrect(corrector, node)
|
86
|
+
corrector.replace(node.send_node.loc.selector, 'let')
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_autocorrect(corrector, node)
|
90
|
+
range = range_by_whole_lines(node.source_range,
|
91
|
+
include_final_newline: true)
|
92
|
+
corrector.remove(range)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -6,13 +6,17 @@ module RuboCop
|
|
6
6
|
# Checks for explicitly referenced test subjects.
|
7
7
|
#
|
8
8
|
# RSpec lets you declare an "implicit subject" using `subject { ... }`
|
9
|
-
# which allows for tests like `it {
|
10
|
-
# reference your test subject you should explicitly
|
11
|
-
# `subject(:your_subject_name) { ... }`. Your test subjects
|
12
|
-
# the most important object in your tests so they deserve
|
13
|
-
# name.
|
9
|
+
# which allows for tests like `it { is_expected.to be_valid }`.
|
10
|
+
# If you need to reference your test subject you should explicitly
|
11
|
+
# name it using `subject(:your_subject_name) { ... }`. Your test subjects
|
12
|
+
# should be the most important object in your tests so they deserve
|
13
|
+
# a descriptive name.
|
14
14
|
#
|
15
|
-
#
|
15
|
+
# This cop can be configured in your configuration using `EnforcedStyle`,
|
16
|
+
# and `IgnoreSharedExamples` which will not report offenses for implicit
|
17
|
+
# subjects in shared example groups.
|
18
|
+
#
|
19
|
+
# @example `EnforcedStyle: always` (default)
|
16
20
|
# # bad
|
17
21
|
# RSpec.describe User do
|
18
22
|
# subject { described_class.new }
|
@@ -23,7 +27,7 @@ module RuboCop
|
|
23
27
|
# end
|
24
28
|
#
|
25
29
|
# # good
|
26
|
-
# RSpec.describe
|
30
|
+
# RSpec.describe User do
|
27
31
|
# subject(:user) { described_class.new }
|
28
32
|
#
|
29
33
|
# it 'is valid' do
|
@@ -32,44 +36,120 @@ module RuboCop
|
|
32
36
|
# end
|
33
37
|
#
|
34
38
|
# # also good
|
35
|
-
# RSpec.describe
|
39
|
+
# RSpec.describe User do
|
40
|
+
# subject(:user) { described_class.new }
|
41
|
+
#
|
42
|
+
# it { is_expected.to be_valid }
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @example `EnforcedStyle: named_only`
|
46
|
+
# # bad
|
47
|
+
# RSpec.describe User do
|
48
|
+
# subject(:user) { described_class.new }
|
49
|
+
#
|
50
|
+
# it 'is valid' do
|
51
|
+
# expect(subject.valid?).to be(true)
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# # good
|
56
|
+
# RSpec.describe User do
|
36
57
|
# subject(:user) { described_class.new }
|
37
58
|
#
|
38
|
-
# it
|
59
|
+
# it 'is valid' do
|
60
|
+
# expect(user.valid?).to be(true)
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# # also good
|
65
|
+
# RSpec.describe User do
|
66
|
+
# subject { described_class.new }
|
67
|
+
#
|
68
|
+
# it { is_expected.to be_valid }
|
39
69
|
# end
|
40
|
-
|
41
|
-
|
70
|
+
#
|
71
|
+
# # acceptable
|
72
|
+
# RSpec.describe User do
|
73
|
+
# subject { described_class.new }
|
74
|
+
#
|
75
|
+
# it 'is valid' do
|
76
|
+
# expect(subject.valid?).to be(true)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
class NamedSubject < Base
|
80
|
+
include ConfigurableEnforcedStyle
|
42
81
|
|
43
|
-
MSG = 'Name your test subject if '
|
44
|
-
'you need to reference it explicitly.'.freeze
|
82
|
+
MSG = 'Name your test subject if you need to reference it explicitly.'
|
45
83
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
...)
|
84
|
+
# @!method example_or_hook_block?(node)
|
85
|
+
def_node_matcher :example_or_hook_block?, <<~PATTERN
|
86
|
+
(block (send nil? {#Examples.all #Hooks.all} ...) ...)
|
50
87
|
PATTERN
|
51
88
|
|
52
|
-
|
89
|
+
# @!method shared_example?(node)
|
90
|
+
def_node_matcher :shared_example?, <<~PATTERN
|
91
|
+
(block (send #rspec? #SharedGroups.examples ...) ...)
|
92
|
+
PATTERN
|
93
|
+
|
94
|
+
# @!method subject_usage(node)
|
95
|
+
def_node_search :subject_usage, '$(send nil? :subject)'
|
53
96
|
|
54
|
-
def on_block(node)
|
55
|
-
|
97
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
98
|
+
if !example_or_hook_block?(node) || ignored_shared_example?(node)
|
99
|
+
return
|
100
|
+
end
|
56
101
|
|
57
102
|
subject_usage(node) do |subject_node|
|
58
|
-
|
103
|
+
check_explicit_subject(subject_node)
|
59
104
|
end
|
60
105
|
end
|
61
106
|
|
62
107
|
private
|
63
108
|
|
64
|
-
def
|
65
|
-
return unless
|
66
|
-
|
67
|
-
unnamed_subject(node, &block)
|
109
|
+
def ignored_shared_example?(node)
|
110
|
+
return false unless cop_config['IgnoreSharedExamples']
|
68
111
|
|
69
|
-
node.
|
70
|
-
|
112
|
+
node.each_ancestor(:block).any? do |ancestor|
|
113
|
+
shared_example?(ancestor)
|
71
114
|
end
|
72
115
|
end
|
116
|
+
|
117
|
+
def check_explicit_subject(node)
|
118
|
+
return if allow_explicit_subject?(node)
|
119
|
+
|
120
|
+
add_offense(node.loc.selector)
|
121
|
+
end
|
122
|
+
|
123
|
+
def allow_explicit_subject?(node)
|
124
|
+
!always? && !named_only?(node)
|
125
|
+
end
|
126
|
+
|
127
|
+
def always?
|
128
|
+
style == :always
|
129
|
+
end
|
130
|
+
|
131
|
+
def named_only?(node)
|
132
|
+
style == :named_only &&
|
133
|
+
subject_definition_is_named?(node)
|
134
|
+
end
|
135
|
+
|
136
|
+
def subject_definition_is_named?(node)
|
137
|
+
subject = nearest_subject(node)
|
138
|
+
|
139
|
+
subject&.send_node&.arguments?
|
140
|
+
end
|
141
|
+
|
142
|
+
def nearest_subject(node)
|
143
|
+
node
|
144
|
+
.each_ancestor(:block)
|
145
|
+
.lazy
|
146
|
+
.map { |block_node| find_subject(block_node) }
|
147
|
+
.find(&:itself)
|
148
|
+
end
|
149
|
+
|
150
|
+
def find_subject(block_node)
|
151
|
+
block_node.body&.child_nodes&.find { |send_node| subject?(send_node) }
|
152
|
+
end
|
73
153
|
end
|
74
154
|
end
|
75
155
|
end
|