rubocop-rspec 1.7.0 → 3.0.2
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 +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
|