rubocop-rspec 2.13.2 → 2.14.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 +402 -383
- data/CODE_OF_CONDUCT.md +4 -4
- data/MIT-LICENSE.md +1 -2
- data/config/default.yml +65 -4
- data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +106 -0
- data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +85 -0
- data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +5 -82
- data/lib/rubocop/cop/rspec/change_by_zero.rb +1 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +4 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +32 -0
- data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +99 -0
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +2 -2
- data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +1 -19
- data/lib/rubocop/cop/rspec/implicit_subject.rb +86 -19
- data/lib/rubocop/cop/rspec/let_before_examples.rb +15 -1
- data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +80 -0
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +48 -1
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +23 -0
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +42 -9
- data/lib/rubocop/cop/rspec/pending.rb +2 -11
- data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +135 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
- data/lib/rubocop/cop/rspec/sort_metadata.rb +102 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +5 -0
- data/lib/rubocop/rspec/factory_bot/language.rb +20 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +2 -0
- metadata +9 -2
@@ -54,25 +54,7 @@ module RuboCop
|
|
54
54
|
|
55
55
|
MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
|
56
56
|
|
57
|
-
RESTRICT_ON_SEND =
|
58
|
-
attributes_for
|
59
|
-
attributes_for_list
|
60
|
-
attributes_for_pair
|
61
|
-
build
|
62
|
-
build_list
|
63
|
-
build_pair
|
64
|
-
build_stubbed
|
65
|
-
build_stubbed_list
|
66
|
-
build_stubbed_pair
|
67
|
-
create
|
68
|
-
create_list
|
69
|
-
create_pair
|
70
|
-
generate
|
71
|
-
generate_list
|
72
|
-
null
|
73
|
-
null_list
|
74
|
-
null_pair
|
75
|
-
].to_set.freeze
|
57
|
+
RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS
|
76
58
|
|
77
59
|
def on_send(node)
|
78
60
|
return unless factory_bot?(node.receiver)
|
@@ -42,12 +42,45 @@ module RuboCop
|
|
42
42
|
# # good
|
43
43
|
# it { expect(subject).to be_truthy }
|
44
44
|
#
|
45
|
+
# @example `EnforcedStyle: require_implicit`
|
46
|
+
# # bad
|
47
|
+
# it { expect(subject).to be_truthy }
|
48
|
+
#
|
49
|
+
# # good
|
50
|
+
# it { is_expected.to be_truthy }
|
51
|
+
#
|
52
|
+
# # bad
|
53
|
+
# it do
|
54
|
+
# expect(subject).to be_truthy
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # good
|
58
|
+
# it do
|
59
|
+
# is_expected.to be_truthy
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # good
|
63
|
+
# it { expect(named_subject).to be_truthy }
|
64
|
+
#
|
45
65
|
class ImplicitSubject < Base
|
46
66
|
extend AutoCorrector
|
47
67
|
include ConfigurableEnforcedStyle
|
48
68
|
|
49
|
-
|
50
|
-
|
69
|
+
MSG_REQUIRE_EXPLICIT = "Don't use implicit subject."
|
70
|
+
|
71
|
+
MSG_REQUIRE_IMPLICIT = "Don't use explicit subject."
|
72
|
+
|
73
|
+
RESTRICT_ON_SEND = %i[
|
74
|
+
expect
|
75
|
+
is_expected
|
76
|
+
should
|
77
|
+
should_not
|
78
|
+
].freeze
|
79
|
+
|
80
|
+
# @!method explicit_unnamed_subject?(node)
|
81
|
+
def_node_matcher :explicit_unnamed_subject?, <<-PATTERN
|
82
|
+
(send nil? :expect (send nil? :subject))
|
83
|
+
PATTERN
|
51
84
|
|
52
85
|
# @!method implicit_subject?(node)
|
53
86
|
def_node_matcher :implicit_subject?, <<-PATTERN
|
@@ -55,8 +88,7 @@ module RuboCop
|
|
55
88
|
PATTERN
|
56
89
|
|
57
90
|
def on_send(node)
|
58
|
-
return unless
|
59
|
-
return if valid_usage?(node)
|
91
|
+
return unless invalid?(node)
|
60
92
|
|
61
93
|
add_offense(node) do |corrector|
|
62
94
|
autocorrect(corrector, node)
|
@@ -66,32 +98,67 @@ module RuboCop
|
|
66
98
|
private
|
67
99
|
|
68
100
|
def autocorrect(corrector, node)
|
69
|
-
replacement = 'expect(subject)'
|
70
101
|
case node.method_name
|
102
|
+
when :expect
|
103
|
+
corrector.replace(node, 'is_expected')
|
104
|
+
when :is_expected
|
105
|
+
corrector.replace(node.location.selector, 'expect(subject)')
|
71
106
|
when :should
|
72
|
-
|
107
|
+
corrector.replace(node.location.selector, 'expect(subject).to')
|
73
108
|
when :should_not
|
74
|
-
|
109
|
+
corrector.replace(node.location.selector, 'expect(subject).not_to')
|
75
110
|
end
|
76
|
-
|
77
|
-
corrector.replace(node.loc.selector, replacement)
|
78
111
|
end
|
79
112
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
113
|
+
def message(_node)
|
114
|
+
case style
|
115
|
+
when :require_implicit
|
116
|
+
MSG_REQUIRE_IMPLICIT
|
117
|
+
else
|
118
|
+
MSG_REQUIRE_EXPLICIT
|
119
|
+
end
|
85
120
|
end
|
86
121
|
|
87
|
-
def
|
122
|
+
def invalid?(node)
|
88
123
|
case style
|
124
|
+
when :require_implicit
|
125
|
+
explicit_unnamed_subject?(node)
|
126
|
+
when :disallow
|
127
|
+
implicit_subject_in_non_its?(node)
|
89
128
|
when :single_line_only
|
90
|
-
|
129
|
+
implicit_subject_in_non_its_and_non_single_line?(node)
|
91
130
|
when :single_statement_only
|
92
|
-
|
93
|
-
|
94
|
-
|
131
|
+
implicit_subject_in_non_its_and_non_single_statement?(node)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def implicit_subject_in_non_its?(node)
|
136
|
+
implicit_subject?(node) && !its?(node)
|
137
|
+
end
|
138
|
+
|
139
|
+
def implicit_subject_in_non_its_and_non_single_line?(node)
|
140
|
+
implicit_subject_in_non_its?(node) && !single_line?(node)
|
141
|
+
end
|
142
|
+
|
143
|
+
def implicit_subject_in_non_its_and_non_single_statement?(node)
|
144
|
+
implicit_subject_in_non_its?(node) && !single_statement?(node)
|
145
|
+
end
|
146
|
+
|
147
|
+
def its?(node)
|
148
|
+
example_of(node)&.method?(:its)
|
149
|
+
end
|
150
|
+
|
151
|
+
def single_line?(node)
|
152
|
+
example_of(node)&.single_line?
|
153
|
+
end
|
154
|
+
|
155
|
+
def single_statement?(node)
|
156
|
+
!example_of(node)&.body&.begin_type?
|
157
|
+
end
|
158
|
+
|
159
|
+
def example_of(node)
|
160
|
+
node.each_ancestor.find do |ancestor|
|
161
|
+
example?(ancestor)
|
95
162
|
end
|
96
163
|
end
|
97
164
|
end
|
@@ -43,6 +43,14 @@ module RuboCop
|
|
43
43
|
}
|
44
44
|
PATTERN
|
45
45
|
|
46
|
+
# @!method include_examples?(node)
|
47
|
+
def_node_matcher :include_examples?, <<~PATTERN
|
48
|
+
{
|
49
|
+
#{block_pattern(':include_examples')}
|
50
|
+
#{send_pattern(':include_examples')}
|
51
|
+
}
|
52
|
+
PATTERN
|
53
|
+
|
46
54
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
47
55
|
return unless example_group_with_body?(node)
|
48
56
|
|
@@ -51,6 +59,10 @@ module RuboCop
|
|
51
59
|
|
52
60
|
private
|
53
61
|
|
62
|
+
def example_group_with_include_examples?(body)
|
63
|
+
body.children.any? { |sibling| include_examples?(sibling) }
|
64
|
+
end
|
65
|
+
|
54
66
|
def multiline_block?(block)
|
55
67
|
block.begin_type?
|
56
68
|
end
|
@@ -59,11 +71,13 @@ module RuboCop
|
|
59
71
|
first_example = find_first_example(node)
|
60
72
|
return unless first_example
|
61
73
|
|
74
|
+
correct = !example_group_with_include_examples?(node)
|
75
|
+
|
62
76
|
first_example.right_siblings.each do |sibling|
|
63
77
|
next unless let?(sibling)
|
64
78
|
|
65
79
|
add_offense(sibling) do |corrector|
|
66
|
-
autocorrect(corrector, sibling, first_example)
|
80
|
+
autocorrect(corrector, sibling, first_example) if correct
|
67
81
|
end
|
68
82
|
end
|
69
83
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Help methods for capybara.
|
7
|
+
module CapybaraHelp
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# @param node [RuboCop::AST::SendNode]
|
11
|
+
# @param locator [String]
|
12
|
+
# @param element [String]
|
13
|
+
# @return [Boolean]
|
14
|
+
def specific_option?(node, locator, element)
|
15
|
+
attrs = CssSelector.attributes(locator).keys
|
16
|
+
return false unless replaceable_element?(node, element, attrs)
|
17
|
+
|
18
|
+
attrs.all? do |attr|
|
19
|
+
CssSelector.specific_options?(element, attr)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param locator [String]
|
24
|
+
# @return [Boolean]
|
25
|
+
def specific_pseudo_classes?(locator)
|
26
|
+
CssSelector.pseudo_classes(locator).all? do |pseudo_class|
|
27
|
+
replaceable_pseudo_class?(pseudo_class, locator)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param pseudo_class [String]
|
32
|
+
# @param locator [String]
|
33
|
+
# @return [Boolean]
|
34
|
+
def replaceable_pseudo_class?(pseudo_class, locator)
|
35
|
+
return false unless CssSelector.specific_pesudo_classes?(pseudo_class)
|
36
|
+
|
37
|
+
case pseudo_class
|
38
|
+
when 'not()' then replaceable_pseudo_class_not?(locator)
|
39
|
+
else true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param locator [String]
|
44
|
+
# @return [Boolean]
|
45
|
+
def replaceable_pseudo_class_not?(locator)
|
46
|
+
locator.scan(/not\(.*?\)/).all? do |negation|
|
47
|
+
CssSelector.attributes(negation).values.all? do |v|
|
48
|
+
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param node [RuboCop::AST::SendNode]
|
54
|
+
# @param element [String]
|
55
|
+
# @param attrs [Array<String>]
|
56
|
+
# @return [Boolean]
|
57
|
+
def replaceable_element?(node, element, attrs)
|
58
|
+
case element
|
59
|
+
when 'link' then replaceable_to_link?(node, attrs)
|
60
|
+
else true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param node [RuboCop::AST::SendNode]
|
65
|
+
# @param attrs [Array<String>]
|
66
|
+
# @return [Boolean]
|
67
|
+
def replaceable_to_link?(node, attrs)
|
68
|
+
include_option?(node, :href) || attrs.include?('href')
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param node [RuboCop::AST::SendNode]
|
72
|
+
# @param option [Symbol]
|
73
|
+
# @return [Boolean]
|
74
|
+
def include_option?(node, option)
|
75
|
+
node.each_descendant(:sym).find { |opt| opt.value == option }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -10,9 +10,56 @@ module RuboCop
|
|
10
10
|
id class style visible obscured exact exact_text normalize_ws match
|
11
11
|
wait filter_set focused
|
12
12
|
].freeze
|
13
|
+
SPECIFIC_OPTIONS = {
|
14
|
+
'button' => (
|
15
|
+
COMMON_OPTIONS + %w[disabled name value title type]
|
16
|
+
).freeze,
|
17
|
+
'link' => (
|
18
|
+
COMMON_OPTIONS + %w[href alt title download]
|
19
|
+
).freeze,
|
20
|
+
'table' => (
|
21
|
+
COMMON_OPTIONS + %w[
|
22
|
+
caption with_cols cols with_rows rows
|
23
|
+
]
|
24
|
+
).freeze,
|
25
|
+
'select' => (
|
26
|
+
COMMON_OPTIONS + %w[
|
27
|
+
disabled name placeholder options enabled_options
|
28
|
+
disabled_options selected with_selected multiple with_options
|
29
|
+
]
|
30
|
+
).freeze,
|
31
|
+
'field' => (
|
32
|
+
COMMON_OPTIONS + %w[
|
33
|
+
checked unchecked disabled valid name placeholder
|
34
|
+
validation_message readonly with type multiple
|
35
|
+
]
|
36
|
+
).freeze
|
37
|
+
}.freeze
|
38
|
+
SPECIFIC_PSEUDO_CLASSES = %w[
|
39
|
+
not() disabled enabled checked unchecked
|
40
|
+
].freeze
|
13
41
|
|
14
42
|
module_function
|
15
43
|
|
44
|
+
# @param element [String]
|
45
|
+
# @param attribute [String]
|
46
|
+
# @return [Boolean]
|
47
|
+
# @example
|
48
|
+
# specific_pesudo_classes?('button', 'name') # => true
|
49
|
+
# specific_pesudo_classes?('link', 'invalid') # => false
|
50
|
+
def specific_options?(element, attribute)
|
51
|
+
SPECIFIC_OPTIONS.fetch(element, []).include?(attribute)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param pseudo_class [String]
|
55
|
+
# @return [Boolean]
|
56
|
+
# @example
|
57
|
+
# specific_pesudo_classes?('disabled') # => true
|
58
|
+
# specific_pesudo_classes?('first-of-type') # => false
|
59
|
+
def specific_pesudo_classes?(pseudo_class)
|
60
|
+
SPECIFIC_PSEUDO_CLASSES.include?(pseudo_class)
|
61
|
+
end
|
62
|
+
|
16
63
|
# @param selector [String]
|
17
64
|
# @return [Boolean]
|
18
65
|
# @example
|
@@ -75,7 +122,7 @@ module RuboCop
|
|
75
122
|
# multiple_selectors?('a.cls b#id') # => true
|
76
123
|
# multiple_selectors?('a.cls') # => false
|
77
124
|
def multiple_selectors?(selector)
|
78
|
-
selector.match?(/[
|
125
|
+
selector.match?(/[ >,+~]/)
|
79
126
|
end
|
80
127
|
|
81
128
|
# @param value [String]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Helps check offenses with variable definitions
|
7
|
+
module SkipOrPending
|
8
|
+
extend RuboCop::NodePattern::Macros
|
9
|
+
|
10
|
+
# @!method skipped_in_metadata?(node)
|
11
|
+
def_node_matcher :skipped_in_metadata?, <<-PATTERN
|
12
|
+
{
|
13
|
+
(send _ _ <#skip_or_pending? ...>)
|
14
|
+
(send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
|
15
|
+
}
|
16
|
+
PATTERN
|
17
|
+
|
18
|
+
# @!method skip_or_pending?(node)
|
19
|
+
def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -28,7 +28,37 @@ module RuboCop
|
|
28
28
|
# expect(a?).to be(true)
|
29
29
|
# end
|
30
30
|
#
|
31
|
+
# This cop can be customized with an allowed expectation methods pattern
|
32
|
+
# with an `AllowedPatterns` option. ^expect_ and ^assert_ are allowed
|
33
|
+
# by default.
|
34
|
+
#
|
35
|
+
# @example `AllowedPatterns` configuration
|
36
|
+
#
|
37
|
+
# # .rubocop.yml
|
38
|
+
# # RSpec/NoExpectationExample:
|
39
|
+
# # AllowedPatterns:
|
40
|
+
# # - ^expect_
|
41
|
+
# # - ^assert_
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# # bad
|
45
|
+
# it do
|
46
|
+
# not_expect_something
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # good
|
50
|
+
# it do
|
51
|
+
# expect_something
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# it do
|
55
|
+
# assert_something
|
56
|
+
# end
|
57
|
+
#
|
31
58
|
class NoExpectationExample < Base
|
59
|
+
include AllowedPattern
|
60
|
+
include SkipOrPending
|
61
|
+
|
32
62
|
MSG = 'No expectation found in this example.'
|
33
63
|
|
34
64
|
# @!method regular_or_focused_example?(node)
|
@@ -41,26 +71,29 @@ module RuboCop
|
|
41
71
|
}
|
42
72
|
PATTERN
|
43
73
|
|
44
|
-
# @!method
|
74
|
+
# @!method includes_expectation?(node)
|
45
75
|
# @param [RuboCop::AST::Node] node
|
46
76
|
# @return [Boolean]
|
47
|
-
def_node_search
|
48
|
-
|
49
|
-
|
50
|
-
|
77
|
+
def_node_search :includes_expectation?, <<~PATTERN
|
78
|
+
{
|
79
|
+
#{send_pattern('#Expectations.all')}
|
80
|
+
(send nil? `#matches_allowed_pattern?)
|
81
|
+
}
|
82
|
+
PATTERN
|
51
83
|
|
52
|
-
# @!method
|
84
|
+
# @!method includes_skip_example?(node)
|
53
85
|
# @param [RuboCop::AST::Node] node
|
54
86
|
# @return [Boolean]
|
55
|
-
def_node_search :
|
87
|
+
def_node_search :includes_skip_example?, <<~PATTERN
|
56
88
|
(send nil? {:pending :skip} ...)
|
57
89
|
PATTERN
|
58
90
|
|
59
91
|
# @param [RuboCop::AST::BlockNode] node
|
60
92
|
def on_block(node)
|
61
93
|
return unless regular_or_focused_example?(node)
|
62
|
-
return if
|
63
|
-
return if
|
94
|
+
return if includes_expectation?(node)
|
95
|
+
return if includes_skip_example?(node)
|
96
|
+
return if skipped_in_metadata?(node.send_node)
|
64
97
|
|
65
98
|
add_offense(node)
|
66
99
|
end
|
@@ -33,6 +33,8 @@ module RuboCop
|
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
class Pending < Base
|
36
|
+
include SkipOrPending
|
37
|
+
|
36
38
|
MSG = 'Pending spec found.'
|
37
39
|
|
38
40
|
# @!method skippable?(node)
|
@@ -41,17 +43,6 @@ module RuboCop
|
|
41
43
|
{#ExampleGroups.regular #Examples.regular}
|
42
44
|
PATTERN
|
43
45
|
|
44
|
-
# @!method skipped_in_metadata?(node)
|
45
|
-
def_node_matcher :skipped_in_metadata?, <<-PATTERN
|
46
|
-
{
|
47
|
-
(send _ _ <#skip_or_pending? ...>)
|
48
|
-
(send _ _ ... (hash <(pair #skip_or_pending? { true str }) ...>))
|
49
|
-
}
|
50
|
-
PATTERN
|
51
|
-
|
52
|
-
# @!method skip_or_pending?(node)
|
53
|
-
def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
|
54
|
-
|
55
46
|
# @!method pending_block?(node)
|
56
47
|
def_node_matcher :pending_block?,
|
57
48
|
send_pattern(<<~PATTERN)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Rails
|
7
|
+
# Identifies redundant spec type.
|
8
|
+
#
|
9
|
+
# After setting up rspec-rails, you will have enabled
|
10
|
+
# `config.infer_spec_type_from_file_location!` by default in
|
11
|
+
# spec/rails_helper.rb. This cop works in conjunction with this config.
|
12
|
+
# If you disable this config, disable this cop as well.
|
13
|
+
#
|
14
|
+
# @safety
|
15
|
+
# This cop is marked as unsafe because
|
16
|
+
# `config.infer_spec_type_from_file_location!` may not be enabled.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# # bad
|
20
|
+
# # spec/models/user_spec.rb
|
21
|
+
# RSpec.describe User, type: :model do
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# # spec/models/user_spec.rb
|
26
|
+
# RSpec.describe User do
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# # spec/models/user_spec.rb
|
31
|
+
# RSpec.describe User, type: :common do
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @example `Inferences` configuration
|
35
|
+
# # .rubocop.yml
|
36
|
+
# # RSpec/InferredSpecType:
|
37
|
+
# # Inferences:
|
38
|
+
# # services: service
|
39
|
+
#
|
40
|
+
# # bad
|
41
|
+
# # spec/services/user_spec.rb
|
42
|
+
# RSpec.describe User, type: :service do
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# # good
|
46
|
+
# # spec/services/user_spec.rb
|
47
|
+
# RSpec.describe User do
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # good
|
51
|
+
# # spec/services/user_spec.rb
|
52
|
+
# RSpec.describe User, type: :common do
|
53
|
+
# end
|
54
|
+
class InferredSpecType < Base
|
55
|
+
extend AutoCorrector
|
56
|
+
|
57
|
+
MSG = 'Remove redundant spec type.'
|
58
|
+
|
59
|
+
# @param [RuboCop::AST::BlockNode] node
|
60
|
+
def on_block(node)
|
61
|
+
return unless example_group?(node)
|
62
|
+
|
63
|
+
pair_node = describe_with_type(node)
|
64
|
+
return unless pair_node
|
65
|
+
return unless inferred_type?(pair_node)
|
66
|
+
|
67
|
+
removable_node = detect_removable_node(pair_node)
|
68
|
+
add_offense(removable_node) do |corrector|
|
69
|
+
autocorrect(corrector, removable_node)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
alias on_numblock on_block
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @!method describe_with_type(node)
|
77
|
+
# @param [RuboCop::AST::BlockNode] node
|
78
|
+
# @return [RuboCop::AST::PairNode, nil]
|
79
|
+
def_node_matcher :describe_with_type, <<~PATTERN
|
80
|
+
(block
|
81
|
+
(send #rspec? #ExampleGroups.all
|
82
|
+
...
|
83
|
+
(hash <$(pair (sym :type) sym) ...>)
|
84
|
+
)
|
85
|
+
...
|
86
|
+
)
|
87
|
+
PATTERN
|
88
|
+
|
89
|
+
# @param [RuboCop::AST::Corrector] corrector
|
90
|
+
# @param [RuboCop::AST::Node] node
|
91
|
+
def autocorrect(corrector, node)
|
92
|
+
corrector.remove(
|
93
|
+
node.location.expression.with(
|
94
|
+
begin_pos: node.left_sibling.location.expression.end_pos
|
95
|
+
)
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param [RuboCop::AST::PairNode] node
|
100
|
+
# @return [RuboCop::AST::Node]
|
101
|
+
def detect_removable_node(node)
|
102
|
+
if node.parent.pairs.size == 1
|
103
|
+
node.parent
|
104
|
+
else
|
105
|
+
node
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [String]
|
110
|
+
def file_path
|
111
|
+
processed_source.file_path
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [RuboCop::AST::PairNode] node
|
115
|
+
# @return [Boolean]
|
116
|
+
def inferred_type?(node)
|
117
|
+
inferred_type_from_file_path.inspect == node.value.source
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Symbol, nil]
|
121
|
+
def inferred_type_from_file_path
|
122
|
+
inferences.find do |prefix, type|
|
123
|
+
break type.to_sym if file_path.include?("spec/#{prefix}/")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [Hash]
|
128
|
+
def inferences
|
129
|
+
cop_config['Inferences'] || {}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -47,7 +47,7 @@ module RuboCop
|
|
47
47
|
#
|
48
48
|
class RepeatedIncludeExample < Base
|
49
49
|
MSG = 'Repeated include of shared_examples %<name>s ' \
|
50
|
-
|
50
|
+
'on line(s) %<repeat>s'
|
51
51
|
|
52
52
|
# @!method several_include_examples?(node)
|
53
53
|
def_node_matcher :several_include_examples?, <<-PATTERN
|