rubocop-rspec 2.13.2 → 2.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|