rubocop-rspec 1.37.1 → 1.41.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 +53 -0
- data/CODE_OF_CONDUCT.md +17 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +3 -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 +20 -5
- 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/attribute_defined_statically.rb +5 -2
- 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 +16 -11
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -2
- 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/return_from_stub.rb +3 -2
- 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 +23 -51
- 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/example_group.rb +21 -49
- data/lib/rubocop/rspec/factory_bot.rb +7 -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 +5 -1
- data/lib/rubocop/rspec/top_level_group.rb +44 -0
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +20 -11
- data/lib/rubocop/rspec/util.rb +0 -19
@@ -40,20 +40,21 @@ module RuboCop
|
|
40
40
|
MSG_BLOCK = 'Use block for static values.'
|
41
41
|
|
42
42
|
def_node_search :contains_stub?, '(send nil? :receive (...))'
|
43
|
+
def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
|
43
44
|
def_node_search :and_return_value, <<-PATTERN
|
44
45
|
$(send _ :and_return $(...))
|
45
46
|
PATTERN
|
46
47
|
|
47
48
|
def on_send(node)
|
48
|
-
return unless contains_stub?(node)
|
49
49
|
return unless style == :block
|
50
|
+
return unless contains_stub?(node)
|
50
51
|
|
51
52
|
check_and_return_call(node)
|
52
53
|
end
|
53
54
|
|
54
55
|
def on_block(node)
|
55
|
-
return unless contains_stub?(node)
|
56
56
|
return unless style == :and_return
|
57
|
+
return unless stub_with_block?(node)
|
57
58
|
|
58
59
|
check_block_body(node)
|
59
60
|
end
|
@@ -35,6 +35,15 @@ module RuboCop
|
|
35
35
|
check_let_declarations(node.body)
|
36
36
|
end
|
37
37
|
|
38
|
+
def autocorrect(node)
|
39
|
+
lambda do |corrector|
|
40
|
+
first_let = find_first_let(node.parent)
|
41
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
42
|
+
node, corrector, processed_source
|
43
|
+
).move_after(first_let)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
38
47
|
private
|
39
48
|
|
40
49
|
def check_let_declarations(body)
|
@@ -47,6 +56,10 @@ module RuboCop
|
|
47
56
|
add_offense(node)
|
48
57
|
end
|
49
58
|
end
|
59
|
+
|
60
|
+
def find_first_let(node)
|
61
|
+
node.children.find { |child| let?(child) }
|
62
|
+
end
|
50
63
|
end
|
51
64
|
end
|
52
65
|
end
|
@@ -23,25 +23,43 @@ module RuboCop
|
|
23
23
|
# end
|
24
24
|
#
|
25
25
|
class ScatteredSetup < Cop
|
26
|
-
MSG = 'Do not define multiple hooks in the same
|
26
|
+
MSG = 'Do not define multiple `%<hook_name>s` hooks in the same '\
|
27
|
+
'example group (also defined on %<lines>s).'
|
27
28
|
|
28
29
|
def on_block(node)
|
29
30
|
return unless example_group?(node)
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
repeated_hooks(node).each do |occurrences|
|
33
|
+
lines = occurrences.map(&:first_line)
|
34
|
+
|
35
|
+
occurrences.each do |occurrence|
|
36
|
+
lines_except_current = lines - [occurrence.first_line]
|
37
|
+
message = format(MSG, hook_name: occurrences.first.method_name,
|
38
|
+
lines: lines_msg(lines_except_current))
|
39
|
+
add_offense(occurrence, message: message)
|
40
|
+
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
RuboCop::RSpec::ExampleGroup.new(node)
|
44
|
+
def repeated_hooks(node)
|
45
|
+
hooks = RuboCop::RSpec::ExampleGroup.new(node)
|
38
46
|
.hooks
|
39
|
-
.select
|
40
|
-
.group_by { |hook| [hook.name, hook.scope] }
|
47
|
+
.select(&:knowable_scope?)
|
48
|
+
.group_by { |hook| [hook.name, hook.scope, hook.metadata] }
|
41
49
|
.values
|
42
50
|
.reject(&:one?)
|
43
|
-
|
44
|
-
|
51
|
+
|
52
|
+
hooks.map do |hook|
|
53
|
+
hook.map(&:to_node)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def lines_msg(numbers)
|
58
|
+
if numbers.size == 1
|
59
|
+
"line #{numbers.first}"
|
60
|
+
else
|
61
|
+
"lines #{numbers.join(', ')}"
|
62
|
+
end
|
45
63
|
end
|
46
64
|
end
|
47
65
|
end
|
@@ -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
|
@@ -20,6 +22,8 @@ module RuboCop
|
|
20
22
|
# end
|
21
23
|
#
|
22
24
|
class SubjectStub < Cop
|
25
|
+
include RuboCop::RSpec::TopLevelGroup
|
26
|
+
|
23
27
|
MSG = 'Do not stub methods of the object under test.'
|
24
28
|
|
25
29
|
# @!method subject(node)
|
@@ -73,72 +77,40 @@ module RuboCop
|
|
73
77
|
} ...)
|
74
78
|
PATTERN
|
75
79
|
|
76
|
-
def
|
77
|
-
|
80
|
+
def on_top_level_group(node)
|
81
|
+
@explicit_subjects = find_all_explicit_subjects(node)
|
78
82
|
|
79
|
-
|
83
|
+
find_subject_expectations(node) do |stub|
|
80
84
|
add_offense(stub)
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
84
88
|
private
|
85
89
|
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
95
|
-
end
|
96
|
-
|
97
|
-
# Find a subject message expectation
|
98
|
-
#
|
99
|
-
# @param node [RuboCop::Node]
|
100
|
-
# @param subject_name [Symbol] name of subject
|
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)
|
90
|
+
def find_all_explicit_subjects(node)
|
91
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
92
|
+
name = subject(child)
|
93
|
+
next unless name
|
109
94
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
95
|
+
outer_example_group = child.each_ancestor.find do |a|
|
96
|
+
example_group?(a)
|
97
|
+
end
|
115
98
|
|
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)
|
99
|
+
h[outer_example_group] ||= []
|
100
|
+
h[outer_example_group] << name
|
124
101
|
end
|
125
102
|
end
|
126
103
|
|
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
|
104
|
+
def find_subject_expectations(node, subject_names = [], &block)
|
105
|
+
subject_names = @explicit_subjects[node] if @explicit_subjects[node]
|
137
106
|
|
138
|
-
|
107
|
+
expectation_detected = (subject_names + [:subject]).any? do |name|
|
108
|
+
message_expectation?(node, name)
|
109
|
+
end
|
110
|
+
return yield(node) if expectation_detected
|
139
111
|
|
140
112
|
node.each_child_node do |child|
|
141
|
-
|
113
|
+
find_subject_expectations(child, subject_names, &block)
|
142
114
|
end
|
143
115
|
end
|
144
116
|
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
|
|