rubocop-rspec 1.7.0 → 3.0.2
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 +5 -5
- data/CHANGELOG.md +955 -79
- data/CODE_OF_CONDUCT.md +17 -0
- data/MIT-LICENSE.md +1 -2
- data/README.md +35 -35
- data/config/default.yml +940 -52
- data/config/obsoletion.yml +30 -0
- data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
- data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
- data/lib/rubocop/cop/rspec/around_block.rb +97 -0
- data/lib/rubocop/cop/rspec/base.rb +26 -0
- data/lib/rubocop/cop/rspec/be.rb +39 -0
- data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
- data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
- data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
- data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
- data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
- data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
- data/lib/rubocop/cop/rspec/class_check.rb +101 -0
- data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
- data/lib/rubocop/cop/rspec/context_method.rb +57 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
- data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
- data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
- data/lib/rubocop/cop/rspec/described_class.rb +181 -34
- data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
- data/lib/rubocop/cop/rspec/dialect.rb +84 -0
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
- data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
- data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_length.rb +38 -20
- data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
- data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
- data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
- data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
- data/lib/rubocop/cop/rspec/focus.rb +79 -25
- data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
- data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
- data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
- data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
- data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
- data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
- data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
- data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
- data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
- data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
- data/lib/rubocop/cop/rspec/match_array.rb +59 -0
- data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
- data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
- data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
- data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
- data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
- data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
- data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
- data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
- data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
- data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
- data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
- data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
- data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
- data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
- data/lib/rubocop/cop/rspec/pending.rb +80 -0
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
- data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
- data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
- data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
- data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
- data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
- data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
- data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
- data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
- data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
- data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
- data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
- data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
- data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
- data/lib/rubocop/cop/rspec/yield.rb +82 -0
- data/lib/rubocop/cop/rspec_cops.rb +112 -0
- data/lib/rubocop/rspec/align_let_brace.rb +63 -0
- data/lib/rubocop/rspec/concept.rb +33 -0
- data/lib/rubocop/rspec/config_formatter.rb +27 -4
- data/lib/rubocop/rspec/cop/generator.rb +25 -0
- data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
- data/lib/rubocop/rspec/description_extractor.rb +60 -18
- data/lib/rubocop/rspec/example.rb +37 -0
- data/lib/rubocop/rspec/example_group.rb +67 -0
- data/lib/rubocop/rspec/hook.rb +79 -0
- data/lib/rubocop/rspec/inject.rb +3 -1
- data/lib/rubocop/rspec/language.rb +184 -41
- data/lib/rubocop/rspec/node.rb +19 -0
- data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +61 -19
- data/lib/rubocop/rspec.rb +6 -2
- data/lib/rubocop-rspec.rb +45 -34
- metadata +130 -195
- data/Gemfile +0 -13
- data/Rakefile +0 -48
- data/lib/rubocop/cop/rspec/file_path.rb +0 -83
- data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
- data/lib/rubocop/rspec/spec_only.rb +0 -61
- data/lib/rubocop/rspec/top_level_describe.rb +0 -61
- data/lib/rubocop/rspec/util.rb +0 -19
- data/rubocop-rspec.gemspec +0 -42
- data/spec/expect_violation/expectation_spec.rb +0 -85
- data/spec/project/changelog_spec.rb +0 -81
- data/spec/project/default_config_spec.rb +0 -52
- data/spec/project/project_requires_spec.rb +0 -8
- data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
- data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
- data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
- data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
- data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
- data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
- data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
- data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
- data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
- data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
- data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
- data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
- data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
- data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
- data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
- data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
- data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
- data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
- data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
- data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
- data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
- data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
- data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
- data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
- data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
- data/spec/rubocop/rspec/util/one_spec.rb +0 -21
- data/spec/rubocop/rspec/wording_spec.rb +0 -44
- data/spec/shared/rspec_only_cop_behavior.rb +0 -68
- data/spec/spec_helper.rb +0 -41
- data/spec/support/expect_violation.rb +0 -166
@@ -3,58 +3,135 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
# Checks
|
6
|
+
# Checks for common mistakes in example descriptions.
|
7
|
+
#
|
8
|
+
# This cop will correct docstrings that begin with 'should' and 'it'.
|
9
|
+
# This cop will also look for insufficient examples and call them out.
|
7
10
|
#
|
8
11
|
# @see http://betterspecs.org/#should
|
9
12
|
#
|
10
13
|
# The autocorrect is experimental - use with care! It can be configured
|
11
14
|
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
|
12
15
|
#
|
16
|
+
# Use the DisallowedExamples setting to prevent unclear or insufficient
|
17
|
+
# descriptions. Please note that this config will not be treated as
|
18
|
+
# case sensitive.
|
19
|
+
#
|
13
20
|
# @example
|
14
21
|
# # bad
|
15
22
|
# it 'should find nothing' do
|
16
23
|
# end
|
17
24
|
#
|
25
|
+
# it 'will find nothing' do
|
26
|
+
# end
|
27
|
+
#
|
18
28
|
# # good
|
19
29
|
# it 'finds nothing' do
|
20
30
|
# end
|
21
|
-
|
22
|
-
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# # bad
|
34
|
+
# it 'it does things' do
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# it 'does things' do
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @example `DisallowedExamples: ['works']` (default)
|
42
|
+
# # bad
|
43
|
+
# it 'works' do
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # good
|
47
|
+
# it 'marks the task as done' do
|
48
|
+
# end
|
49
|
+
class ExampleWording < Base
|
50
|
+
extend AutoCorrector
|
51
|
+
|
52
|
+
MSG_SHOULD = 'Do not use should when describing your tests.'
|
53
|
+
MSG_WILL = 'Do not use the future tense when describing your tests.'
|
54
|
+
MSG_IT = "Do not repeat 'it' when describing your tests."
|
55
|
+
MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \
|
56
|
+
'insufficient.'
|
23
57
|
|
24
|
-
|
58
|
+
SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
|
59
|
+
WILL_PREFIX = /\A(?:will|won't)\b/i.freeze
|
60
|
+
IT_PREFIX = /\Ait /i.freeze
|
25
61
|
|
26
|
-
|
27
|
-
|
28
|
-
|
62
|
+
# @!method it_description(node)
|
63
|
+
def_node_matcher :it_description, <<~PATTERN
|
64
|
+
(block (send _ :it ${
|
65
|
+
(str $_)
|
66
|
+
(dstr (str $_ ) ...)
|
67
|
+
} ...) ...)
|
68
|
+
PATTERN
|
29
69
|
|
30
|
-
|
70
|
+
# rubocop:disable Metrics/MethodLength
|
71
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
72
|
+
it_description(node) do |description_node, message|
|
73
|
+
if message.match?(SHOULD_PREFIX)
|
74
|
+
add_wording_offense(description_node, MSG_SHOULD)
|
75
|
+
elsif message.match?(WILL_PREFIX)
|
76
|
+
add_wording_offense(description_node, MSG_WILL)
|
77
|
+
elsif message.match?(IT_PREFIX)
|
78
|
+
add_wording_offense(description_node, MSG_IT)
|
79
|
+
elsif insufficient_docstring?(description_node)
|
80
|
+
add_offense(docstring(description_node),
|
81
|
+
message: MSG_INSUFFICIENT_DESCRIPTION)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# rubocop:enable Metrics/MethodLength
|
31
86
|
|
32
|
-
|
33
|
-
message = arguments.first.to_s
|
34
|
-
return unless message.downcase.start_with?('should')
|
87
|
+
private
|
35
88
|
|
36
|
-
|
37
|
-
|
38
|
-
arg1.begin_pos + 1,
|
39
|
-
arg1.end_pos - 1)
|
89
|
+
def add_wording_offense(node, message)
|
90
|
+
docstring = docstring(node)
|
40
91
|
|
41
|
-
add_offense(
|
92
|
+
add_offense(docstring, message: message) do |corrector|
|
93
|
+
next if node.heredoc?
|
94
|
+
|
95
|
+
corrector.replace(docstring, replacement_text(node))
|
96
|
+
end
|
42
97
|
end
|
43
98
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
99
|
+
def docstring(node)
|
100
|
+
expr = node.source_range
|
101
|
+
|
102
|
+
Parser::Source::Range.new(
|
103
|
+
expr.source_buffer,
|
104
|
+
expr.begin_pos + 1,
|
105
|
+
expr.end_pos - 1
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def replacement_text(node)
|
110
|
+
text = text(node)
|
111
|
+
|
112
|
+
if text.match?(SHOULD_PREFIX) || text.match?(WILL_PREFIX)
|
113
|
+
RuboCop::RSpec::Wording.new(
|
114
|
+
text,
|
115
|
+
ignore: ignored_words,
|
116
|
+
replace: custom_transform
|
117
|
+
).rewrite
|
118
|
+
else
|
119
|
+
text.sub(IT_PREFIX, '')
|
54
120
|
end
|
55
121
|
end
|
56
122
|
|
57
|
-
|
123
|
+
# Recursive processing is required to process nested dstr nodes
|
124
|
+
# that is the case for \-separated multiline strings with interpolation.
|
125
|
+
def text(node)
|
126
|
+
case node.type
|
127
|
+
when :dstr
|
128
|
+
node.node_parts.map { |child_node| text(child_node) }.join
|
129
|
+
when :str
|
130
|
+
node.value
|
131
|
+
when :begin
|
132
|
+
node.source
|
133
|
+
end
|
134
|
+
end
|
58
135
|
|
59
136
|
def custom_transform
|
60
137
|
cop_config.fetch('CustomTransform', {})
|
@@ -63,6 +140,19 @@ module RuboCop
|
|
63
140
|
def ignored_words
|
64
141
|
cop_config.fetch('IgnoredWords', [])
|
65
142
|
end
|
143
|
+
|
144
|
+
def insufficient_docstring?(description_node)
|
145
|
+
insufficient_examples.include?(preprocess(text(description_node)))
|
146
|
+
end
|
147
|
+
|
148
|
+
def insufficient_examples
|
149
|
+
examples = cop_config.fetch('DisallowedExamples', [])
|
150
|
+
examples.map! { |example| preprocess(example) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def preprocess(message)
|
154
|
+
message.strip.squeeze(' ').downcase
|
155
|
+
end
|
66
156
|
end
|
67
157
|
end
|
68
158
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for excessive whitespace in example descriptions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it ' has excessive spacing ' do
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# it 'has excessive spacing' do
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad
|
19
|
+
# context ' when a condition is met ' do
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# context 'when a condition is met' do
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class ExcessiveDocstringSpacing < Base
|
27
|
+
extend AutoCorrector
|
28
|
+
|
29
|
+
MSG = 'Excessive whitespace.'
|
30
|
+
|
31
|
+
# @!method example_description(node)
|
32
|
+
def_node_matcher :example_description, <<~PATTERN
|
33
|
+
(send _ {#Examples.all #ExampleGroups.all} ${
|
34
|
+
$str
|
35
|
+
$(dstr ({str dstr `sym} ...) ...)
|
36
|
+
} ...)
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
def on_send(node)
|
40
|
+
example_description(node) do |description_node, message|
|
41
|
+
return if description_node.heredoc?
|
42
|
+
|
43
|
+
text = text(message)
|
44
|
+
|
45
|
+
return unless excessive_whitespace?(text)
|
46
|
+
|
47
|
+
add_whitespace_offense(description_node, text)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @param text [String]
|
54
|
+
def excessive_whitespace?(text)
|
55
|
+
text.match?(/
|
56
|
+
# Leading space
|
57
|
+
\A[[:blank:]]
|
58
|
+
|
|
59
|
+
# Trailing space
|
60
|
+
[[:blank:]]\z
|
61
|
+
|
|
62
|
+
# Two or more consecutive spaces, except if they are leading spaces
|
63
|
+
[^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]]
|
64
|
+
/x)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param text [String]
|
68
|
+
def strip_excessive_whitespace(text)
|
69
|
+
text
|
70
|
+
.gsub(/[[:blank:]]{2,}/, ' ')
|
71
|
+
.gsub(/\A[[:blank:]]|[[:blank:]]\z/, '')
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param node [RuboCop::AST::Node]
|
75
|
+
# @param text [String]
|
76
|
+
def add_whitespace_offense(node, text)
|
77
|
+
docstring = docstring(node)
|
78
|
+
corrected = strip_excessive_whitespace(text)
|
79
|
+
|
80
|
+
add_offense(docstring) do |corrector|
|
81
|
+
corrector.replace(docstring, corrected)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def docstring(node)
|
86
|
+
expr = node.source_range
|
87
|
+
|
88
|
+
Parser::Source::Range.new(
|
89
|
+
expr.source_buffer,
|
90
|
+
expr.begin_pos + 1,
|
91
|
+
expr.end_pos - 1
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Recursive processing is required to process nested dstr nodes
|
96
|
+
# that is the case for \-separated multiline strings with interpolation.
|
97
|
+
def text(node)
|
98
|
+
case node.type
|
99
|
+
when :dstr
|
100
|
+
node.node_parts.map { |child_node| text(child_node) }.join
|
101
|
+
when :str, :sym
|
102
|
+
node.value
|
103
|
+
when :begin
|
104
|
+
node.source
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -5,6 +5,8 @@ module RuboCop
|
|
5
5
|
module RSpec
|
6
6
|
# Checks for `expect(...)` calls containing literal values.
|
7
7
|
#
|
8
|
+
# Autocorrection is performed when the expected is not a literal.
|
9
|
+
#
|
8
10
|
# @example
|
9
11
|
# # bad
|
10
12
|
# expect(5).to eq(price)
|
@@ -16,12 +18,17 @@ module RuboCop
|
|
16
18
|
# expect(pattern).to eq(/foo/)
|
17
19
|
# expect(name).to eq("John")
|
18
20
|
#
|
19
|
-
|
20
|
-
|
21
|
+
# # bad (not supported autocorrection)
|
22
|
+
# expect(false).to eq(true)
|
23
|
+
#
|
24
|
+
class ExpectActual < Base
|
25
|
+
extend AutoCorrector
|
26
|
+
|
27
|
+
MSG = 'Provide the actual value you are testing to `expect(...)`.'
|
21
28
|
|
22
|
-
|
29
|
+
RESTRICT_ON_SEND = Runners.all
|
23
30
|
|
24
|
-
SIMPLE_LITERALS = %i
|
31
|
+
SIMPLE_LITERALS = %i[
|
25
32
|
true
|
26
33
|
false
|
27
34
|
nil
|
@@ -32,37 +39,56 @@ module RuboCop
|
|
32
39
|
complex
|
33
40
|
rational
|
34
41
|
regopt
|
35
|
-
|
42
|
+
].freeze
|
36
43
|
|
37
|
-
COMPLEX_LITERALS = %i
|
44
|
+
COMPLEX_LITERALS = %i[
|
38
45
|
array
|
39
46
|
hash
|
40
47
|
pair
|
41
48
|
irange
|
42
49
|
erange
|
43
50
|
regexp
|
44
|
-
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
SKIPPED_MATCHERS = %i[route_to be_routable].freeze
|
54
|
+
CORRECTABLE_MATCHERS = %i[eq eql equal be].freeze
|
55
|
+
|
56
|
+
# @!method expect_literal(node)
|
57
|
+
def_node_matcher :expect_literal, <<~PATTERN
|
58
|
+
(send
|
59
|
+
(send nil? :expect $#literal?)
|
60
|
+
#Runners.all
|
61
|
+
${
|
62
|
+
(send (send nil? $:be) :== $_)
|
63
|
+
(send nil? $_ $_ ...)
|
64
|
+
}
|
65
|
+
)
|
66
|
+
PATTERN
|
45
67
|
|
46
|
-
|
68
|
+
def on_send(node) # rubocop:disable Metrics/MethodLength
|
69
|
+
expect_literal(node) do |actual, send_node, matcher, expected|
|
70
|
+
next if SKIPPED_MATCHERS.include?(matcher)
|
47
71
|
|
48
|
-
|
49
|
-
|
50
|
-
|
72
|
+
add_offense(actual.source_range) do |corrector|
|
73
|
+
next unless CORRECTABLE_MATCHERS.include?(matcher)
|
74
|
+
next if literal?(expected)
|
75
|
+
|
76
|
+
corrector.replace(actual, expected.source)
|
77
|
+
if matcher == :be
|
78
|
+
corrector.replace(expected, actual.source)
|
79
|
+
else
|
80
|
+
corrector.replace(send_node, "#{matcher}(#{actual.source})")
|
81
|
+
end
|
82
|
+
end
|
51
83
|
end
|
52
84
|
end
|
53
85
|
|
54
86
|
private
|
55
87
|
|
56
|
-
# This is not
|
88
|
+
# This is not implemented using a NodePattern because it seems
|
57
89
|
# to not be able to match against an explicit (nil) sexp
|
58
|
-
def expect_literal(node)
|
59
|
-
return unless (argument = expect(node))
|
60
|
-
|
61
|
-
yield(argument) if literal?(argument)
|
62
|
-
end
|
63
|
-
|
64
90
|
def literal?(node)
|
65
|
-
simple_literal?(node) || complex_literal?(node)
|
91
|
+
node && (simple_literal?(node) || complex_literal?(node))
|
66
92
|
end
|
67
93
|
|
68
94
|
def simple_literal?(node)
|
@@ -71,7 +97,7 @@ module RuboCop
|
|
71
97
|
|
72
98
|
def complex_literal?(node)
|
73
99
|
COMPLEX_LITERALS.include?(node.type) &&
|
74
|
-
node.each_child_node.all?
|
100
|
+
node.each_child_node.all? { |child_node| literal?(child_node) }
|
75
101
|
end
|
76
102
|
end
|
77
103
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for consistent style of change matcher.
|
7
|
+
#
|
8
|
+
# Enforces either passing object and attribute as arguments to the matcher
|
9
|
+
# or passing a block that reads the attribute value.
|
10
|
+
#
|
11
|
+
# This cop can be configured using the `EnforcedStyle` option.
|
12
|
+
#
|
13
|
+
# @example `EnforcedStyle: method_call` (default)
|
14
|
+
# # bad
|
15
|
+
# expect { run }.to change { Foo.bar }
|
16
|
+
# expect { run }.to change { foo.baz }
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# expect { run }.to change(Foo, :bar)
|
20
|
+
# expect { run }.to change(foo, :baz)
|
21
|
+
# # also good when there are arguments or chained method calls
|
22
|
+
# expect { run }.to change { Foo.bar(:count) }
|
23
|
+
# expect { run }.to change { user.reload.name }
|
24
|
+
#
|
25
|
+
# @example `EnforcedStyle: block`
|
26
|
+
# # bad
|
27
|
+
# expect { run }.to change(Foo, :bar)
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# expect { run }.to change { Foo.bar }
|
31
|
+
#
|
32
|
+
class ExpectChange < Base
|
33
|
+
extend AutoCorrector
|
34
|
+
include ConfigurableEnforcedStyle
|
35
|
+
|
36
|
+
MSG_BLOCK = 'Prefer `change(%<obj>s, :%<attr>s)`.'
|
37
|
+
MSG_CALL = 'Prefer `change { %<obj>s.%<attr>s }`.'
|
38
|
+
RESTRICT_ON_SEND = %i[change].freeze
|
39
|
+
|
40
|
+
# @!method expect_change_with_arguments(node)
|
41
|
+
def_node_matcher :expect_change_with_arguments, <<~PATTERN
|
42
|
+
(send nil? :change $_ ({sym str} $_))
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
# @!method expect_change_with_block(node)
|
46
|
+
def_node_matcher :expect_change_with_block, <<~PATTERN
|
47
|
+
(block
|
48
|
+
(send nil? :change)
|
49
|
+
(args)
|
50
|
+
(send
|
51
|
+
${
|
52
|
+
(send nil? _) # change { user.name }
|
53
|
+
const # change { User.count }
|
54
|
+
}
|
55
|
+
$_
|
56
|
+
)
|
57
|
+
)
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
def on_send(node)
|
61
|
+
return unless style == :block
|
62
|
+
|
63
|
+
expect_change_with_arguments(node) do |receiver, message|
|
64
|
+
msg = format(MSG_CALL, obj: receiver.source, attr: message)
|
65
|
+
add_offense(node, message: msg) do |corrector|
|
66
|
+
replacement = "change { #{receiver.source}.#{message} }"
|
67
|
+
corrector.replace(node, replacement)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
73
|
+
return unless style == :method_call
|
74
|
+
|
75
|
+
expect_change_with_block(node) do |receiver, message|
|
76
|
+
msg = format(MSG_BLOCK, obj: receiver.source, attr: message)
|
77
|
+
add_offense(node, message: msg) do |corrector|
|
78
|
+
replacement = "change(#{receiver.source}, :#{message})"
|
79
|
+
corrector.replace(node, replacement)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Do not use `expect` in hooks such as `before`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# before do
|
11
|
+
# expect(something).to eq 'foo'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # bad
|
15
|
+
# after do
|
16
|
+
# expect_any_instance_of(Something).to receive(:foo)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# it do
|
21
|
+
# expect(something).to eq 'foo'
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
class ExpectInHook < Base
|
25
|
+
MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
|
26
|
+
|
27
|
+
# @!method expectation(node)
|
28
|
+
def_node_search :expectation, '(send nil? #Expectations.all ...)'
|
29
|
+
|
30
|
+
def on_block(node)
|
31
|
+
return unless hook?(node)
|
32
|
+
return if node.body.nil?
|
33
|
+
|
34
|
+
expectation(node.body) do |expect|
|
35
|
+
add_offense(expect.loc.selector,
|
36
|
+
message: message(expect, node))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
alias on_numblock on_block
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def message(expect, hook)
|
45
|
+
format(MSG, expect: expect.method_name, hook: hook.method_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Do not use `expect` in let.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# let(:foo) do
|
11
|
+
# expect(something).to eq 'foo'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# it do
|
16
|
+
# expect(something).to eq 'foo'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class ExpectInLet < Base
|
20
|
+
MSG = 'Do not use `%<expect>s` in let'
|
21
|
+
|
22
|
+
# @!method expectation(node)
|
23
|
+
def_node_search :expectation, '(send nil? #Expectations.all ...)'
|
24
|
+
|
25
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
26
|
+
return unless let?(node)
|
27
|
+
return if node.body.nil?
|
28
|
+
|
29
|
+
expectation(node.body) do |expect|
|
30
|
+
add_offense(expect.loc.selector, message: message(expect))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def message(expect)
|
37
|
+
format(MSG, expect: expect.method_name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for opportunities to use `expect { ... }.to output`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# $stdout = StringIO.new
|
11
|
+
# my_app.print_report
|
12
|
+
# $stdout = STDOUT
|
13
|
+
# expect($stdout.string).to eq('Hello World')
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# expect { my_app.print_report }.to output('Hello World').to_stdout
|
17
|
+
#
|
18
|
+
class ExpectOutput < Base
|
19
|
+
MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
|
20
|
+
'instead of mutating $%<name>s.'
|
21
|
+
|
22
|
+
def on_gvasgn(node)
|
23
|
+
return unless inside_example_scope?(node)
|
24
|
+
|
25
|
+
name = node.name[1..]
|
26
|
+
return unless name.eql?('stdout') || name.eql?('stderr')
|
27
|
+
|
28
|
+
add_offense(node.loc.name, message: format(MSG, name: name))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Detect if we are inside the scope of a single example
|
34
|
+
#
|
35
|
+
# We want to encourage using `expect { ... }.to output` so
|
36
|
+
# we only care about situations where you would replace with
|
37
|
+
# an expectation. Therefore, assignments to stderr or stdout
|
38
|
+
# within a `before(:all)` or otherwise outside of an example
|
39
|
+
# don't matter.
|
40
|
+
def inside_example_scope?(node)
|
41
|
+
return false if node.nil? || example_group?(node)
|
42
|
+
return true if example?(node)
|
43
|
+
return RuboCop::RSpec::Hook.new(node).example? if hook?(node)
|
44
|
+
|
45
|
+
inside_example_scope?(node.parent)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|