rubocop-rspec 1.44.0 → 2.1.0
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 +32 -1
- data/README.md +5 -1
- data/config/default.yml +170 -81
- data/lib/rubocop-rspec.rb +7 -8
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +7 -3
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +7 -3
- data/lib/rubocop/cop/rspec/around_block.rb +1 -1
- data/lib/rubocop/cop/rspec/base.rb +6 -55
- data/lib/rubocop/cop/rspec/be.rb +2 -2
- data/lib/rubocop/cop/rspec/before_after_all.rb +3 -3
- data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +2 -2
- data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +3 -2
- data/lib/rubocop/cop/rspec/describe_class.rb +2 -2
- data/lib/rubocop/cop/rspec/describe_method.rb +2 -2
- data/lib/rubocop/cop/rspec/described_class.rb +1 -2
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -2
- data/lib/rubocop/cop/rspec/dialect.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_example_group.rb +6 -45
- data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -2
- data/lib/rubocop/cop/rspec/expect_actual.rb +2 -1
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
- data/lib/rubocop/cop/rspec/file_path.rb +24 -17
- data/lib/rubocop/cop/rspec/focus.rb +43 -8
- data/lib/rubocop/cop/rspec/hook_argument.rb +2 -4
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +2 -2
- data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -1
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +2 -2
- data/lib/rubocop/cop/rspec/let_setup.rb +7 -4
- data/lib/rubocop/cop/rspec/message_spies.rb +3 -3
- data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +51 -0
- data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
- data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/variable.rb +20 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +2 -3
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +3 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +8 -12
- data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
- data/lib/rubocop/cop/rspec/pending.rb +13 -5
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -2
- data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +18 -11
- data/lib/rubocop/cop/rspec/shared_examples.rb +3 -1
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +16 -6
- data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
- data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +0 -1
- data/lib/rubocop/rspec/align_let_brace.rb +1 -1
- data/lib/rubocop/rspec/concept.rb +2 -2
- data/lib/rubocop/rspec/config_formatter.rb +5 -3
- data/lib/rubocop/rspec/corrector/move_node.rb +1 -1
- data/lib/rubocop/rspec/example_group.rb +15 -5
- data/lib/rubocop/rspec/hook.rb +1 -1
- data/lib/rubocop/rspec/inject.rb +4 -2
- data/lib/rubocop/rspec/language.rb +143 -109
- data/lib/rubocop/rspec/language/node_pattern.rb +7 -24
- data/lib/rubocop/rspec/node.rb +1 -1
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +13 -17
- data/lib/rubocop/cop/rspec/cop.rb +0 -10
- data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +0 -41
- data/lib/rubocop/rspec.rb +0 -12
- data/lib/rubocop/rspec/empty_line_separation.rb +0 -48
- data/lib/rubocop/rspec/final_end_location.rb +0 -17
- data/lib/rubocop/rspec/top_level_describe.rb +0 -52
- data/lib/rubocop/rspec/top_level_group.rb +0 -57
- data/lib/rubocop/rspec/variable.rb +0 -16
@@ -23,7 +23,7 @@ module RuboCop
|
|
23
23
|
class ExpectInHook < Base
|
24
24
|
MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
|
25
25
|
|
26
|
-
def_node_search :expectation, Expectations
|
26
|
+
def_node_search :expectation, send_pattern('#Expectations.all')
|
27
27
|
|
28
28
|
def on_block(node)
|
29
29
|
return unless hook?(node)
|
@@ -15,7 +15,7 @@ module RuboCop
|
|
15
15
|
# # good
|
16
16
|
# expect { my_app.print_report }.to output('Hello World').to_stdout
|
17
17
|
class ExpectOutput < Base
|
18
|
-
MSG = 'Use `expect { ... }.to output(...).to_%<name>s` '\
|
18
|
+
MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
|
19
19
|
'instead of mutating $%<name>s.'
|
20
20
|
|
21
21
|
def on_gvasgn(node)
|
@@ -57,7 +57,7 @@ module RuboCop
|
|
57
57
|
# my_class_spec.rb # describe MyClass, '#method'
|
58
58
|
#
|
59
59
|
class FilePath < Base
|
60
|
-
include
|
60
|
+
include TopLevelGroup
|
61
61
|
|
62
62
|
MSG = 'Spec path should end with `%<suffix>s`.'
|
63
63
|
|
@@ -82,30 +82,39 @@ module RuboCop
|
|
82
82
|
private
|
83
83
|
|
84
84
|
def ensure_correct_file_path(send_node, described_class, arguments)
|
85
|
-
|
86
|
-
return if filename_ends_with?(
|
87
|
-
|
88
|
-
|
85
|
+
pattern = pattern_for(described_class, arguments.first)
|
86
|
+
return if filename_ends_with?(pattern)
|
87
|
+
|
88
|
+
# For the suffix shown in the offense message, modify the regular
|
89
|
+
# expression pattern to resemble a glob pattern for clearer error
|
90
|
+
# messages.
|
91
|
+
offense_suffix = pattern.gsub('.*', '*').sub('[^/]', '')
|
92
|
+
.sub('\.', '.')
|
93
|
+
add_offense(send_node, message: format(MSG, suffix: offense_suffix))
|
89
94
|
end
|
90
95
|
|
91
96
|
def routing_spec?(args)
|
92
97
|
args.any?(&method(:routing_metadata?))
|
93
98
|
end
|
94
99
|
|
95
|
-
def
|
96
|
-
return
|
100
|
+
def pattern_for(described_class, method_name)
|
101
|
+
return pattern_for_spec_suffix_only? if spec_suffix_only?
|
97
102
|
|
98
|
-
|
103
|
+
[
|
104
|
+
expected_path(described_class),
|
105
|
+
name_pattern(method_name),
|
106
|
+
'[^/]*_spec\.rb'
|
107
|
+
].join
|
99
108
|
end
|
100
109
|
|
101
|
-
def
|
102
|
-
'
|
110
|
+
def pattern_for_spec_suffix_only?
|
111
|
+
'.*_spec\.rb'
|
103
112
|
end
|
104
113
|
|
105
|
-
def
|
114
|
+
def name_pattern(method_name)
|
106
115
|
return unless method_name&.str_type?
|
107
116
|
|
108
|
-
"
|
117
|
+
".*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
|
109
118
|
end
|
110
119
|
|
111
120
|
def expected_path(constant)
|
@@ -131,11 +140,9 @@ module RuboCop
|
|
131
140
|
cop_config['IgnoreMethods']
|
132
141
|
end
|
133
142
|
|
134
|
-
def filename_ends_with?(
|
135
|
-
filename =
|
136
|
-
|
137
|
-
.gsub('../', '')
|
138
|
-
File.fnmatch?("*#{glob}", filename)
|
143
|
+
def filename_ends_with?(pattern)
|
144
|
+
filename = File.expand_path(processed_source.buffer.name)
|
145
|
+
filename.match?("#{pattern}$")
|
139
146
|
end
|
140
147
|
|
141
148
|
def relevant_rubocop_rspec_file?(_file)
|
@@ -20,25 +20,40 @@ module RuboCop
|
|
20
20
|
# describe MyClass do
|
21
21
|
# end
|
22
22
|
class Focus < Base
|
23
|
-
|
23
|
+
extend AutoCorrector
|
24
|
+
include RangeHelp
|
24
25
|
|
25
|
-
|
26
|
+
MSG = 'Focused spec found.'
|
26
27
|
|
27
|
-
def_node_matcher :focusable_selector?,
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def_node_matcher :focusable_selector?, <<-PATTERN
|
29
|
+
{
|
30
|
+
#ExampleGroups.regular
|
31
|
+
#ExampleGroups.skipped
|
32
|
+
#Examples.regular
|
33
|
+
#Examples.skipped
|
34
|
+
#Examples.pending
|
35
|
+
}
|
36
|
+
PATTERN
|
31
37
|
|
32
38
|
def_node_matcher :metadata, <<-PATTERN
|
33
39
|
{(send #rspec? #focusable_selector? <$(sym :focus) ...>)
|
34
40
|
(send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))}
|
35
41
|
PATTERN
|
36
42
|
|
37
|
-
def_node_matcher :focused_block?,
|
43
|
+
def_node_matcher :focused_block?,
|
44
|
+
send_pattern(<<~PATTERN)
|
45
|
+
{#ExampleGroups.focused #Examples.focused}
|
46
|
+
PATTERN
|
38
47
|
|
39
48
|
def on_send(node)
|
40
49
|
focus_metadata(node) do |focus|
|
41
|
-
add_offense(focus)
|
50
|
+
add_offense(focus) do |corrector|
|
51
|
+
if focus.pair_type? || focus.str_type? || focus.sym_type?
|
52
|
+
corrector.remove(with_surrounding(focus))
|
53
|
+
elsif focus.send_type?
|
54
|
+
correct_send(corrector, focus)
|
55
|
+
end
|
56
|
+
end
|
42
57
|
end
|
43
58
|
end
|
44
59
|
|
@@ -49,6 +64,26 @@ module RuboCop
|
|
49
64
|
|
50
65
|
metadata(node, &block)
|
51
66
|
end
|
67
|
+
|
68
|
+
def with_surrounding(focus)
|
69
|
+
range_with_space = range_with_surrounding_space(
|
70
|
+
range: focus.loc.expression,
|
71
|
+
side: :left
|
72
|
+
)
|
73
|
+
|
74
|
+
range_with_surrounding_comma(range_with_space, :left)
|
75
|
+
end
|
76
|
+
|
77
|
+
def correct_send(corrector, focus)
|
78
|
+
range = focus.loc.selector
|
79
|
+
unfocused = focus.method_name.to_s.sub(/^f/, '')
|
80
|
+
unless Examples.regular(unfocused) || ExampleGroups.regular(unfocused)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
corrector.replace(range,
|
85
|
+
range.source.sub(focus.method_name.to_s, unfocused))
|
86
|
+
end
|
52
87
|
end
|
53
88
|
end
|
54
89
|
end
|
@@ -64,13 +64,11 @@ module RuboCop
|
|
64
64
|
IMPLICIT_MSG = 'Omit the default `%<scope>p` argument for RSpec hooks.'
|
65
65
|
EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.'
|
66
66
|
|
67
|
-
def_node_matcher :hook?, Hooks::ALL.node_pattern_union
|
68
|
-
|
69
67
|
def_node_matcher :scoped_hook, <<-PATTERN
|
70
|
-
(block $(send _ #
|
68
|
+
(block $(send _ #Hooks.all (sym ${:each :example})) ...)
|
71
69
|
PATTERN
|
72
70
|
|
73
|
-
def_node_matcher :unscoped_hook, '(block $(send _ #
|
71
|
+
def_node_matcher :unscoped_hook, '(block $(send _ #Hooks.all) ...)'
|
74
72
|
|
75
73
|
def on_block(node)
|
76
74
|
hook(node) do |method_send, scope_name|
|
@@ -30,8 +30,8 @@ module RuboCop
|
|
30
30
|
|
31
31
|
def_node_matcher :example_or_group?, <<-PATTERN
|
32
32
|
{
|
33
|
-
#{(
|
34
|
-
#{Includes
|
33
|
+
#{block_pattern('{#ExampleGroups.all #Examples.all}')}
|
34
|
+
#{send_pattern('#Includes.examples')}
|
35
35
|
}
|
36
36
|
PATTERN
|
37
37
|
|
@@ -21,7 +21,7 @@ module RuboCop
|
|
21
21
|
class InstanceSpy < Base
|
22
22
|
extend AutoCorrector
|
23
23
|
|
24
|
-
MSG = 'Use `instance_spy` when you check your double '\
|
24
|
+
MSG = 'Use `instance_spy` when you check your double ' \
|
25
25
|
'with `have_received`.'
|
26
26
|
|
27
27
|
def_node_search :null_double, <<-PATTERN
|
@@ -22,7 +22,7 @@ module RuboCop
|
|
22
22
|
extend AutoCorrector
|
23
23
|
include ConfigurableEnforcedStyle
|
24
24
|
|
25
|
-
MSG = 'Prefer `%<replacement>s` over `%<original>s` when including '\
|
25
|
+
MSG = 'Prefer `%<replacement>s` over `%<original>s` when including ' \
|
26
26
|
'examples in a nested context.'
|
27
27
|
|
28
28
|
def_node_matcher :example_inclusion_offense, '(send _ % ...)'
|
@@ -37,8 +37,8 @@ module RuboCop
|
|
37
37
|
|
38
38
|
def_node_matcher :example_or_group?, <<-PATTERN
|
39
39
|
{
|
40
|
-
#{(
|
41
|
-
#{Includes
|
40
|
+
#{block_pattern('{#ExampleGroups.all #Examples.all}')}
|
41
|
+
#{send_pattern('#Includes.examples')}
|
42
42
|
}
|
43
43
|
PATTERN
|
44
44
|
|
@@ -29,10 +29,13 @@ module RuboCop
|
|
29
29
|
MSG = 'Do not use `let!` to setup objects not referenced in tests.'
|
30
30
|
|
31
31
|
def_node_matcher :example_or_shared_group_or_including?,
|
32
|
-
(
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
block_pattern(<<~PATTERN)
|
33
|
+
{
|
34
|
+
#SharedGroups.all
|
35
|
+
#ExampleGroups.all
|
36
|
+
#Includes.all
|
37
|
+
}
|
38
|
+
PATTERN
|
36
39
|
|
37
40
|
def_node_matcher :let_bang, <<-PATTERN
|
38
41
|
{
|
@@ -29,14 +29,14 @@ module RuboCop
|
|
29
29
|
|
30
30
|
MSG_RECEIVE = 'Prefer `receive` for setting message expectations.'
|
31
31
|
|
32
|
-
MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message '\
|
33
|
-
'expectations. Setup `%<source>s` as a spy using '\
|
32
|
+
MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
|
33
|
+
'expectations. Setup `%<source>s` as a spy using ' \
|
34
34
|
'`allow` or `instance_spy`.'
|
35
35
|
|
36
36
|
SUPPORTED_STYLES = %w[have_received receive].freeze
|
37
37
|
|
38
38
|
def_node_matcher :message_expectation, %(
|
39
|
-
(send (send nil? :expect $_) #
|
39
|
+
(send (send nil? :expect $_) #Runners.all ...)
|
40
40
|
)
|
41
41
|
|
42
42
|
def_node_search :receive_message, %(
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Helps determine the offending location if there is not an empty line
|
7
|
+
# following the node. Allows comments to follow directly after.
|
8
|
+
module EmptyLineSeparation
|
9
|
+
include FinalEndLocation
|
10
|
+
include RangeHelp
|
11
|
+
|
12
|
+
def missing_separating_line_offense(node)
|
13
|
+
return if last_child?(node)
|
14
|
+
|
15
|
+
missing_separating_line(node) do |location|
|
16
|
+
msg = yield(node.method_name)
|
17
|
+
add_offense(location, message: msg) do |corrector|
|
18
|
+
corrector.insert_after(location.end, "\n")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def missing_separating_line(node)
|
24
|
+
line = final_end_location(node).line
|
25
|
+
|
26
|
+
line += 1 while comment_line?(processed_source[line])
|
27
|
+
|
28
|
+
return if processed_source[line].blank?
|
29
|
+
|
30
|
+
yield offending_loc(line)
|
31
|
+
end
|
32
|
+
|
33
|
+
def offending_loc(last_line)
|
34
|
+
offending_line = processed_source[last_line - 1]
|
35
|
+
|
36
|
+
content_length = offending_line.lstrip.length
|
37
|
+
start = offending_line.length - content_length
|
38
|
+
|
39
|
+
source_range(processed_source.buffer,
|
40
|
+
last_line, start, content_length)
|
41
|
+
end
|
42
|
+
|
43
|
+
def last_child?(node)
|
44
|
+
return true unless node.parent&.begin_type?
|
45
|
+
|
46
|
+
node.equal?(node.parent.children.last)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Helps find the true end location of nodes which might contain heredocs.
|
7
|
+
module FinalEndLocation
|
8
|
+
def final_end_location(start_node)
|
9
|
+
heredoc_endings =
|
10
|
+
start_node.each_node(:str, :dstr, :xstr)
|
11
|
+
.select(&:heredoc?)
|
12
|
+
.map { |node| node.loc.heredoc_end }
|
13
|
+
|
14
|
+
[start_node.loc.end, *heredoc_endings].max_by(&:line)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Helper methods for top level example group cops
|
7
|
+
module TopLevelGroup
|
8
|
+
extend RuboCop::NodePattern::Macros
|
9
|
+
|
10
|
+
def on_new_investigation
|
11
|
+
super
|
12
|
+
|
13
|
+
top_level_groups.each do |node|
|
14
|
+
on_top_level_example_group(node) if example_group?(node)
|
15
|
+
on_top_level_group(node)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def top_level_groups
|
20
|
+
@top_level_groups ||=
|
21
|
+
top_level_nodes(root_node).select { |n| spec_group?(n) }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Dummy methods to be overridden in the consumer
|
27
|
+
def on_top_level_example_group(_node); end
|
28
|
+
|
29
|
+
def on_top_level_group(_node); end
|
30
|
+
|
31
|
+
def top_level_group?(node)
|
32
|
+
top_level_groups.include?(node)
|
33
|
+
end
|
34
|
+
|
35
|
+
def top_level_nodes(node)
|
36
|
+
return [] if node.nil?
|
37
|
+
|
38
|
+
case node.type
|
39
|
+
when :begin
|
40
|
+
node.children
|
41
|
+
when :module, :class
|
42
|
+
top_level_nodes(node.body)
|
43
|
+
else
|
44
|
+
[node]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def root_node
|
49
|
+
processed_source.ast
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Helps check offenses with variable definitions
|
7
|
+
module Variable
|
8
|
+
extend RuboCop::NodePattern::Macros
|
9
|
+
|
10
|
+
Subjects = RuboCop::RSpec::Language::Subjects
|
11
|
+
Helpers = RuboCop::RSpec::Language::Helpers
|
12
|
+
|
13
|
+
def_node_matcher :variable_definition?, <<~PATTERN
|
14
|
+
(send nil? {#Subjects.all #Helpers.all}
|
15
|
+
$({sym str dsym dstr} ...) ...)
|
16
|
+
PATTERN
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -23,10 +23,9 @@ module RuboCop
|
|
23
23
|
# end
|
24
24
|
# end
|
25
25
|
class MultipleDescribes < Base
|
26
|
-
include
|
26
|
+
include TopLevelGroup
|
27
27
|
|
28
|
-
MSG = 'Do not use multiple top-level example groups - '
|
29
|
-
'try to nest them.'
|
28
|
+
MSG = 'Do not use multiple top-level example groups - try to nest them.'
|
30
29
|
|
31
30
|
def on_top_level_group(node)
|
32
31
|
top_level_example_groups =
|