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
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for redundant predicate matcher.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# expect(foo).to be_exist(bar)
|
11
|
+
# expect(foo).not_to be_include(bar)
|
12
|
+
# expect(foo).to be_all(bar)
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# expect(foo).to exist(bar)
|
16
|
+
# expect(foo).not_to include(bar)
|
17
|
+
# expect(foo).to all be(bar)
|
18
|
+
#
|
19
|
+
class RedundantPredicateMatcher < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
|
22
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
23
|
+
RESTRICT_ON_SEND =
|
24
|
+
%i[be_all be_cover be_end_with be_eql be_equal
|
25
|
+
be_exist be_exists be_include be_match
|
26
|
+
be_respond_to be_start_with].freeze
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
return if node.parent.block_type? || node.arguments.empty?
|
30
|
+
return unless replaceable_arguments?(node)
|
31
|
+
|
32
|
+
method_name = node.method_name.to_s
|
33
|
+
replaced = replaced_method_name(method_name)
|
34
|
+
add_offense(node, message: message(method_name,
|
35
|
+
replaced)) do |corrector|
|
36
|
+
unless node.method?(:be_all)
|
37
|
+
corrector.replace(node.loc.selector, replaced)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def message(bad_method, good_method)
|
45
|
+
format(MSG, bad: bad_method, good: good_method)
|
46
|
+
end
|
47
|
+
|
48
|
+
def replaceable_arguments?(node)
|
49
|
+
if node.method?(:be_all)
|
50
|
+
node.first_argument.send_type?
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def replaced_method_name(method_name)
|
57
|
+
name = method_name.to_s.delete_prefix('be_')
|
58
|
+
if name == 'exists'
|
59
|
+
'exist'
|
60
|
+
else
|
61
|
+
name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that `remove_const` is not used in specs.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it 'does something' do
|
11
|
+
# Object.send(:remove_const, :SomeConstant)
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# before do
|
15
|
+
# SomeClass.send(:remove_const, :SomeConstant)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class RemoveConst < Base
|
19
|
+
include RuboCop::RSpec::Language
|
20
|
+
|
21
|
+
MSG = 'Do not use remove_const in specs. ' \
|
22
|
+
'Consider using e.g. `stub_const`.'
|
23
|
+
RESTRICT_ON_SEND = %i[send __send__].freeze
|
24
|
+
|
25
|
+
# @!method remove_const(node)
|
26
|
+
def_node_matcher :remove_const, <<~PATTERN
|
27
|
+
(send _ {:send | :__send__} (sym :remove_const) _)
|
28
|
+
PATTERN
|
29
|
+
|
30
|
+
# Check for offenses
|
31
|
+
def on_send(node)
|
32
|
+
remove_const(node) do
|
33
|
+
add_offense(node)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated description strings in example groups.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# RSpec.describe User do
|
11
|
+
# it 'is valid' do
|
12
|
+
# # ...
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# it 'is valid' do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# RSpec.describe User do
|
22
|
+
# it 'is valid when first and last name are present' do
|
23
|
+
# # ...
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# it 'is valid when last name only is present' do
|
27
|
+
# # ...
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# RSpec.describe User do
|
33
|
+
# it 'is valid' do
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# it 'is valid', :flag do
|
38
|
+
# # ...
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
class RepeatedDescription < Base
|
43
|
+
MSG = "Don't repeat descriptions within an example group."
|
44
|
+
|
45
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
46
|
+
return unless example_group?(node)
|
47
|
+
|
48
|
+
repeated_descriptions(node).each do |description|
|
49
|
+
add_offense(description)
|
50
|
+
end
|
51
|
+
|
52
|
+
repeated_its(node).each do |its|
|
53
|
+
add_offense(its)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Select examples in the current scope with repeated description strings
|
60
|
+
def repeated_descriptions(node)
|
61
|
+
grouped_examples =
|
62
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
63
|
+
.examples
|
64
|
+
.reject { |n| n.definition.method?(:its) }
|
65
|
+
.group_by { |example| example_signature(example) }
|
66
|
+
|
67
|
+
grouped_examples
|
68
|
+
.select { |signatures, group| signatures.any? && group.size > 1 }
|
69
|
+
.values
|
70
|
+
.flatten
|
71
|
+
.map(&:definition)
|
72
|
+
end
|
73
|
+
|
74
|
+
def repeated_its(node)
|
75
|
+
grouped_its =
|
76
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
77
|
+
.examples
|
78
|
+
.select { |n| n.definition.method?(:its) }
|
79
|
+
.group_by { |example| its_signature(example) }
|
80
|
+
|
81
|
+
grouped_its
|
82
|
+
.select { |signatures, group| signatures.any? && group.size > 1 }
|
83
|
+
.values
|
84
|
+
.flatten
|
85
|
+
.map(&:to_node)
|
86
|
+
end
|
87
|
+
|
88
|
+
def example_signature(example)
|
89
|
+
[example.metadata, example.doc_string]
|
90
|
+
end
|
91
|
+
|
92
|
+
def its_signature(example)
|
93
|
+
[example.doc_string, example]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated examples within example groups.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# it 'is valid' do
|
11
|
+
# expect(user).to be_valid
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# it 'validates the user' do
|
15
|
+
# expect(user).to be_valid
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class RepeatedExample < Base
|
19
|
+
MSG = "Don't repeat examples within an example group."
|
20
|
+
|
21
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
22
|
+
return unless example_group?(node)
|
23
|
+
|
24
|
+
repeated_examples(node).each do |repeated_example|
|
25
|
+
add_offense(repeated_example)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def repeated_examples(node)
|
32
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
33
|
+
.examples
|
34
|
+
.group_by { |example| example_signature(example) }
|
35
|
+
.values
|
36
|
+
.reject(&:one?)
|
37
|
+
.flatten
|
38
|
+
.map(&:to_node)
|
39
|
+
end
|
40
|
+
|
41
|
+
def example_signature(example)
|
42
|
+
key_parts = [example.metadata, example.implementation]
|
43
|
+
|
44
|
+
if example.definition.method?(:its)
|
45
|
+
key_parts << example.definition.arguments
|
46
|
+
end
|
47
|
+
|
48
|
+
key_parts
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated describe and context block body.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe 'cool feature x' do
|
11
|
+
# it { cool_predicate }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# describe 'cool feature y' do
|
15
|
+
# it { cool_predicate }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# describe 'cool feature' do
|
20
|
+
# it { cool_predicate }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# describe 'another cool feature' do
|
24
|
+
# it { another_predicate }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# context 'when case x', :tag do
|
29
|
+
# it { cool_predicate }
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# context 'when case y' do
|
33
|
+
# it { cool_predicate }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # good
|
37
|
+
# context Array do
|
38
|
+
# it { is_expected.to respond_to :each }
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# context Hash do
|
42
|
+
# it { is_expected.to respond_to :each }
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
class RepeatedExampleGroupBody < Base
|
46
|
+
include SkipOrPending
|
47
|
+
|
48
|
+
MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
|
49
|
+
|
50
|
+
# @!method several_example_groups?(node)
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
|
+
(begin <#example_group_with_body? #example_group_with_body? ...>)
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
# @!method metadata(node)
|
56
|
+
def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
|
57
|
+
|
58
|
+
# @!method body(node)
|
59
|
+
def_node_matcher :body, '(block _ args $...)'
|
60
|
+
|
61
|
+
# @!method const_arg(node)
|
62
|
+
def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
|
63
|
+
|
64
|
+
def on_begin(node)
|
65
|
+
return unless several_example_groups?(node)
|
66
|
+
|
67
|
+
repeated_group_bodies(node).each do |group, repeats|
|
68
|
+
add_offense(group, message: message(group, repeats))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def repeated_group_bodies(node)
|
75
|
+
node
|
76
|
+
.children
|
77
|
+
.select { |child| example_group_with_body?(child) }
|
78
|
+
.reject { |child| skip_or_pending_inside_block?(child) }
|
79
|
+
.group_by { |group| signature_keys(group) }
|
80
|
+
.values
|
81
|
+
.reject(&:one?)
|
82
|
+
.flat_map { |groups| add_repeated_lines(groups) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_repeated_lines(groups)
|
86
|
+
repeated_lines = groups.map(&:first_line)
|
87
|
+
groups.map { |group| [group, repeated_lines - [group.first_line]] }
|
88
|
+
end
|
89
|
+
|
90
|
+
def signature_keys(group)
|
91
|
+
[metadata(group), body(group), const_arg(group)]
|
92
|
+
end
|
93
|
+
|
94
|
+
def message(group, repeats)
|
95
|
+
format(MSG, group: group.method_name, loc: repeats)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated example group descriptions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe 'cool feature' do
|
11
|
+
# # example group
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# describe 'cool feature' do
|
15
|
+
# # example group
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # bad
|
19
|
+
# context 'when case x' do
|
20
|
+
# # example group
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# describe 'when case x' do
|
24
|
+
# # example group
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# describe 'cool feature' do
|
29
|
+
# # example group
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# describe 'another cool feature' do
|
33
|
+
# # example group
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # good
|
37
|
+
# context 'when case x' do
|
38
|
+
# # example group
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# context 'when another case' do
|
42
|
+
# # example group
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
class RepeatedExampleGroupDescription < Base
|
46
|
+
include SkipOrPending
|
47
|
+
|
48
|
+
MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
|
49
|
+
|
50
|
+
# @!method several_example_groups?(node)
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
|
+
(begin <#example_group? #example_group? ...>)
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
# @!method doc_string_and_metadata(node)
|
56
|
+
def_node_matcher :doc_string_and_metadata, <<~PATTERN
|
57
|
+
(block (send _ _ $_ $...) ...)
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
# @!method empty_description?(node)
|
61
|
+
def_node_matcher :empty_description?, '(block (send _ _) ...)'
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
return unless several_example_groups?(node)
|
65
|
+
|
66
|
+
repeated_group_descriptions(node).each do |group, repeats|
|
67
|
+
add_offense(group, message: message(group, repeats))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_group_descriptions(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| example_group?(child) }
|
77
|
+
.reject { |child| skip_or_pending_inside_block?(child) }
|
78
|
+
.reject { |child| empty_description?(child) }
|
79
|
+
.group_by { |group| doc_string_and_metadata(group) }
|
80
|
+
.values
|
81
|
+
.reject(&:one?)
|
82
|
+
.flat_map { |groups| add_repeated_lines(groups) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_repeated_lines(groups)
|
86
|
+
repeated_lines = groups.map(&:first_line)
|
87
|
+
groups.map { |group| [group, repeated_lines - [group.first_line]] }
|
88
|
+
end
|
89
|
+
|
90
|
+
def message(group, repeats)
|
91
|
+
format(MSG, group: group.method_name, loc: repeats)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated include of shared examples.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe 'foo' do
|
11
|
+
# include_examples 'cool stuff'
|
12
|
+
# include_examples 'cool stuff'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# describe 'foo' do
|
17
|
+
# it_behaves_like 'a cool', 'thing'
|
18
|
+
# it_behaves_like 'a cool', 'thing'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# context 'foo' do
|
23
|
+
# it_should_behave_like 'a duck'
|
24
|
+
# it_should_behave_like 'a duck'
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# describe 'foo' do
|
29
|
+
# include_examples 'cool stuff'
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# describe 'bar' do
|
33
|
+
# include_examples 'cool stuff'
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# # good
|
37
|
+
# describe 'foo' do
|
38
|
+
# it_behaves_like 'a cool', 'thing'
|
39
|
+
# it_behaves_like 'a cool', 'person'
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# # good
|
43
|
+
# context 'foo' do
|
44
|
+
# it_should_behave_like 'a duck'
|
45
|
+
# it_should_behave_like 'a goose'
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
class RepeatedIncludeExample < Base
|
49
|
+
MSG = 'Repeated include of shared_examples %<name>s ' \
|
50
|
+
'on line(s) %<repeat>s'
|
51
|
+
|
52
|
+
# @!method several_include_examples?(node)
|
53
|
+
def_node_matcher :several_include_examples?, <<~PATTERN
|
54
|
+
(begin <#include_examples? #include_examples? ...>)
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
# @!method include_examples?(node)
|
58
|
+
def_node_matcher :include_examples?,
|
59
|
+
'(send nil? #Includes.examples ...)'
|
60
|
+
|
61
|
+
# @!method shared_examples_name(node)
|
62
|
+
def_node_matcher :shared_examples_name,
|
63
|
+
'(send nil? #Includes.examples $_name ...)'
|
64
|
+
|
65
|
+
def on_begin(node)
|
66
|
+
return unless several_include_examples?(node)
|
67
|
+
|
68
|
+
repeated_include_examples(node).each do |item, repeats|
|
69
|
+
add_offense(item, message: message(item, repeats))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def repeated_include_examples(node)
|
76
|
+
node
|
77
|
+
.children
|
78
|
+
.select { |child| literal_include_examples?(child) }
|
79
|
+
.group_by { |child| signature_keys(child) }
|
80
|
+
.values
|
81
|
+
.reject(&:one?)
|
82
|
+
.flat_map { |items| add_repeated_lines(items) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def literal_include_examples?(node)
|
86
|
+
include_examples?(node) &&
|
87
|
+
node.arguments.all?(&:recursive_literal_or_const?)
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_repeated_lines(items)
|
91
|
+
repeated_lines = items.map(&:first_line)
|
92
|
+
items.map { |item| [item, repeated_lines - [item.first_line]] }
|
93
|
+
end
|
94
|
+
|
95
|
+
def signature_keys(item)
|
96
|
+
item.arguments
|
97
|
+
end
|
98
|
+
|
99
|
+
def message(item, repeats)
|
100
|
+
format(MSG, name: shared_examples_name(item).source, repeat: repeats)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for repeated calls to subject missing that it is memoized.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it do
|
11
|
+
# subject
|
12
|
+
# expect { subject }.to not_change { A.count }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# it do
|
16
|
+
# expect { subject }.to change { A.count }
|
17
|
+
# expect { subject }.to not_change { A.count }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# it do
|
22
|
+
# expect { my_method }.to change { A.count }
|
23
|
+
# expect { my_method }.to not_change { A.count }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # also good
|
27
|
+
# it do
|
28
|
+
# expect { subject.a }.to change { A.count }
|
29
|
+
# expect { subject.b }.to not_change { A.count }
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
class RepeatedSubjectCall < Base
|
33
|
+
include TopLevelGroup
|
34
|
+
|
35
|
+
MSG = 'Calls to subject are memoized, this block is misleading'
|
36
|
+
|
37
|
+
# @!method subject?(node)
|
38
|
+
# Find a named or unnamed subject definition
|
39
|
+
#
|
40
|
+
# @example anonymous subject
|
41
|
+
# subject?(parse('subject { foo }').ast) do |name|
|
42
|
+
# name # => :subject
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @example named subject
|
46
|
+
# subject?(parse('subject(:thing) { foo }').ast) do |name|
|
47
|
+
# name # => :thing
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @param node [RuboCop::AST::Node]
|
51
|
+
#
|
52
|
+
# @yield [Symbol] subject name
|
53
|
+
def_node_matcher :subject?, <<-PATTERN
|
54
|
+
(block
|
55
|
+
(send nil?
|
56
|
+
{ #Subjects.all (sym $_) | $#Subjects.all }
|
57
|
+
) args ...)
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
# @!method subject_calls(node, method_name)
|
61
|
+
def_node_search :subject_calls, <<~PATTERN
|
62
|
+
(send nil? %)
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
def on_top_level_group(node)
|
66
|
+
@subjects_by_node = detect_subjects_in_scope(node)
|
67
|
+
|
68
|
+
detect_offenses_in_block(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def detect_offense(subject_node)
|
74
|
+
return if subject_node.chained?
|
75
|
+
return if subject_node.parent.send_type?
|
76
|
+
return unless (block_node = expect_block(subject_node))
|
77
|
+
|
78
|
+
add_offense(block_node)
|
79
|
+
end
|
80
|
+
|
81
|
+
def expect_block(node)
|
82
|
+
node.each_ancestor(:block).find { |block| block.method?(:expect) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def detect_offenses_in_block(node, subject_names = [])
|
86
|
+
subject_names = [*subject_names, *@subjects_by_node[node]]
|
87
|
+
|
88
|
+
if example?(node)
|
89
|
+
return detect_offenses_in_example(node, subject_names)
|
90
|
+
end
|
91
|
+
|
92
|
+
node.each_child_node(:send, :def, :block, :begin) do |child|
|
93
|
+
detect_offenses_in_block(child, subject_names)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def detect_offenses_in_example(node, subject_names)
|
98
|
+
return unless node.body
|
99
|
+
|
100
|
+
subjects_used = Hash.new(false)
|
101
|
+
|
102
|
+
subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
|
103
|
+
if subjects_used[call.method_name]
|
104
|
+
detect_offense(call)
|
105
|
+
else
|
106
|
+
subjects_used[call.method_name] = true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def detect_subjects_in_scope(node)
|
112
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
113
|
+
subject?(child) do |name|
|
114
|
+
outer_example_group = child.each_ancestor(:block).find do |a|
|
115
|
+
example_group?(a)
|
116
|
+
end
|
117
|
+
|
118
|
+
(h[outer_example_group] ||= []) << name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|