rubocop-rspec 1.37.0 → 1.40.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 +52 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +2 -1
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
- data/lib/rubocop/cop/rspec/cop.rb +9 -29
- data/lib/rubocop/cop/rspec/describe_class.rb +15 -2
- data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
- data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
- data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +16 -1
- data/lib/rubocop/cop/rspec/file_path.rb +32 -4
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/instance_variable.rb +13 -4
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
- data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +25 -47
- data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
- data/lib/rubocop/cop/rspec_cops.rb +7 -1
- data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
- data/lib/rubocop/rspec/description_extractor.rb +2 -6
- data/lib/rubocop/rspec/example.rb +1 -1
- data/lib/rubocop/rspec/hook.rb +44 -11
- data/lib/rubocop/rspec/language.rb +20 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +1 -1
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +16 -9
- data/lib/rubocop/rspec/util.rb +0 -19
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module RuboCop
|
4
6
|
module Cop
|
5
7
|
module RSpec
|
@@ -75,70 +77,46 @@ module RuboCop
|
|
75
77
|
|
76
78
|
def on_block(node)
|
77
79
|
return unless example_group?(node)
|
80
|
+
return if (processed_example_groups & node.ancestors).any?
|
81
|
+
|
82
|
+
processed_example_groups << node
|
83
|
+
@explicit_subjects = find_all_explicit_subjects(node)
|
78
84
|
|
79
|
-
|
85
|
+
find_subject_expectations(node) do |stub|
|
80
86
|
add_offense(stub)
|
81
87
|
end
|
82
88
|
end
|
83
89
|
|
84
90
|
private
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
# @param node [RuboCop::Node] example group
|
89
|
-
#
|
90
|
-
# @yield [RuboCop::Node] message expectations for subject
|
91
|
-
def find_subject_stub(node, &block)
|
92
|
-
find_subject(node) do |subject_name, context|
|
93
|
-
find_subject_expectation(context, subject_name, &block)
|
94
|
-
end
|
92
|
+
def processed_example_groups
|
93
|
+
@processed_example_groups ||= Set.new
|
95
94
|
end
|
96
95
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
#
|
102
|
-
# @yield [RuboCop::Node] message expectation
|
103
|
-
def find_subject_expectation(node, subject_name, &block)
|
104
|
-
# Do not search node if it is an example group with its own subject.
|
105
|
-
return if example_group?(node) && redefines_subject?(node)
|
106
|
-
|
107
|
-
# Yield the current node if it is a message expectation.
|
108
|
-
yield(node) if message_expectation?(node, subject_name)
|
96
|
+
def find_all_explicit_subjects(node)
|
97
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
98
|
+
name = subject(child)
|
99
|
+
next unless name
|
109
100
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
101
|
+
outer_example_group = child.each_ancestor.find do |a|
|
102
|
+
example_group?(a)
|
103
|
+
end
|
115
104
|
|
116
|
-
|
117
|
-
|
118
|
-
# @param node [RuboCop::Node]
|
119
|
-
#
|
120
|
-
# @return [Boolean]
|
121
|
-
def redefines_subject?(node)
|
122
|
-
node.each_child_node.any? do |child|
|
123
|
-
subject(child) || redefines_subject?(child)
|
105
|
+
h[outer_example_group] ||= []
|
106
|
+
h[outer_example_group] << name
|
124
107
|
end
|
125
108
|
end
|
126
109
|
|
127
|
-
|
128
|
-
|
129
|
-
# @param node [RuboCop::Node]
|
130
|
-
# @param parent [RuboCop::Node,nil]
|
131
|
-
#
|
132
|
-
# @yieldparam subject_name [Symbol] name of subject being defined
|
133
|
-
# @yieldparam parent [RuboCop::Node] parent of subject definition
|
134
|
-
def find_subject(node, parent: nil, &block)
|
135
|
-
# An implicit subject is defined by RSpec when no subject is declared
|
136
|
-
subject_name = subject(node) || :subject
|
110
|
+
def find_subject_expectations(node, subject_names = [], &block)
|
111
|
+
subject_names = @explicit_subjects[node] if @explicit_subjects[node]
|
137
112
|
|
138
|
-
|
113
|
+
expectation_detected = (subject_names + [:subject]).any? do |name|
|
114
|
+
message_expectation?(node, name)
|
115
|
+
end
|
116
|
+
return yield(node) if expectation_detected
|
139
117
|
|
140
118
|
node.each_child_node do |child|
|
141
|
-
|
119
|
+
find_subject_expectations(child, subject_names, &block)
|
142
120
|
end
|
143
121
|
end
|
144
122
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that memoized helpers names are symbols or strings.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: symbols (default)
|
9
|
+
# # bad
|
10
|
+
# let('user_name') { 'Adam' }
|
11
|
+
# subject('user') { create_user }
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# let(:user_name) { 'Adam' }
|
15
|
+
# subject(:user) { create_user }
|
16
|
+
#
|
17
|
+
# @example EnforcedStyle: strings
|
18
|
+
# # bad
|
19
|
+
# let(:user_name) { 'Adam' }
|
20
|
+
# subject(:user) { create_user }
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# let('user_name') { 'Adam' }
|
24
|
+
# subject('user') { create_user }
|
25
|
+
class VariableDefinition < Cop
|
26
|
+
include ConfigurableEnforcedStyle
|
27
|
+
include RuboCop::RSpec::Variable
|
28
|
+
|
29
|
+
MSG = 'Use %<style>s for variable names.'
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
variable_definition?(node) do |variable|
|
33
|
+
if style_violation?(variable)
|
34
|
+
add_offense(variable, message: format(MSG, style: style))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def style_violation?(variable)
|
42
|
+
style == :symbols && string?(variable) ||
|
43
|
+
style == :strings && symbol?(variable)
|
44
|
+
end
|
45
|
+
|
46
|
+
def string?(node)
|
47
|
+
node.str_type? || node.dstr_type?
|
48
|
+
end
|
49
|
+
|
50
|
+
def symbol?(node)
|
51
|
+
node.sym_type? || node.dsym_type?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that memoized helper names use the configured style.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: snake_case (default)
|
9
|
+
# # bad
|
10
|
+
# let(:userName) { 'Adam' }
|
11
|
+
# subject(:userName) { 'Adam' }
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# let(:user_name) { 'Adam' }
|
15
|
+
# subject(:user_name) { 'Adam' }
|
16
|
+
#
|
17
|
+
# @example EnforcedStyle: camelCase
|
18
|
+
# # bad
|
19
|
+
# let(:user_name) { 'Adam' }
|
20
|
+
# subject(:user_name) { 'Adam' }
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# let(:userName) { 'Adam' }
|
24
|
+
# subject(:userName) { 'Adam' }
|
25
|
+
class VariableName < Cop
|
26
|
+
include ConfigurableNaming
|
27
|
+
include RuboCop::RSpec::Variable
|
28
|
+
|
29
|
+
MSG = 'Use %<style>s for variable names.'
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
variable_definition?(node) do |variable|
|
33
|
+
return if variable.dstr_type? || variable.dsym_type?
|
34
|
+
|
35
|
+
check_name(node, variable.value, variable.loc.expression)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def message(style)
|
42
|
+
format(MSG, style: style)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'rspec/capybara/current_path_expectation'
|
4
4
|
require_relative 'rspec/capybara/feature_methods'
|
5
|
+
require_relative 'rspec/capybara/visibility_matcher'
|
5
6
|
|
6
7
|
require_relative 'rspec/factory_bot/attribute_defined_statically'
|
7
8
|
require_relative 'rspec/factory_bot/create_list'
|
@@ -9,7 +10,7 @@ require_relative 'rspec/factory_bot/factory_class_name'
|
|
9
10
|
|
10
11
|
begin
|
11
12
|
require_relative 'rspec/rails/http_status'
|
12
|
-
rescue LoadError
|
13
|
+
rescue LoadError
|
13
14
|
# Rails/HttpStatus cannot be loaded if rack/utils is unavailable.
|
14
15
|
end
|
15
16
|
|
@@ -29,6 +30,7 @@ require_relative 'rspec/described_class'
|
|
29
30
|
require_relative 'rspec/described_class_module_wrapping'
|
30
31
|
require_relative 'rspec/dialect'
|
31
32
|
require_relative 'rspec/empty_example_group'
|
33
|
+
require_relative 'rspec/empty_hook'
|
32
34
|
require_relative 'rspec/empty_line_after_example'
|
33
35
|
require_relative 'rspec/empty_line_after_example_group'
|
34
36
|
require_relative 'rspec/empty_line_after_final_let'
|
@@ -74,6 +76,8 @@ require_relative 'rspec/receive_counts'
|
|
74
76
|
require_relative 'rspec/receive_never'
|
75
77
|
require_relative 'rspec/repeated_description'
|
76
78
|
require_relative 'rspec/repeated_example'
|
79
|
+
require_relative 'rspec/repeated_example_group_body'
|
80
|
+
require_relative 'rspec/repeated_example_group_description'
|
77
81
|
require_relative 'rspec/return_from_stub'
|
78
82
|
require_relative 'rspec/scattered_let'
|
79
83
|
require_relative 'rspec/scattered_setup'
|
@@ -82,6 +86,8 @@ require_relative 'rspec/shared_examples'
|
|
82
86
|
require_relative 'rspec/single_argument_message_chain'
|
83
87
|
require_relative 'rspec/subject_stub'
|
84
88
|
require_relative 'rspec/unspecified_exception'
|
89
|
+
require_relative 'rspec/variable_definition'
|
90
|
+
require_relative 'rspec/variable_name'
|
85
91
|
require_relative 'rspec/verified_doubles'
|
86
92
|
require_relative 'rspec/void_expect'
|
87
93
|
require_relative 'rspec/yield'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module RSpec
|
5
|
+
module Corrector
|
6
|
+
# Helper methods to move a node
|
7
|
+
class MoveNode
|
8
|
+
include RuboCop::Cop::RangeHelp
|
9
|
+
include RuboCop::RSpec::FinalEndLocation
|
10
|
+
|
11
|
+
attr_reader :original, :corrector, :processed_source
|
12
|
+
|
13
|
+
def initialize(node, corrector, processed_source)
|
14
|
+
@original = node
|
15
|
+
@corrector = corrector
|
16
|
+
@processed_source = processed_source # used by RangeHelp
|
17
|
+
end
|
18
|
+
|
19
|
+
def move_before(other) # rubocop:disable Metrics/AbcSize
|
20
|
+
position = other.loc.expression
|
21
|
+
indent = "\n" + ' ' * other.loc.column
|
22
|
+
|
23
|
+
corrector.insert_before(position, source(original) + indent)
|
24
|
+
corrector.remove(node_range_with_surrounding_space(original))
|
25
|
+
end
|
26
|
+
|
27
|
+
def move_after(other)
|
28
|
+
position = final_end_location(other)
|
29
|
+
indent = "\n" + ' ' * other.loc.column
|
30
|
+
|
31
|
+
corrector.insert_after(position, indent + source(original))
|
32
|
+
corrector.remove(node_range_with_surrounding_space(original))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def source(node)
|
38
|
+
node_range(node).source
|
39
|
+
end
|
40
|
+
|
41
|
+
def node_range(node)
|
42
|
+
node.loc.expression.with(end_pos: final_end_location(node).end_pos)
|
43
|
+
end
|
44
|
+
|
45
|
+
def node_range_with_surrounding_space(node)
|
46
|
+
range = node_range(node)
|
47
|
+
range_by_whole_lines(range, include_final_newline: true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -21,7 +21,7 @@ module RuboCop
|
|
21
21
|
|
22
22
|
# Decorator of a YARD code object for working with documented rspec cops
|
23
23
|
class CodeObject
|
24
|
-
|
24
|
+
COP_CLASS_NAME = 'RuboCop::Cop::RSpec::Cop'
|
25
25
|
RSPEC_NAMESPACE = 'RuboCop::Cop::RSpec'
|
26
26
|
|
27
27
|
def initialize(yardoc)
|
@@ -68,11 +68,7 @@ module RuboCop
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def cop_subclass?
|
71
|
-
|
72
|
-
# RuboCop::Cop::WorkaroundCop are shown as having RuboCop::Cop as
|
73
|
-
# superclass, while all the following classes are listed as having
|
74
|
-
# RuboCop::Cop::RSpec::Cop as their superclass.
|
75
|
-
COP_CLASS_NAMES.include?(yardoc.superclass.path)
|
71
|
+
yardoc.superclass.path == COP_CLASS_NAME
|
76
72
|
end
|
77
73
|
|
78
74
|
def abstract?
|
@@ -4,7 +4,7 @@ module RuboCop
|
|
4
4
|
module RSpec
|
5
5
|
# Wrapper for RSpec examples
|
6
6
|
class Example < Concept
|
7
|
-
def_node_matcher :extract_doc_string, '(send _ _ $
|
7
|
+
def_node_matcher :extract_doc_string, '(send _ _ $_ ...)'
|
8
8
|
def_node_matcher :extract_metadata, '(send _ _ _ $...)'
|
9
9
|
def_node_matcher :extract_implementation, '(block send args $_)'
|
10
10
|
|
data/lib/rubocop/rspec/hook.rb
CHANGED
@@ -4,21 +4,24 @@ module RuboCop
|
|
4
4
|
module RSpec
|
5
5
|
# Wrapper for RSpec hook
|
6
6
|
class Hook < Concept
|
7
|
-
|
8
|
-
|
7
|
+
def_node_matcher :extract_metadata, <<~PATTERN
|
8
|
+
(block
|
9
|
+
{
|
10
|
+
(send _ _ #valid_scope? $...)
|
11
|
+
(send _ _ $...)
|
12
|
+
}
|
13
|
+
...
|
14
|
+
)
|
15
|
+
PATTERN
|
9
16
|
|
10
17
|
def name
|
11
18
|
node.method_name
|
12
19
|
end
|
13
20
|
|
14
21
|
def knowable_scope?
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def valid_scope?
|
21
|
-
STANDARDIZED_SCOPES.include?(scope)
|
22
|
+
scope_argument.nil? ||
|
23
|
+
scope_argument.sym_type? ||
|
24
|
+
scope_argument.hash_type?
|
22
25
|
end
|
23
26
|
|
24
27
|
def example?
|
@@ -26,17 +29,47 @@ module RuboCop
|
|
26
29
|
end
|
27
30
|
|
28
31
|
def scope
|
32
|
+
return :each if scope_argument&.hash_type?
|
33
|
+
|
29
34
|
case scope_name
|
30
35
|
when nil, :each, :example then :each
|
31
36
|
when :context, :all then :context
|
32
37
|
when :suite then :suite
|
33
|
-
else
|
34
|
-
scope_name
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
41
|
+
def metadata
|
42
|
+
(extract_metadata(node) || [])
|
43
|
+
.map { |meta| transform_metadata(meta) }
|
44
|
+
.flatten
|
45
|
+
.inject(&:merge)
|
46
|
+
end
|
47
|
+
|
38
48
|
private
|
39
49
|
|
50
|
+
def valid_scope?(node)
|
51
|
+
node&.sym_type? && Hooks::Scopes::ALL.include?(node.value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def transform_metadata(meta)
|
55
|
+
if meta.sym_type?
|
56
|
+
{ meta => true }
|
57
|
+
else
|
58
|
+
# This check is to be able to compare those two hooks:
|
59
|
+
#
|
60
|
+
# before(:example, :special) { ... }
|
61
|
+
# before(:example, special: true) { ... }
|
62
|
+
#
|
63
|
+
# In the second case it's a node with a pair that has a value
|
64
|
+
# of a `true_type?`.
|
65
|
+
meta.pairs.map { |pair| { pair.key => transform_true(pair.value) } }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def transform_true(node)
|
70
|
+
node.true_type? ? true : node
|
71
|
+
end
|
72
|
+
|
40
73
|
def scope_name
|
41
74
|
scope_argument.to_a.first
|
42
75
|
end
|
@@ -28,6 +28,14 @@ module RuboCop
|
|
28
28
|
"(block #{send_pattern} ...)"
|
29
29
|
end
|
30
30
|
|
31
|
+
def block_pass_pattern
|
32
|
+
"(send #{RSPEC} #{node_pattern_union} _ block_pass)"
|
33
|
+
end
|
34
|
+
|
35
|
+
def block_or_block_pass_pattern
|
36
|
+
"{#{block_pattern} #{block_pass_pattern}}"
|
37
|
+
end
|
38
|
+
|
31
39
|
def send_pattern
|
32
40
|
"(send #{RSPEC} #{node_pattern_union} ...)"
|
33
41
|
end
|
@@ -94,6 +102,18 @@ module RuboCop
|
|
94
102
|
append_after
|
95
103
|
]
|
96
104
|
)
|
105
|
+
|
106
|
+
module Scopes
|
107
|
+
ALL = SelectorSet.new(
|
108
|
+
%i[
|
109
|
+
each
|
110
|
+
example
|
111
|
+
context
|
112
|
+
all
|
113
|
+
suite
|
114
|
+
]
|
115
|
+
)
|
116
|
+
end
|
97
117
|
end
|
98
118
|
|
99
119
|
module Helpers
|