rubocop-rspec 2.22.0 → 2.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +94 -9
- data/README.md +1 -1
- data/config/default.yml +126 -21
- data/lib/rubocop/cop/rspec/around_block.rb +3 -3
- data/lib/rubocop/cop/rspec/be.rb +1 -1
- data/lib/rubocop/cop/rspec/be_empty.rb +1 -0
- data/lib/rubocop/cop/rspec/be_eq.rb +1 -1
- data/lib/rubocop/cop/rspec/be_eql.rb +1 -1
- data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
- data/lib/rubocop/cop/rspec/before_after_all.rb +7 -13
- data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -2
- data/lib/rubocop/cop/rspec/change_by_zero.rb +30 -4
- data/lib/rubocop/cop/rspec/context_method.rb +2 -2
- data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
- data/lib/rubocop/cop/rspec/described_class.rb +33 -11
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_example_group.rb +4 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +2 -2
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_length.rb +11 -5
- data/lib/rubocop/cop/rspec/example_without_description.rb +11 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +11 -2
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
- data/lib/rubocop/cop/rspec/expect_actual.rb +7 -4
- data/lib/rubocop/cop/rspec/expect_change.rb +2 -2
- data/lib/rubocop/cop/rspec/expect_output.rb +1 -4
- data/lib/rubocop/cop/rspec/file_path.rb +6 -0
- data/lib/rubocop/cop/rspec/focus.rb +17 -2
- data/lib/rubocop/cop/rspec/hook_argument.rb +2 -2
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -1
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +2 -2
- data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
- data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -2
- data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
- data/lib/rubocop/cop/rspec/instance_spy.rb +2 -2
- data/lib/rubocop/cop/rspec/instance_variable.rb +3 -3
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -3
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +5 -1
- data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/message_expectation.rb +1 -2
- data/lib/rubocop/cop/rspec/message_spies.rb +0 -2
- data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +2 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +12 -7
- data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/pending.rb +12 -2
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +9 -9
- data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/rails/have_http_status.rb +34 -10
- data/lib/rubocop/cop/rspec/rails/http_status.rb +29 -18
- data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +314 -22
- data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
- data/lib/rubocop/cop/rspec/remove_const.rb +40 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +2 -2
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +124 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_examples.rb +66 -20
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +2 -3
- data/lib/rubocop/cop/rspec/sort_metadata.rb +2 -1
- 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 +1 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +4 -4
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -2
- data/lib/rubocop/cop/rspec/variable_definition.rb +4 -4
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +6 -6
- data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -2
- data/lib/rubocop/cop/rspec/void_expect.rb +4 -3
- data/lib/rubocop/cop/rspec_cops.rb +11 -0
- data/lib/rubocop/rspec/language.rb +8 -8
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +8 -0
- data/lib/rubocop-rspec.rb +1 -0
- metadata +20 -8
@@ -0,0 +1,40 @@
|
|
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
|
+
extend RuboCop::RSpec::Language::NodePattern
|
21
|
+
|
22
|
+
MSG = 'Do not use remove_const in specs. ' \
|
23
|
+
'Consider using e.g. `stub_const`.'
|
24
|
+
RESTRICT_ON_SEND = %i[send __send__].freeze
|
25
|
+
|
26
|
+
# @!method remove_const(node)
|
27
|
+
def_node_matcher :remove_const, <<~PATTERN
|
28
|
+
(send _ {:send | :__send__} (sym :remove_const) _)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
# Check for offenses
|
32
|
+
def on_send(node)
|
33
|
+
remove_const(node) do
|
34
|
+
add_offense(node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
|
49
49
|
|
50
50
|
# @!method several_example_groups?(node)
|
51
|
-
def_node_matcher :several_example_groups?,
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
52
|
(begin <#example_group_with_body? #example_group_with_body? ...>)
|
53
53
|
PATTERN
|
54
54
|
|
@@ -48,12 +48,12 @@ module RuboCop
|
|
48
48
|
MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
|
49
49
|
|
50
50
|
# @!method several_example_groups?(node)
|
51
|
-
def_node_matcher :several_example_groups?,
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
52
|
(begin <#example_group? #example_group? ...>)
|
53
53
|
PATTERN
|
54
54
|
|
55
55
|
# @!method doc_string_and_metadata(node)
|
56
|
-
def_node_matcher :doc_string_and_metadata,
|
56
|
+
def_node_matcher :doc_string_and_metadata, <<~PATTERN
|
57
57
|
(block (send _ _ $_ $...) ...)
|
58
58
|
PATTERN
|
59
59
|
|
@@ -50,7 +50,7 @@ module RuboCop
|
|
50
50
|
'on line(s) %<repeat>s'
|
51
51
|
|
52
52
|
# @!method several_include_examples?(node)
|
53
|
-
def_node_matcher :several_include_examples?,
|
53
|
+
def_node_matcher :several_include_examples?, <<~PATTERN
|
54
54
|
(begin <#include_examples? #include_examples? ...>)
|
55
55
|
PATTERN
|
56
56
|
|
@@ -0,0 +1,124 @@
|
|
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 unless (block_node = expect_block(subject_node))
|
76
|
+
|
77
|
+
add_offense(block_node)
|
78
|
+
end
|
79
|
+
|
80
|
+
def expect_block(node)
|
81
|
+
node.each_ancestor(:block).find { |block| block.method?(:expect) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def detect_offenses_in_block(node, subject_names = [])
|
85
|
+
subject_names = [*subject_names, *@subjects_by_node[node]]
|
86
|
+
|
87
|
+
if example?(node)
|
88
|
+
return detect_offenses_in_example(node, subject_names)
|
89
|
+
end
|
90
|
+
|
91
|
+
node.each_child_node(:send, :def, :block, :begin) do |child|
|
92
|
+
detect_offenses_in_block(child, subject_names)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def detect_offenses_in_example(node, subject_names)
|
97
|
+
return unless node.body
|
98
|
+
|
99
|
+
subjects_used = Hash.new(false)
|
100
|
+
|
101
|
+
subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
|
102
|
+
if subjects_used[call.method_name]
|
103
|
+
detect_offense(call)
|
104
|
+
else
|
105
|
+
subjects_used[call.method_name] = true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def detect_subjects_in_scope(node)
|
111
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
112
|
+
subject?(child) do |name|
|
113
|
+
outer_example_group = child.each_ancestor(:block).find do |a|
|
114
|
+
example_group?(a)
|
115
|
+
end
|
116
|
+
|
117
|
+
(h[outer_example_group] ||= []) << name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
|
49
49
|
|
50
50
|
# @!method and_return_value(node)
|
51
|
-
def_node_search :and_return_value,
|
51
|
+
def_node_search :and_return_value, <<~PATTERN
|
52
52
|
$(send _ :and_return $(...))
|
53
53
|
PATTERN
|
54
54
|
|
@@ -3,9 +3,13 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
#
|
6
|
+
# Checks for consistent style for shared example names.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# Enforces either `string` or `symbol` for shared example names.
|
9
|
+
#
|
10
|
+
# This cop can be configured using the `EnforcedStyle` option
|
11
|
+
#
|
12
|
+
# @example `EnforcedStyle: string` (default)
|
9
13
|
# # bad
|
10
14
|
# it_behaves_like :foo_bar_baz
|
11
15
|
# it_should_behave_like :foo_bar_baz
|
@@ -20,33 +24,66 @@ module RuboCop
|
|
20
24
|
# shared_examples_for 'foo bar baz'
|
21
25
|
# include_examples 'foo bar baz'
|
22
26
|
#
|
27
|
+
# @example `EnforcedStyle: symbol`
|
28
|
+
# # bad
|
29
|
+
# it_behaves_like 'foo bar baz'
|
30
|
+
# it_should_behave_like 'foo bar baz'
|
31
|
+
# shared_examples 'foo bar baz'
|
32
|
+
# shared_examples_for 'foo bar baz'
|
33
|
+
# include_examples 'foo bar baz'
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# it_behaves_like :foo_bar_baz
|
37
|
+
# it_should_behave_like :foo_bar_baz
|
38
|
+
# shared_examples :foo_bar_baz
|
39
|
+
# shared_examples_for :foo_bar_baz
|
40
|
+
# include_examples :foo_bar_baz
|
41
|
+
#
|
23
42
|
class SharedExamples < Base
|
24
43
|
extend AutoCorrector
|
44
|
+
include ConfigurableEnforcedStyle
|
25
45
|
|
26
46
|
# @!method shared_examples(node)
|
27
47
|
def_node_matcher :shared_examples, <<~PATTERN
|
28
48
|
{
|
29
|
-
(send #rspec? #SharedGroups.all ...)
|
30
|
-
(send nil? #Includes.all ...)
|
49
|
+
(send #rspec? #SharedGroups.all $_ ...)
|
50
|
+
(send nil? #Includes.all $_ ...)
|
31
51
|
}
|
32
52
|
PATTERN
|
33
53
|
|
34
54
|
def on_send(node)
|
35
|
-
shared_examples(node) do
|
36
|
-
|
37
|
-
next unless ast_node&.sym_type?
|
55
|
+
shared_examples(node) do |ast_node|
|
56
|
+
next unless offense?(ast_node)
|
38
57
|
|
39
|
-
checker =
|
40
|
-
add_offense(
|
41
|
-
corrector.replace(
|
58
|
+
checker = new_checker(ast_node)
|
59
|
+
add_offense(ast_node, message: checker.message) do |corrector|
|
60
|
+
corrector.replace(ast_node, checker.preferred_style)
|
42
61
|
end
|
43
62
|
end
|
44
63
|
end
|
45
64
|
|
65
|
+
private
|
66
|
+
|
67
|
+
def offense?(ast_node)
|
68
|
+
if style == :symbol
|
69
|
+
ast_node.str_type?
|
70
|
+
else # string
|
71
|
+
ast_node.sym_type?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_checker(ast_node)
|
76
|
+
if style == :symbol
|
77
|
+
SymbolChecker.new(ast_node)
|
78
|
+
else # string
|
79
|
+
StringChecker.new(ast_node)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
46
83
|
# :nodoc:
|
47
|
-
class
|
84
|
+
class SymbolChecker
|
48
85
|
MSG = 'Prefer %<prefer>s over `%<current>s` ' \
|
49
|
-
'to
|
86
|
+
'to symbolize shared examples.'
|
50
87
|
|
51
88
|
attr_reader :node
|
52
89
|
|
@@ -55,22 +92,31 @@ module RuboCop
|
|
55
92
|
end
|
56
93
|
|
57
94
|
def message
|
58
|
-
format(MSG, prefer: preferred_style, current:
|
95
|
+
format(MSG, prefer: preferred_style, current: node.value.inspect)
|
59
96
|
end
|
60
97
|
|
61
98
|
def preferred_style
|
62
|
-
|
63
|
-
wrap_with_single_quotes(string)
|
99
|
+
":#{node.value.to_s.downcase.tr(' ', '_')}"
|
64
100
|
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# :nodoc:
|
104
|
+
class StringChecker
|
105
|
+
MSG = 'Prefer %<prefer>s over `%<current>s` ' \
|
106
|
+
'to titleize shared examples.'
|
65
107
|
|
66
|
-
|
108
|
+
attr_reader :node
|
67
109
|
|
68
|
-
def
|
69
|
-
node
|
110
|
+
def initialize(node)
|
111
|
+
@node = node
|
70
112
|
end
|
71
113
|
|
72
|
-
def
|
73
|
-
|
114
|
+
def message
|
115
|
+
format(MSG, prefer: preferred_style, current: node.value.inspect)
|
116
|
+
end
|
117
|
+
|
118
|
+
def preferred_style
|
119
|
+
"'#{node.value.to_s.tr('_', ' ')}'"
|
74
120
|
end
|
75
121
|
end
|
76
122
|
end
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
24
24
|
RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
|
25
25
|
|
26
26
|
# @!method message_chain(node)
|
27
|
-
def_node_matcher :message_chain,
|
27
|
+
def_node_matcher :message_chain, <<~PATTERN
|
28
28
|
(send _ {:receive_message_chain :stub_chain} $_)
|
29
29
|
PATTERN
|
30
30
|
|
@@ -81,8 +81,7 @@ module RuboCop
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def key_to_arg(node)
|
84
|
-
|
85
|
-
node.sym_type? ? ":#{key}" : node.source
|
84
|
+
node.sym_type? ? ":#{node.value}" : node.source
|
86
85
|
end
|
87
86
|
|
88
87
|
def replacement(method)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that spec file paths are consistent and well-formed.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# whatever_spec.rb # describe MyClass
|
11
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# my_class_spec.rb # describe MyClass
|
15
|
+
# my_class_method_spec.rb # describe MyClass, '#method'
|
16
|
+
# my_class/method_spec.rb # describe MyClass, '#method'
|
17
|
+
#
|
18
|
+
# @example `CustomTransform: {RuboCop=>rubocop, RSpec=>rspec}` (default)
|
19
|
+
# # good
|
20
|
+
# rubocop_spec.rb # describe RuboCop
|
21
|
+
# rspec_spec.rb # describe RSpec
|
22
|
+
#
|
23
|
+
# @example `IgnoreMethods: false` (default)
|
24
|
+
# # bad
|
25
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
26
|
+
#
|
27
|
+
# @example `IgnoreMethods: true`
|
28
|
+
# # good
|
29
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
30
|
+
#
|
31
|
+
# @example `IgnoreMetadata: {type=>routing}` (default)
|
32
|
+
# # good
|
33
|
+
# whatever_spec.rb # describe MyClass, type: :routing do; end
|
34
|
+
#
|
35
|
+
class SpecFilePathFormat < Base
|
36
|
+
include TopLevelGroup
|
37
|
+
include Namespace
|
38
|
+
include FileHelp
|
39
|
+
|
40
|
+
MSG = 'Spec path should end with `%<suffix>s`.'
|
41
|
+
|
42
|
+
# @!method example_group_arguments(node)
|
43
|
+
def_node_matcher :example_group_arguments, <<~PATTERN
|
44
|
+
(block $(send #rspec? #ExampleGroups.all $_ $...) ...)
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
# @!method metadata_key_value(node)
|
48
|
+
def_node_search :metadata_key_value, '(pair (sym $_key) (sym $_value))'
|
49
|
+
|
50
|
+
def on_top_level_example_group(node)
|
51
|
+
return unless top_level_groups.one?
|
52
|
+
|
53
|
+
example_group_arguments(node) do |send_node, class_name, arguments|
|
54
|
+
next if !class_name.const_type? || ignore_metadata?(arguments)
|
55
|
+
|
56
|
+
ensure_correct_file_path(send_node, class_name, arguments)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def ensure_correct_file_path(send_node, class_name, arguments)
|
63
|
+
pattern = correct_path_pattern(class_name, arguments)
|
64
|
+
return if filename_ends_with?(pattern)
|
65
|
+
|
66
|
+
# For the suffix shown in the offense message, modify the regular
|
67
|
+
# expression pattern to resemble a glob pattern for clearer error
|
68
|
+
# messages.
|
69
|
+
suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
|
70
|
+
add_offense(send_node, message: format(MSG, suffix: suffix))
|
71
|
+
end
|
72
|
+
|
73
|
+
def ignore_metadata?(arguments)
|
74
|
+
arguments.any? do |argument|
|
75
|
+
metadata_key_value(argument).any? do |key, value|
|
76
|
+
ignore_metadata.values_at(key.to_s).include?(value.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def correct_path_pattern(class_name, arguments)
|
82
|
+
path = [expected_path(class_name)]
|
83
|
+
path << '.*' unless ignore?(arguments.first)
|
84
|
+
path << [name_pattern(arguments.first), '[^/]*_spec\.rb']
|
85
|
+
path.join
|
86
|
+
end
|
87
|
+
|
88
|
+
def name_pattern(method_name)
|
89
|
+
return if ignore?(method_name)
|
90
|
+
|
91
|
+
method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
|
92
|
+
end
|
93
|
+
|
94
|
+
def ignore?(method_name)
|
95
|
+
!method_name&.str_type? || ignore_methods?
|
96
|
+
end
|
97
|
+
|
98
|
+
def expected_path(constant)
|
99
|
+
constants = namespace(constant) + constant.const_name.split('::')
|
100
|
+
|
101
|
+
File.join(
|
102
|
+
constants.map do |name|
|
103
|
+
custom_transform.fetch(name) { camel_to_snake_case(name) }
|
104
|
+
end
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def camel_to_snake_case(string)
|
109
|
+
string
|
110
|
+
.gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
|
111
|
+
.gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
|
112
|
+
.downcase
|
113
|
+
end
|
114
|
+
|
115
|
+
def custom_transform
|
116
|
+
cop_config.fetch('CustomTransform', {})
|
117
|
+
end
|
118
|
+
|
119
|
+
def ignore_methods?
|
120
|
+
cop_config['IgnoreMethods']
|
121
|
+
end
|
122
|
+
|
123
|
+
def ignore_metadata
|
124
|
+
cop_config.fetch('IgnoreMetadata', {})
|
125
|
+
end
|
126
|
+
|
127
|
+
def filename_ends_with?(pattern)
|
128
|
+
expanded_file_path.match?("#{pattern}$")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that spec file paths suffix are consistent and well-formed.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# my_class/foo_specorb.rb # describe MyClass
|
11
|
+
# spec/models/user.rb # describe User
|
12
|
+
# spec/models/user_specxrb # describe User
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# my_class_spec.rb # describe MyClass
|
16
|
+
#
|
17
|
+
# # good - shared examples are allowed
|
18
|
+
# spec/models/user.rb # shared_examples_for 'foo'
|
19
|
+
#
|
20
|
+
class SpecFilePathSuffix < Base
|
21
|
+
include TopLevelGroup
|
22
|
+
include FileHelp
|
23
|
+
|
24
|
+
MSG = 'Spec path should end with `_spec.rb`.'
|
25
|
+
|
26
|
+
def on_top_level_example_group(node)
|
27
|
+
example_group?(node) do
|
28
|
+
add_global_offense(MSG) unless correct_path?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def correct_path?
|
35
|
+
expanded_file_path.end_with?('_spec.rb')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -31,7 +31,7 @@ module RuboCop
|
|
31
31
|
#
|
32
32
|
# @param node [RuboCop::AST::Node]
|
33
33
|
# @return [Array<RuboCop::AST::Node>] matching nodes
|
34
|
-
def_node_matcher :message_expectation?,
|
34
|
+
def_node_matcher :message_expectation?, <<~PATTERN
|
35
35
|
{
|
36
36
|
(send nil? { :receive :receive_message_chain } ...) # receive(:foo)
|
37
37
|
(send (send nil? :receive ...) :with ...) # receive(:foo).with('bar')
|
@@ -68,7 +68,7 @@ module RuboCop
|
|
68
68
|
# @param node [RuboCop::AST::Node]
|
69
69
|
#
|
70
70
|
# @yield [Symbol] subject name
|
71
|
-
def_node_matcher :subject?,
|
71
|
+
def_node_matcher :subject?, <<~PATTERN
|
72
72
|
(block
|
73
73
|
(send nil?
|
74
74
|
{ #Subjects.all (sym $_) | $#Subjects.all }
|
@@ -77,7 +77,7 @@ module RuboCop
|
|
77
77
|
|
78
78
|
# @!method let?(node)
|
79
79
|
# Find a memoized helper
|
80
|
-
def_node_matcher :let?,
|
80
|
+
def_node_matcher :let?, <<~PATTERN
|
81
81
|
(block
|
82
82
|
(send nil? :let (sym $_)
|
83
83
|
) args ...)
|
@@ -94,7 +94,7 @@ module RuboCop
|
|
94
94
|
# expect(foo).to receive(:bar).with(1)
|
95
95
|
# expect(foo).to receive(:bar).with(1).and_return(2)
|
96
96
|
#
|
97
|
-
def_node_matcher :message_expectation?,
|
97
|
+
def_node_matcher :message_expectation?, <<~PATTERN
|
98
98
|
(send
|
99
99
|
{
|
100
100
|
(send nil? { :expect :allow } (send nil? %))
|
@@ -106,7 +106,7 @@ module RuboCop
|
|
106
106
|
PATTERN
|
107
107
|
|
108
108
|
# @!method message_expectation_matcher?(node)
|
109
|
-
def_node_search :message_expectation_matcher?,
|
109
|
+
def_node_search :message_expectation_matcher?, <<~PATTERN
|
110
110
|
(send nil? {
|
111
111
|
:receive :receive_messages :receive_message_chain :have_received
|
112
112
|
} ...)
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
RESTRICT_ON_SEND = %i[to].freeze
|
36
36
|
|
37
37
|
# @!method empty_raise_error_or_exception(node)
|
38
|
-
def_node_matcher :empty_raise_error_or_exception,
|
38
|
+
def_node_matcher :empty_raise_error_or_exception, <<~PATTERN
|
39
39
|
(send
|
40
40
|
(block
|
41
41
|
(send nil? :expect) ...)
|
@@ -57,7 +57,7 @@ module RuboCop
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def block_with_args?(node)
|
60
|
-
return unless node&.block_type?
|
60
|
+
return false unless node&.block_type?
|
61
61
|
|
62
62
|
node.arguments?
|
63
63
|
end
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
return unless inside_example_group?(node)
|
36
36
|
|
37
37
|
variable_definition?(node) do |variable|
|
38
|
-
next unless
|
38
|
+
next unless style_offense?(variable)
|
39
39
|
|
40
40
|
add_offense(
|
41
41
|
variable,
|
@@ -59,9 +59,9 @@ module RuboCop
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
63
|
-
style == :symbols && string?(variable) ||
|
64
|
-
style == :strings && symbol?(variable)
|
62
|
+
def style_offense?(variable)
|
63
|
+
(style == :symbols && string?(variable)) ||
|
64
|
+
(style == :strings && symbol?(variable))
|
65
65
|
end
|
66
66
|
|
67
67
|
def string?(node)
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# Only investigates references that are one of the supported styles.
|
9
9
|
#
|
10
|
-
# @see https://
|
10
|
+
# @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
|
11
11
|
#
|
12
12
|
# This cop can be configured in your configuration using the
|
13
13
|
# `EnforcedStyle` option and supports `--auto-gen-config`.
|
@@ -79,8 +79,8 @@ module RuboCop
|
|
79
79
|
expression = class_reference.source_range
|
80
80
|
|
81
81
|
add_offense(expression, message: message) do |corrector|
|
82
|
-
|
83
|
-
corrector.replace(expression, correct_style(
|
82
|
+
offense = class_reference.source
|
83
|
+
corrector.replace(expression, correct_style(offense))
|
84
84
|
|
85
85
|
opposite_style_detected
|
86
86
|
end
|
@@ -98,11 +98,11 @@ module RuboCop
|
|
98
98
|
class_reference_style != style
|
99
99
|
end
|
100
100
|
|
101
|
-
def correct_style(
|
101
|
+
def correct_style(offense)
|
102
102
|
if style == :string
|
103
|
-
"'#{
|
103
|
+
"'#{offense}'"
|
104
104
|
else
|
105
|
-
|
105
|
+
offense.gsub(/^['"]|['"]$/, '')
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|