rubocop-rspec 2.12.0 → 2.13.1
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 +118 -86
- data/config/default.yml +44 -6
- data/config/obsoletion.yml +14 -0
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +8 -9
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +8 -9
- data/lib/rubocop/cop/rspec/any_instance.rb +1 -0
- data/lib/rubocop/cop/rspec/around_block.rb +26 -3
- data/lib/rubocop/cop/rspec/be.rb +0 -1
- data/lib/rubocop/cop/rspec/be_eq.rb +0 -1
- data/lib/rubocop/cop/rspec/be_eql.rb +0 -1
- data/lib/rubocop/cop/rspec/before_after_all.rb +1 -0
- data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +9 -3
- data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -1
- data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +86 -0
- data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +91 -10
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +0 -1
- data/lib/rubocop/cop/rspec/change_by_zero.rb +60 -5
- data/lib/rubocop/cop/rspec/class_check.rb +101 -0
- data/lib/rubocop/cop/rspec/context_method.rb +2 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +49 -18
- data/lib/rubocop/cop/rspec/describe_class.rb +1 -1
- data/lib/rubocop/cop/rspec/describe_method.rb +1 -0
- data/lib/rubocop/cop/rspec/described_class.rb +4 -14
- data/lib/rubocop/cop/rspec/dialect.rb +1 -0
- data/lib/rubocop/cop/rspec/empty_example_group.rb +19 -4
- data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +4 -9
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +2 -1
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +32 -2
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -1
- data/lib/rubocop/cop/rspec/example_length.rb +2 -1
- data/lib/rubocop/cop/rspec/example_without_description.rb +2 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +2 -1
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +3 -0
- data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +4 -1
- data/lib/rubocop/cop/rspec/expect_output.rb +1 -0
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +2 -1
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +26 -12
- data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +1 -0
- data/lib/rubocop/cop/rspec/file_path.rb +6 -3
- data/lib/rubocop/cop/rspec/focus.rb +18 -0
- data/lib/rubocop/cop/rspec/hook_argument.rb +7 -2
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +10 -9
- data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +0 -1
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -0
- data/lib/rubocop/cop/rspec/implicit_expect.rb +0 -2
- data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_variable.rb +0 -1
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +16 -0
- data/lib/rubocop/cop/rspec/leading_subject.rb +15 -15
- data/lib/rubocop/cop/rspec/let_before_examples.rb +7 -8
- data/lib/rubocop/cop/rspec/let_setup.rb +4 -4
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +2 -1
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +99 -0
- data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -0
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -5
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -3
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -2
- data/lib/rubocop/cop/rspec/named_subject.rb +2 -1
- data/lib/rubocop/cop/rspec/nested_groups.rb +45 -25
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +64 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
- data/lib/rubocop/cop/rspec/pending.rb +1 -0
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -1
- data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -2
- data/lib/rubocop/cop/rspec/receive_counts.rb +14 -15
- data/lib/rubocop/cop/rspec/receive_never.rb +4 -5
- data/lib/rubocop/cop/rspec/repeated_description.rb +25 -26
- data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +28 -29
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +28 -29
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +32 -33
- data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
- data/lib/rubocop/cop/rspec/scattered_let.rb +1 -5
- data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +0 -1
- data/lib/rubocop/cop/rspec/subject_declaration.rb +0 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +2 -2
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +15 -15
- data/lib/rubocop/cop/rspec/variable_definition.rb +1 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +6 -7
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -0
- data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
- data/lib/rubocop/cop/rspec/yield.rb +2 -1
- data/lib/rubocop/cop/rspec_cops.rb +3 -0
- data/lib/rubocop/rspec/config_formatter.rb +14 -3
- data/lib/rubocop/rspec/inject.rb +1 -3
- data/lib/rubocop/rspec/language/node_pattern.rb +4 -0
- data/lib/rubocop/rspec/language.rb +6 -1
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +2 -2
- data/lib/rubocop/rspec.rb +14 -0
- data/lib/rubocop-rspec.rb +3 -0
- metadata +11 -88
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Checks if there is a more specific finder offered by Capybara.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# find('#some-id')
|
12
|
+
# find('[visible][id=some-id]')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# find_by_id('some-id')
|
16
|
+
# find_by_id('some-id', visible: true)
|
17
|
+
#
|
18
|
+
class SpecificFinders < Base
|
19
|
+
extend AutoCorrector
|
20
|
+
|
21
|
+
include RangeHelp
|
22
|
+
|
23
|
+
MSG = 'Prefer `find_by` over `find`.'
|
24
|
+
RESTRICT_ON_SEND = %i[find].freeze
|
25
|
+
|
26
|
+
# @!method find_argument(node)
|
27
|
+
def_node_matcher :find_argument, <<~PATTERN
|
28
|
+
(send _ :find (str $_) ...)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
find_argument(node) do |arg|
|
33
|
+
next if CssSelector.multiple_selectors?(arg)
|
34
|
+
|
35
|
+
on_attr(node, arg) if attribute?(arg)
|
36
|
+
on_id(node, arg) if CssSelector.id?(arg)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def on_attr(node, arg)
|
43
|
+
return unless (id = CssSelector.attributes(arg)['id'])
|
44
|
+
|
45
|
+
register_offense(node, replaced_arguments(arg, id))
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_id(node, arg)
|
49
|
+
register_offense(node, "'#{arg.to_s.delete('#')}'")
|
50
|
+
end
|
51
|
+
|
52
|
+
def attribute?(arg)
|
53
|
+
CssSelector.attribute?(arg) &&
|
54
|
+
CssSelector.common_attributes?(arg)
|
55
|
+
end
|
56
|
+
|
57
|
+
def register_offense(node, arg_replacement)
|
58
|
+
add_offense(offense_range(node)) do |corrector|
|
59
|
+
corrector.replace(node.loc.selector, 'find_by_id')
|
60
|
+
corrector.replace(node.first_argument.loc.expression,
|
61
|
+
arg_replacement)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def replaced_arguments(arg, id)
|
66
|
+
options = to_options(CssSelector.attributes(arg))
|
67
|
+
options.empty? ? id : "#{id}, #{options}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_options(attrs)
|
71
|
+
attrs.each.map do |key, value|
|
72
|
+
next if key == 'id'
|
73
|
+
|
74
|
+
"#{key}: #{value}"
|
75
|
+
end.compact.join(', ')
|
76
|
+
end
|
77
|
+
|
78
|
+
def offense_range(node)
|
79
|
+
range_between(node.loc.selector.begin_pos,
|
80
|
+
node.loc.end.end_pos)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -12,19 +12,21 @@ module RuboCop
|
|
12
12
|
# expect(page).to have_selector('button')
|
13
13
|
# expect(page).to have_no_selector('button.cls')
|
14
14
|
# expect(page).to have_css('button')
|
15
|
-
# expect(page).to have_no_css('a.cls',
|
15
|
+
# expect(page).to have_no_css('a.cls', href: 'http://example.com')
|
16
16
|
# expect(page).to have_css('table.cls')
|
17
17
|
# expect(page).to have_css('select')
|
18
|
+
# expect(page).to have_css('input', exact_text: 'foo')
|
18
19
|
#
|
19
20
|
# # good
|
20
21
|
# expect(page).to have_button
|
21
22
|
# expect(page).to have_no_button(class: 'cls')
|
22
23
|
# expect(page).to have_button
|
23
|
-
# expect(page).to have_no_link('foo', class: 'cls')
|
24
|
+
# expect(page).to have_no_link('foo', class: 'cls', href: 'http://example.com')
|
24
25
|
# expect(page).to have_table(class: 'cls')
|
25
26
|
# expect(page).to have_select
|
27
|
+
# expect(page).to have_field('foo')
|
26
28
|
#
|
27
|
-
class SpecificMatcher < Base
|
29
|
+
class SpecificMatcher < Base # rubocop:disable Metrics/ClassLength
|
28
30
|
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
|
29
31
|
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
|
30
32
|
have_no_css].freeze
|
@@ -32,20 +34,57 @@ module RuboCop
|
|
32
34
|
'button' => 'button',
|
33
35
|
'a' => 'link',
|
34
36
|
'table' => 'table',
|
35
|
-
'select' => 'select'
|
37
|
+
'select' => 'select',
|
38
|
+
'input' => 'field'
|
36
39
|
}.freeze
|
40
|
+
SPECIFIC_MATCHER_OPTIONS = {
|
41
|
+
'button' => (
|
42
|
+
CssSelector::COMMON_OPTIONS + %w[disabled name value title type]
|
43
|
+
).freeze,
|
44
|
+
'link' => (
|
45
|
+
CssSelector::COMMON_OPTIONS + %w[href alt title download]
|
46
|
+
).freeze,
|
47
|
+
'table' => (
|
48
|
+
CssSelector::COMMON_OPTIONS + %w[
|
49
|
+
caption with_cols cols with_rows rows
|
50
|
+
]
|
51
|
+
).freeze,
|
52
|
+
'select' => (
|
53
|
+
CssSelector::COMMON_OPTIONS + %w[
|
54
|
+
disabled name placeholder options enabled_options
|
55
|
+
disabled_options selected with_selected multiple with_options
|
56
|
+
]
|
57
|
+
).freeze,
|
58
|
+
'field' => (
|
59
|
+
CssSelector::COMMON_OPTIONS + %w[
|
60
|
+
checked unchecked disabled valid name placeholder
|
61
|
+
validation_message readonly with type multiple
|
62
|
+
]
|
63
|
+
).freeze
|
64
|
+
}.freeze
|
65
|
+
SPECIFIC_MATCHER_PSEUDO_CLASSES = %w[
|
66
|
+
not() disabled enabled checked unchecked
|
67
|
+
].freeze
|
37
68
|
|
38
69
|
# @!method first_argument(node)
|
39
70
|
def_node_matcher :first_argument, <<-PATTERN
|
40
71
|
(send nil? _ (str $_) ... )
|
41
72
|
PATTERN
|
42
73
|
|
74
|
+
# @!method option?(node)
|
75
|
+
def_node_search :option?, <<-PATTERN
|
76
|
+
(pair (sym %) _)
|
77
|
+
PATTERN
|
78
|
+
|
43
79
|
def on_send(node)
|
44
|
-
|
45
|
-
|
46
|
-
|
80
|
+
first_argument(node) do |arg|
|
81
|
+
next unless (matcher = specific_matcher(arg))
|
82
|
+
next if CssSelector.multiple_selectors?(arg)
|
83
|
+
next unless specific_matcher_option?(node, arg, matcher)
|
84
|
+
next unless specific_matcher_pseudo_classes?(arg)
|
47
85
|
|
48
|
-
|
86
|
+
add_offense(node, message: message(node, matcher))
|
87
|
+
end
|
49
88
|
end
|
50
89
|
|
51
90
|
private
|
@@ -55,8 +94,50 @@ module RuboCop
|
|
55
94
|
SPECIFIC_MATCHER[splitted_arg]
|
56
95
|
end
|
57
96
|
|
58
|
-
def
|
59
|
-
|
97
|
+
def specific_matcher_option?(node, arg, matcher)
|
98
|
+
attrs = CssSelector.attributes(arg).keys
|
99
|
+
return true if attrs.empty?
|
100
|
+
return false unless replaceable_matcher?(node, matcher, attrs)
|
101
|
+
|
102
|
+
attrs.all? do |attr|
|
103
|
+
SPECIFIC_MATCHER_OPTIONS.fetch(matcher, []).include?(attr)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def specific_matcher_pseudo_classes?(arg)
|
108
|
+
CssSelector.pseudo_classes(arg).all? do |pseudo_class|
|
109
|
+
replaceable_pseudo_class?(pseudo_class, arg)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def replaceable_pseudo_class?(pseudo_class, arg)
|
114
|
+
unless SPECIFIC_MATCHER_PSEUDO_CLASSES.include?(pseudo_class)
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
|
118
|
+
case pseudo_class
|
119
|
+
when 'not()' then replaceable_pseudo_class_not?(arg)
|
120
|
+
else true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def replaceable_pseudo_class_not?(arg)
|
125
|
+
arg.scan(/not\(.*?\)/).all? do |not_arg|
|
126
|
+
CssSelector.attributes(not_arg).values.all? do |v|
|
127
|
+
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def replaceable_matcher?(node, matcher, attrs)
|
133
|
+
case matcher
|
134
|
+
when 'link' then replaceable_to_have_link?(node, attrs)
|
135
|
+
else true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def replaceable_to_have_link?(node, attrs)
|
140
|
+
option?(node, :href) || attrs.include?('href')
|
60
141
|
end
|
61
142
|
|
62
143
|
def message(node, matcher)
|
@@ -5,12 +5,20 @@ module RuboCop
|
|
5
5
|
module RSpec
|
6
6
|
# Prefer negated matchers over `to change.by(0)`.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# In the case of composite expectations, cop suggest using the
|
9
|
+
# negation matchers of `RSpec::Matchers#change`.
|
10
|
+
#
|
11
|
+
# By default the cop does not support autocorrect of
|
12
|
+
# compound expectations, but if you set the
|
13
|
+
# negated matcher for `change`, e.g. `not_change` with
|
14
|
+
# the `NegatedMatcher` option, the cop will perform the autocorrection.
|
15
|
+
#
|
16
|
+
# @example NegatedMatcher: ~ (default)
|
9
17
|
# # bad
|
10
18
|
# expect { run }.to change(Foo, :bar).by(0)
|
11
19
|
# expect { run }.to change { Foo.bar }.by(0)
|
12
20
|
#
|
13
|
-
# # bad - compound expectations
|
21
|
+
# # bad - compound expectations (does not support autocorrection)
|
14
22
|
# expect { run }
|
15
23
|
# .to change(Foo, :bar).by(0)
|
16
24
|
# .and change(Foo, :baz).by(0)
|
@@ -31,10 +39,28 @@ module RuboCop
|
|
31
39
|
# .to not_change { Foo.bar }
|
32
40
|
# .and not_change { Foo.baz }
|
33
41
|
#
|
42
|
+
# @example NegatedMatcher: not_change
|
43
|
+
# # bad (support autocorrection to good case)
|
44
|
+
# expect { run }
|
45
|
+
# .to change(Foo, :bar).by(0)
|
46
|
+
# .and change(Foo, :baz).by(0)
|
47
|
+
# expect { run }
|
48
|
+
# .to change { Foo.bar }.by(0)
|
49
|
+
# .and change { Foo.baz }.by(0)
|
50
|
+
#
|
51
|
+
# # good
|
52
|
+
# define_negated_matcher :not_change, :change
|
53
|
+
# expect { run }
|
54
|
+
# .to not_change(Foo, :bar)
|
55
|
+
# .and not_change(Foo, :baz)
|
56
|
+
# expect { run }
|
57
|
+
# .to not_change { Foo.bar }
|
58
|
+
# .and not_change { Foo.baz }
|
59
|
+
#
|
34
60
|
class ChangeByZero < Base
|
35
61
|
extend AutoCorrector
|
36
62
|
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
|
37
|
-
MSG_COMPOUND = 'Prefer
|
63
|
+
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
38
64
|
'over `change.by(0)`.'
|
39
65
|
RESTRICT_ON_SEND = %i[change].freeze
|
40
66
|
|
@@ -55,6 +81,11 @@ module RuboCop
|
|
55
81
|
(int 0))
|
56
82
|
PATTERN
|
57
83
|
|
84
|
+
# @!method change_nodes(node)
|
85
|
+
def_node_search :change_nodes, <<-PATTERN
|
86
|
+
$(send nil? :change ...)
|
87
|
+
PATTERN
|
88
|
+
|
58
89
|
def on_send(node)
|
59
90
|
expect_change_with_arguments(node.parent) do
|
60
91
|
check_offense(node.parent)
|
@@ -70,7 +101,9 @@ module RuboCop
|
|
70
101
|
def check_offense(node)
|
71
102
|
expression = node.loc.expression
|
72
103
|
if compound_expectations?(node)
|
73
|
-
add_offense(expression, message:
|
104
|
+
add_offense(expression, message: message_compound) do |corrector|
|
105
|
+
autocorrect_compound(corrector, node)
|
106
|
+
end
|
74
107
|
else
|
75
108
|
add_offense(expression) do |corrector|
|
76
109
|
autocorrect(corrector, node)
|
@@ -79,7 +112,7 @@ module RuboCop
|
|
79
112
|
end
|
80
113
|
|
81
114
|
def compound_expectations?(node)
|
82
|
-
%i[and or].include?(node.parent.method_name)
|
115
|
+
%i[and or & |].include?(node.parent.method_name)
|
83
116
|
end
|
84
117
|
|
85
118
|
def autocorrect(corrector, node)
|
@@ -87,6 +120,28 @@ module RuboCop
|
|
87
120
|
range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
|
88
121
|
corrector.remove(range)
|
89
122
|
end
|
123
|
+
|
124
|
+
def autocorrect_compound(corrector, node)
|
125
|
+
return unless negated_matcher
|
126
|
+
|
127
|
+
change_nodes(node) do |change_node|
|
128
|
+
corrector.replace(change_node.loc.selector, negated_matcher)
|
129
|
+
range = node.loc.dot.with(end_pos: node.loc.expression.end_pos)
|
130
|
+
corrector.remove(range)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def negated_matcher
|
135
|
+
cop_config['NegatedMatcher']
|
136
|
+
end
|
137
|
+
|
138
|
+
def message_compound
|
139
|
+
format(MSG_COMPOUND, preferred: preferred_method)
|
140
|
+
end
|
141
|
+
|
142
|
+
def preferred_method
|
143
|
+
negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
|
144
|
+
end
|
90
145
|
end
|
91
146
|
end
|
92
147
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Enforces consistent use of `be_a` or `be_kind_of`.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: be_a (default)
|
9
|
+
# # bad
|
10
|
+
# expect(object).to be_kind_of(String)
|
11
|
+
# expect(object).to be_a_kind_of(String)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# expect(object).to be_a(String)
|
15
|
+
# expect(object).to be_an(String)
|
16
|
+
#
|
17
|
+
# @example EnforcedStyle: be_kind_of
|
18
|
+
# # bad
|
19
|
+
# expect(object).to be_a(String)
|
20
|
+
# expect(object).to be_an(String)
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# expect(object).to be_kind_of(String)
|
24
|
+
# expect(object).to be_a_kind_of(String)
|
25
|
+
#
|
26
|
+
class ClassCheck < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
include ConfigurableEnforcedStyle
|
29
|
+
|
30
|
+
MSG = 'Prefer `%<preferred>s` over `%<current>s`.'
|
31
|
+
|
32
|
+
METHOD_NAMES_FOR_BE_A = ::Set[
|
33
|
+
:be_a,
|
34
|
+
:be_an
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
METHOD_NAMES_FOR_KIND_OF = ::Set[
|
38
|
+
:be_a_kind_of,
|
39
|
+
:be_kind_of
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
PREFERRED_METHOD_NAME_BY_STYLE = {
|
43
|
+
be_a: :be_a,
|
44
|
+
be_kind_of: :be_kind_of
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
RESTRICT_ON_SEND = %i[
|
48
|
+
be_a
|
49
|
+
be_a_kind_of
|
50
|
+
be_an
|
51
|
+
be_kind_of
|
52
|
+
].freeze
|
53
|
+
|
54
|
+
def on_send(node)
|
55
|
+
return unless offending?(node)
|
56
|
+
|
57
|
+
add_offense(
|
58
|
+
node.loc.selector,
|
59
|
+
message: format_message(node)
|
60
|
+
) do |corrector|
|
61
|
+
autocorrect(corrector, node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def autocorrect(corrector, node)
|
68
|
+
corrector.replace(node.loc.selector, preferred_method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_message(node)
|
72
|
+
format(
|
73
|
+
MSG,
|
74
|
+
current: node.method_name,
|
75
|
+
preferred: preferred_method_name
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def offending?(node)
|
80
|
+
!node.receiver && !preferred_method_name?(node.method_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def preferred_method_name?(method_name)
|
84
|
+
preferred_method_names.include?(method_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
def preferred_method_name
|
88
|
+
PREFERRED_METHOD_NAME_BY_STYLE[style]
|
89
|
+
end
|
90
|
+
|
91
|
+
def preferred_method_names
|
92
|
+
if style == :be_a
|
93
|
+
METHOD_NAMES_FOR_BE_A
|
94
|
+
else
|
95
|
+
METHOD_NAMES_FOR_KIND_OF
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -23,6 +23,7 @@ module RuboCop
|
|
23
23
|
# describe '.foo_bar' do
|
24
24
|
# # ...
|
25
25
|
# end
|
26
|
+
#
|
26
27
|
class ContextMethod < Base
|
27
28
|
extend AutoCorrector
|
28
29
|
|
@@ -33,7 +34,7 @@ module RuboCop
|
|
33
34
|
(block (send #rspec? :context $(str #method_name?) ...) ...)
|
34
35
|
PATTERN
|
35
36
|
|
36
|
-
def on_block(node)
|
37
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
37
38
|
context_method(node) do |context|
|
38
39
|
add_offense(context) do |corrector|
|
39
40
|
corrector.replace(node.send_node.loc.selector, 'describe')
|
@@ -14,7 +14,6 @@ module RuboCop
|
|
14
14
|
# @see http://www.betterspecs.org/#contexts
|
15
15
|
#
|
16
16
|
# @example `Prefixes` configuration
|
17
|
-
#
|
18
17
|
# # .rubocop.yml
|
19
18
|
# # RSpec/ContextWording:
|
20
19
|
# # Prefixes:
|
@@ -35,41 +34,73 @@ module RuboCop
|
|
35
34
|
# context 'when the display name is not present' do
|
36
35
|
# # ...
|
37
36
|
# end
|
37
|
+
#
|
38
|
+
# This cop can be customized allowed context description pattern
|
39
|
+
# with `AllowedPatterns`. By default, there are no checking by pattern.
|
40
|
+
#
|
41
|
+
# @example `AllowedPatterns` configuration
|
42
|
+
#
|
43
|
+
# # .rubocop.yml
|
44
|
+
# # RSpec/ContextWording:
|
45
|
+
# # AllowedPatterns:
|
46
|
+
# # - /とき$/
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# # bad
|
50
|
+
# context '条件を満たす' do
|
51
|
+
# # ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # good
|
55
|
+
# context '条件を満たすとき' do
|
56
|
+
# # ...
|
57
|
+
# end
|
58
|
+
#
|
38
59
|
class ContextWording < Base
|
39
|
-
|
60
|
+
include AllowedPattern
|
61
|
+
|
62
|
+
MSG = 'Context description should match %<patterns>s.'
|
40
63
|
|
41
64
|
# @!method context_wording(node)
|
42
65
|
def_node_matcher :context_wording, <<-PATTERN
|
43
|
-
(block (send #rspec? { :context :shared_context } $(str
|
66
|
+
(block (send #rspec? { :context :shared_context } $(str $_) ...) ...)
|
44
67
|
PATTERN
|
45
68
|
|
46
|
-
def on_block(node)
|
47
|
-
context_wording(node) do |context|
|
48
|
-
|
49
|
-
|
69
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
70
|
+
context_wording(node) do |context, description|
|
71
|
+
if bad_pattern?(description)
|
72
|
+
message = format(MSG, patterns: expect_patterns)
|
73
|
+
add_offense(context, message: message)
|
74
|
+
end
|
50
75
|
end
|
51
76
|
end
|
52
77
|
|
53
78
|
private
|
54
79
|
|
55
|
-
def
|
56
|
-
|
80
|
+
def allowed_patterns
|
81
|
+
super + prefix_regexes
|
57
82
|
end
|
58
83
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
84
|
+
def prefix_regexes
|
85
|
+
@prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
|
86
|
+
end
|
87
|
+
|
88
|
+
def bad_pattern?(description)
|
89
|
+
return false if allowed_patterns.empty?
|
62
90
|
|
63
|
-
|
64
|
-
quoted.join(', ')
|
91
|
+
!matches_allowed_pattern?(description)
|
65
92
|
end
|
66
93
|
|
67
|
-
def
|
68
|
-
|
94
|
+
def expect_patterns
|
95
|
+
inspected = allowed_patterns.map(&:inspect)
|
96
|
+
return inspected.first if inspected.size == 1
|
97
|
+
|
98
|
+
inspected << "or #{inspected.pop}"
|
99
|
+
inspected.join(', ')
|
69
100
|
end
|
70
101
|
|
71
|
-
def
|
72
|
-
|
102
|
+
def prefixes
|
103
|
+
Array(cop_config.fetch('Prefixes', []))
|
73
104
|
end
|
74
105
|
end
|
75
106
|
end
|
@@ -10,7 +10,6 @@ module RuboCop
|
|
10
10
|
# Ignores Rails and Aruba `type` metadata by default.
|
11
11
|
#
|
12
12
|
# @example `IgnoredMetadata` configuration
|
13
|
-
#
|
14
13
|
# # .rubocop.yml
|
15
14
|
# # RSpec/DescribeClass:
|
16
15
|
# # IgnoredMetadata:
|
@@ -34,6 +33,7 @@ module RuboCop
|
|
34
33
|
#
|
35
34
|
# describe "A feature example", type: :feature do
|
36
35
|
# end
|
36
|
+
#
|
37
37
|
class DescribeClass < Base
|
38
38
|
include TopLevelGroup
|
39
39
|
|
@@ -57,6 +57,7 @@ module RuboCop
|
|
57
57
|
class DescribedClass < Base
|
58
58
|
extend AutoCorrector
|
59
59
|
include ConfigurableEnforcedStyle
|
60
|
+
include Namespace
|
60
61
|
|
61
62
|
DESCRIBED_CLASS = 'described_class'
|
62
63
|
MSG = 'Use `%<replacement>s` instead of `%<src>s`.'
|
@@ -81,7 +82,7 @@ module RuboCop
|
|
81
82
|
def_node_search :contains_described_class?,
|
82
83
|
'(send nil? :described_class)'
|
83
84
|
|
84
|
-
def on_block(node)
|
85
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
85
86
|
# In case the explicit style is used, we need to remember what's
|
86
87
|
# being described.
|
87
88
|
@described_class, body = described_constant(node)
|
@@ -160,7 +161,8 @@ module RuboCop
|
|
160
161
|
end
|
161
162
|
|
162
163
|
def full_const_name(node)
|
163
|
-
|
164
|
+
symbolized_namespace = namespace(node).map(&:to_sym)
|
165
|
+
collapse_namespace(symbolized_namespace, const_name(node))
|
164
166
|
end
|
165
167
|
|
166
168
|
# @param namespace [Array<Symbol>]
|
@@ -200,18 +202,6 @@ module RuboCop
|
|
200
202
|
[nil, name]
|
201
203
|
end
|
202
204
|
end
|
203
|
-
|
204
|
-
# @param node [RuboCop::AST::Node]
|
205
|
-
# @return [Array<Symbol>]
|
206
|
-
# @example
|
207
|
-
# namespace(node) # => [:A, :B, :C]
|
208
|
-
def namespace(node)
|
209
|
-
node
|
210
|
-
.each_ancestor(:class, :module)
|
211
|
-
.reverse_each
|
212
|
-
.flat_map { |ancestor| ancestor.defined_module_name.split('::') }
|
213
|
-
.map(&:to_sym)
|
214
|
-
end
|
215
205
|
end
|
216
206
|
end
|
217
207
|
end
|