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
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Sort RSpec metadata alphabetically.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# describe 'Something', :b, :a
|
11
|
+
# context 'Something', foo: 'bar', baz: true
|
12
|
+
# it 'works', :b, :a, foo: 'bar', baz: true
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# describe 'Something', :a, :b
|
16
|
+
# context 'Something', baz: true, foo: 'bar'
|
17
|
+
# it 'works', :a, :b, baz: true, foo: 'bar'
|
18
|
+
#
|
19
|
+
class SortMetadata < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
include Metadata
|
22
|
+
include RangeHelp
|
23
|
+
|
24
|
+
MSG = 'Sort metadata alphabetically.'
|
25
|
+
|
26
|
+
def on_metadata(symbols, hash)
|
27
|
+
pairs = hash&.pairs || []
|
28
|
+
return if sorted?(symbols, pairs)
|
29
|
+
|
30
|
+
crime_scene = crime_scene(symbols, pairs)
|
31
|
+
add_offense(crime_scene) do |corrector|
|
32
|
+
corrector.replace(crime_scene, replacement(symbols, pairs))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def crime_scene(symbols, pairs)
|
39
|
+
metadata = symbols + pairs
|
40
|
+
|
41
|
+
range_between(
|
42
|
+
metadata.first.source_range.begin_pos,
|
43
|
+
metadata.last.source_range.end_pos
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def replacement(symbols, pairs)
|
48
|
+
(sort_symbols(symbols) + sort_pairs(pairs)).map(&:source).join(', ')
|
49
|
+
end
|
50
|
+
|
51
|
+
def sorted?(symbols, pairs)
|
52
|
+
symbols == sort_symbols(symbols) && pairs == sort_pairs(pairs)
|
53
|
+
end
|
54
|
+
|
55
|
+
def sort_pairs(pairs)
|
56
|
+
pairs.sort_by { |pair| pair.key.source.downcase }
|
57
|
+
end
|
58
|
+
|
59
|
+
def sort_symbols(symbols)
|
60
|
+
symbols.sort_by do |symbol|
|
61
|
+
if symbol.str_type? || symbol.sym_type?
|
62
|
+
symbol.value.to_s.downcase
|
63
|
+
else
|
64
|
+
symbol.source.downcase
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that spec file paths are consistent and well-formed.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# whatever_spec.rb # describe MyClass
|
11
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# my_class_spec.rb # describe MyClass
|
15
|
+
# my_class_method_spec.rb # describe MyClass, '#method'
|
16
|
+
# my_class/method_spec.rb # describe MyClass, '#method'
|
17
|
+
#
|
18
|
+
# @example `CustomTransform: {RuboCop=>rubocop, RSpec=>rspec}` (default)
|
19
|
+
# # good
|
20
|
+
# rubocop_spec.rb # describe RuboCop
|
21
|
+
# rspec_spec.rb # describe RSpec
|
22
|
+
#
|
23
|
+
# @example `IgnoreMethods: false` (default)
|
24
|
+
# # bad
|
25
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
26
|
+
#
|
27
|
+
# @example `IgnoreMethods: true`
|
28
|
+
# # good
|
29
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
30
|
+
#
|
31
|
+
# @example `IgnoreMetadata: {type=>routing}` (default)
|
32
|
+
# # good
|
33
|
+
# whatever_spec.rb # describe MyClass, type: :routing do; end
|
34
|
+
#
|
35
|
+
class SpecFilePathFormat < Base
|
36
|
+
include TopLevelGroup
|
37
|
+
include Namespace
|
38
|
+
include FileHelp
|
39
|
+
|
40
|
+
MSG = 'Spec path should end with `%<suffix>s`.'
|
41
|
+
|
42
|
+
# @!method example_group_arguments(node)
|
43
|
+
def_node_matcher :example_group_arguments, <<~PATTERN
|
44
|
+
(block $(send #rspec? #ExampleGroups.all $_ $...) ...)
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
# @!method metadata_key_value(node)
|
48
|
+
def_node_search :metadata_key_value, '(pair (sym $_key) (sym $_value))'
|
49
|
+
|
50
|
+
def on_top_level_example_group(node)
|
51
|
+
return unless top_level_groups.one?
|
52
|
+
|
53
|
+
example_group_arguments(node) do |send_node, class_name, arguments|
|
54
|
+
next if !class_name.const_type? || ignore_metadata?(arguments)
|
55
|
+
|
56
|
+
ensure_correct_file_path(send_node, class_name, arguments)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def ensure_correct_file_path(send_node, class_name, arguments)
|
63
|
+
pattern = correct_path_pattern(class_name, arguments)
|
64
|
+
return if filename_ends_with?(pattern)
|
65
|
+
|
66
|
+
# For the suffix shown in the offense message, modify the regular
|
67
|
+
# expression pattern to resemble a glob pattern for clearer error
|
68
|
+
# messages.
|
69
|
+
suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
|
70
|
+
add_offense(send_node, message: format(MSG, suffix: suffix))
|
71
|
+
end
|
72
|
+
|
73
|
+
def ignore_metadata?(arguments)
|
74
|
+
arguments.any? do |argument|
|
75
|
+
metadata_key_value(argument).any? do |key, value|
|
76
|
+
ignore_metadata.values_at(key.to_s).include?(value.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def correct_path_pattern(class_name, arguments)
|
82
|
+
path = [expected_path(class_name)]
|
83
|
+
path << '.*' unless ignore?(arguments.first)
|
84
|
+
path << [name_pattern(arguments.first), '[^/]*_spec\.rb']
|
85
|
+
path.join
|
86
|
+
end
|
87
|
+
|
88
|
+
def name_pattern(method_name)
|
89
|
+
return if ignore?(method_name)
|
90
|
+
|
91
|
+
method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
|
92
|
+
end
|
93
|
+
|
94
|
+
def ignore?(method_name)
|
95
|
+
!method_name&.str_type? || ignore_methods?
|
96
|
+
end
|
97
|
+
|
98
|
+
def expected_path(constant)
|
99
|
+
constants = namespace(constant) + constant.const_name.split('::')
|
100
|
+
|
101
|
+
File.join(
|
102
|
+
constants.map do |name|
|
103
|
+
custom_transform.fetch(name) { camel_to_snake_case(name) }
|
104
|
+
end
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def camel_to_snake_case(string)
|
109
|
+
string
|
110
|
+
.gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
|
111
|
+
.gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
|
112
|
+
.downcase
|
113
|
+
end
|
114
|
+
|
115
|
+
def custom_transform
|
116
|
+
cop_config.fetch('CustomTransform', {})
|
117
|
+
end
|
118
|
+
|
119
|
+
def ignore_methods?
|
120
|
+
cop_config['IgnoreMethods']
|
121
|
+
end
|
122
|
+
|
123
|
+
def ignore_metadata
|
124
|
+
cop_config.fetch('IgnoreMetadata', {})
|
125
|
+
end
|
126
|
+
|
127
|
+
def filename_ends_with?(pattern)
|
128
|
+
expanded_file_path.match?("#{pattern}$")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that spec file paths suffix are consistent and well-formed.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# my_class/foo_specorb.rb # describe MyClass
|
11
|
+
# spec/models/user.rb # describe User
|
12
|
+
# spec/models/user_specxrb # describe User
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# my_class_spec.rb # describe MyClass
|
16
|
+
#
|
17
|
+
# # good - shared examples are allowed
|
18
|
+
# spec/models/user.rb # shared_examples_for 'foo'
|
19
|
+
#
|
20
|
+
class SpecFilePathSuffix < Base
|
21
|
+
include TopLevelGroup
|
22
|
+
include FileHelp
|
23
|
+
|
24
|
+
MSG = 'Spec path should end with `_spec.rb`.'
|
25
|
+
|
26
|
+
def on_top_level_example_group(node)
|
27
|
+
example_group?(node) do
|
28
|
+
add_global_offense(MSG) unless correct_path?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def correct_path?
|
35
|
+
expanded_file_path.end_with?('_spec.rb')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that message expectations do not have a configured response.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# expect(foo).to receive(:bar).with(42).and_return("hello world")
|
11
|
+
#
|
12
|
+
# # good (without spies)
|
13
|
+
# allow(foo).to receive(:bar).with(42).and_return("hello world")
|
14
|
+
# expect(foo).to receive(:bar).with(42)
|
15
|
+
#
|
16
|
+
class StubbedMock < Base
|
17
|
+
MSG = 'Prefer `%<replacement>s` over `%<method_name>s` when ' \
|
18
|
+
'configuring a response.'
|
19
|
+
|
20
|
+
# @!method message_expectation?(node)
|
21
|
+
# Match message expectation matcher
|
22
|
+
#
|
23
|
+
# @example source that matches
|
24
|
+
# receive(:foo)
|
25
|
+
#
|
26
|
+
# @example source that matches
|
27
|
+
# receive_message_chain(:foo, :bar)
|
28
|
+
#
|
29
|
+
# @example source that matches
|
30
|
+
# receive(:foo).with('bar')
|
31
|
+
#
|
32
|
+
# @param node [RuboCop::AST::Node]
|
33
|
+
# @return [Array<RuboCop::AST::Node>] matching nodes
|
34
|
+
def_node_matcher :message_expectation?, <<~PATTERN
|
35
|
+
{
|
36
|
+
(send nil? { :receive :receive_message_chain } ...) # receive(:foo)
|
37
|
+
(send (send nil? :receive ...) :with ...) # receive(:foo).with('bar')
|
38
|
+
}
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
# @!method configured_response?(node)
|
42
|
+
def_node_matcher :configured_response?, <<~PATTERN
|
43
|
+
{ :and_return :and_raise :and_throw :and_yield
|
44
|
+
:and_call_original :and_wrap_original }
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
# @!method expectation(node)
|
48
|
+
# Match expectation
|
49
|
+
#
|
50
|
+
# @example source that matches
|
51
|
+
# is_expected.to be_in_the_bar
|
52
|
+
#
|
53
|
+
# @example source that matches
|
54
|
+
# expect(cocktail).to contain_exactly(:fresh_orange_juice, :campari)
|
55
|
+
#
|
56
|
+
# @example source that matches
|
57
|
+
# expect_any_instance_of(Officer).to be_alert
|
58
|
+
#
|
59
|
+
# @param node [RuboCop::AST::Node]
|
60
|
+
# @yield [RuboCop::AST::Node] expectation, method name, matcher
|
61
|
+
def_node_matcher :expectation, <<~PATTERN
|
62
|
+
(send
|
63
|
+
$(send nil? $#Expectations.all ...)
|
64
|
+
:to $_)
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method matcher_with_configured_response(node)
|
68
|
+
# Match matcher with a configured response
|
69
|
+
#
|
70
|
+
# @example source that matches
|
71
|
+
# receive(:foo).and_return('bar')
|
72
|
+
#
|
73
|
+
# @example source that matches
|
74
|
+
# receive(:lower).and_raise(SomeError)
|
75
|
+
#
|
76
|
+
# @example source that matches
|
77
|
+
# receive(:redirect).and_call_original
|
78
|
+
#
|
79
|
+
# @param node [RuboCop::AST::Node]
|
80
|
+
# @yield [RuboCop::AST::Node] matcher
|
81
|
+
def_node_matcher :matcher_with_configured_response, <<~PATTERN
|
82
|
+
(send #message_expectation? #configured_response? _)
|
83
|
+
PATTERN
|
84
|
+
|
85
|
+
# @!method matcher_with_return_block(node)
|
86
|
+
# Match matcher with a return block
|
87
|
+
#
|
88
|
+
# @example source that matches
|
89
|
+
# receive(:foo) { 'bar' }
|
90
|
+
#
|
91
|
+
# @param node [RuboCop::AST::Node]
|
92
|
+
# @yield [RuboCop::AST::Node] matcher
|
93
|
+
def_node_matcher :matcher_with_return_block, <<~PATTERN
|
94
|
+
(block #message_expectation? (args) _) # receive(:foo) { 'bar' }
|
95
|
+
PATTERN
|
96
|
+
|
97
|
+
# @!method matcher_with_hash(node)
|
98
|
+
# Match matcher with a configured response defined as a hash
|
99
|
+
#
|
100
|
+
# @example source that matches
|
101
|
+
# receive_messages(foo: 'bar', baz: 'qux')
|
102
|
+
#
|
103
|
+
# @example source that matches
|
104
|
+
# receive_message_chain(:foo, bar: 'baz')
|
105
|
+
#
|
106
|
+
# @param node [RuboCop::AST::Node]
|
107
|
+
# @yield [RuboCop::AST::Node] matcher
|
108
|
+
def_node_matcher :matcher_with_hash, <<~PATTERN
|
109
|
+
{
|
110
|
+
(send nil? :receive_messages hash) # receive_messages(foo: 'bar', baz: 'qux')
|
111
|
+
(send nil? :receive_message_chain ... hash) # receive_message_chain(:foo, bar: 'baz')
|
112
|
+
}
|
113
|
+
PATTERN
|
114
|
+
|
115
|
+
# @!method matcher_with_blockpass(node)
|
116
|
+
# Match matcher with a configured response in block-pass
|
117
|
+
#
|
118
|
+
# @example source that matches
|
119
|
+
# receive(:foo, &canned)
|
120
|
+
#
|
121
|
+
# @example source that matches
|
122
|
+
# receive_message_chain(:foo, :bar, &canned)
|
123
|
+
#
|
124
|
+
# @example source that matches
|
125
|
+
# receive(:foo).with('bar', &canned)
|
126
|
+
#
|
127
|
+
# @param node [RuboCop::AST::Node]
|
128
|
+
# @yield [RuboCop::AST::Node] matcher
|
129
|
+
def_node_matcher :matcher_with_blockpass, <<~PATTERN
|
130
|
+
{
|
131
|
+
(send nil? { :receive :receive_message_chain } ... block_pass) # receive(:foo, &canned)
|
132
|
+
(send (send nil? :receive ...) :with ... block_pass) # receive(:foo).with('foo', &canned)
|
133
|
+
}
|
134
|
+
PATTERN
|
135
|
+
|
136
|
+
RESTRICT_ON_SEND = %i[to].freeze
|
137
|
+
|
138
|
+
def on_send(node)
|
139
|
+
expectation(node) do |expectation, method_name, matcher|
|
140
|
+
on_expectation(expectation, method_name, matcher)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def on_expectation(expectation, method_name, matcher)
|
147
|
+
flag_expectation = lambda do
|
148
|
+
add_offense(expectation, message: msg(method_name))
|
149
|
+
end
|
150
|
+
|
151
|
+
matcher_with_configured_response(matcher, &flag_expectation)
|
152
|
+
matcher_with_return_block(matcher, &flag_expectation)
|
153
|
+
matcher_with_hash(matcher, &flag_expectation)
|
154
|
+
matcher_with_blockpass(matcher, &flag_expectation)
|
155
|
+
end
|
156
|
+
|
157
|
+
def msg(method_name)
|
158
|
+
format(MSG,
|
159
|
+
method_name: method_name,
|
160
|
+
replacement: replacement(method_name))
|
161
|
+
end
|
162
|
+
|
163
|
+
def replacement(method_name)
|
164
|
+
case method_name
|
165
|
+
when :expect
|
166
|
+
:allow
|
167
|
+
when :is_expected
|
168
|
+
'allow(subject)'
|
169
|
+
when :expect_any_instance_of
|
170
|
+
:allow_any_instance_of
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Ensure that subject is defined using subject helper.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# let(:subject) { foo }
|
11
|
+
# let!(:subject) { foo }
|
12
|
+
# subject(:subject) { foo }
|
13
|
+
# subject!(:subject) { foo }
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# block = -> {}
|
17
|
+
# let(:subject, &block)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# subject(:test_subject) { foo }
|
21
|
+
#
|
22
|
+
class SubjectDeclaration < Base
|
23
|
+
MSG_LET = 'Use subject explicitly rather than using let'
|
24
|
+
MSG_REDUNDANT = 'Ambiguous declaration of subject'
|
25
|
+
|
26
|
+
# @!method offensive_subject_declaration?(node)
|
27
|
+
def_node_matcher :offensive_subject_declaration?, <<~PATTERN
|
28
|
+
(send nil? ${#Subjects.all #Helpers.all} ({sym str} #Subjects.all) ...)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
offense = offensive_subject_declaration?(node)
|
33
|
+
return unless offense
|
34
|
+
|
35
|
+
add_offense(node, message: message_for(offense))
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def message_for(offense)
|
41
|
+
Helpers.all(offense) ? MSG_LET : MSG_REDUNDANT
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,51 +1,86 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module RuboCop
|
4
6
|
module Cop
|
5
7
|
module RSpec
|
6
8
|
# Checks for stubbed test subjects.
|
7
9
|
#
|
10
|
+
# Checks nested subject stubs for innermost subject definition
|
11
|
+
# when subject is also defined in parent example groups.
|
12
|
+
#
|
8
13
|
# @see https://robots.thoughtbot.com/don-t-stub-the-system-under-test
|
14
|
+
# @see https://penelope.zone/2015/12/27/introducing-rspec-smells-and-where-to-find-them.html#smell-1-stubjec
|
9
15
|
#
|
10
16
|
# @example
|
11
17
|
# # bad
|
12
|
-
# describe
|
13
|
-
# subject(:
|
18
|
+
# describe Article do
|
19
|
+
# subject(:article) { Article.new }
|
20
|
+
#
|
21
|
+
# it 'indicates that the author is unknown' do
|
22
|
+
# allow(article).to receive(:author).and_return(nil)
|
23
|
+
# expect(article.description).to include('by an unknown author')
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # bad
|
28
|
+
# describe Article do
|
29
|
+
# subject(:foo) { Article.new }
|
30
|
+
#
|
31
|
+
# context 'nested subject' do
|
32
|
+
# subject(:article) { Article.new }
|
14
33
|
#
|
15
|
-
#
|
16
|
-
#
|
34
|
+
# it 'indicates that the author is unknown' do
|
35
|
+
# allow(article).to receive(:author).and_return(nil)
|
36
|
+
# expect(article.description).to include('by an unknown author')
|
37
|
+
# end
|
17
38
|
# end
|
18
39
|
# end
|
19
40
|
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
41
|
+
# # good
|
42
|
+
# describe Article do
|
43
|
+
# subject(:article) { Article.new(author: nil) }
|
44
|
+
#
|
45
|
+
# it 'indicates that the author is unknown' do
|
46
|
+
# expect(article.description).to include('by an unknown author')
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
class SubjectStub < Base
|
51
|
+
include TopLevelGroup
|
25
52
|
|
26
|
-
MSG = 'Do not stub
|
53
|
+
MSG = 'Do not stub methods of the object under test.'
|
27
54
|
|
28
|
-
# @!method subject(node)
|
55
|
+
# @!method subject?(node)
|
29
56
|
# Find a named or unnamed subject definition
|
30
57
|
#
|
31
58
|
# @example anonymous subject
|
32
|
-
# subject(parse('subject { foo }').ast) do |name|
|
59
|
+
# subject?(parse('subject { foo }').ast) do |name|
|
33
60
|
# name # => :subject
|
34
61
|
# end
|
35
62
|
#
|
36
63
|
# @example named subject
|
37
|
-
# subject(parse('subject(:thing) { foo }').ast) do |name|
|
64
|
+
# subject?(parse('subject(:thing) { foo }').ast) do |name|
|
38
65
|
# name # => :thing
|
39
66
|
# end
|
40
67
|
#
|
41
|
-
# @param node [RuboCop::Node]
|
68
|
+
# @param node [RuboCop::AST::Node]
|
42
69
|
#
|
43
70
|
# @yield [Symbol] subject name
|
44
|
-
def_node_matcher :subject
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
71
|
+
def_node_matcher :subject?, <<~PATTERN
|
72
|
+
(block
|
73
|
+
(send nil?
|
74
|
+
{ #Subjects.all (sym $_) | $#Subjects.all }
|
75
|
+
) args ...)
|
76
|
+
PATTERN
|
77
|
+
|
78
|
+
# @!method let?(node)
|
79
|
+
# Find a memoized helper
|
80
|
+
def_node_matcher :let?, <<~PATTERN
|
81
|
+
(block
|
82
|
+
(send nil? :let (sym $_)
|
83
|
+
) args ...)
|
49
84
|
PATTERN
|
50
85
|
|
51
86
|
# @!method message_expectation?(node, method_name)
|
@@ -58,76 +93,60 @@ module RuboCop
|
|
58
93
|
# expect(foo).to receive(:bar)
|
59
94
|
# expect(foo).to receive(:bar).with(1)
|
60
95
|
# expect(foo).to receive(:bar).with(1).and_return(2)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
96
|
+
#
|
97
|
+
def_node_matcher :message_expectation?, <<~PATTERN
|
98
|
+
(send
|
99
|
+
{
|
100
|
+
(send nil? { :expect :allow } (send nil? %))
|
101
|
+
(send nil? :is_expected)
|
102
|
+
}
|
103
|
+
#Runners.all
|
104
|
+
#message_expectation_matcher?
|
105
|
+
)
|
66
106
|
PATTERN
|
67
107
|
|
68
|
-
|
108
|
+
# @!method message_expectation_matcher?(node)
|
109
|
+
def_node_search :message_expectation_matcher?, <<~PATTERN
|
110
|
+
(send nil? {
|
111
|
+
:receive :receive_messages :receive_message_chain :have_received
|
112
|
+
} ...)
|
113
|
+
PATTERN
|
69
114
|
|
70
|
-
def
|
71
|
-
|
115
|
+
def on_top_level_group(node)
|
116
|
+
@explicit_subjects = find_all_explicit(node) { |n| subject?(n) }
|
117
|
+
@subject_overrides = find_all_explicit(node) { |n| let?(n) }
|
72
118
|
|
73
|
-
|
119
|
+
find_subject_expectations(node) do |stub|
|
120
|
+
add_offense(stub)
|
121
|
+
end
|
74
122
|
end
|
75
123
|
|
76
124
|
private
|
77
125
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
# @yield [RuboCop::Node] message expectations for subject
|
83
|
-
def find_subject_stub(node, &block)
|
84
|
-
find_subject(node) do |subject_name, context|
|
85
|
-
find_subject_expectation(context, subject_name, &block)
|
86
|
-
end
|
87
|
-
end
|
126
|
+
def find_all_explicit(node)
|
127
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
128
|
+
name = yield(child)
|
129
|
+
next unless name
|
88
130
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# @param subject_name [Symbol] name of subject
|
93
|
-
#
|
94
|
-
# @yield [RuboCop::Node] message expectation
|
95
|
-
def find_subject_expectation(node, subject_name, &block)
|
96
|
-
# Do not search node if it is an example group with its own subject.
|
97
|
-
return if example_group?(node) && redefines_subject?(node)
|
131
|
+
outer_example_group = child.each_ancestor(:block).find do |a|
|
132
|
+
example_group?(a)
|
133
|
+
end
|
98
134
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
# Recurse through node's children looking for a message expectation.
|
103
|
-
node.each_child_node do |child|
|
104
|
-
find_subject_expectation(child, subject_name, &block)
|
135
|
+
h[outer_example_group] ||= []
|
136
|
+
h[outer_example_group] << name
|
105
137
|
end
|
106
138
|
end
|
107
139
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
#
|
112
|
-
# @return [Boolean]
|
113
|
-
def redefines_subject?(node)
|
114
|
-
node.each_child_node.any? do |child|
|
115
|
-
subject(child) || redefines_subject?(child)
|
116
|
-
end
|
117
|
-
end
|
140
|
+
def find_subject_expectations(node, subject_names = [], &block)
|
141
|
+
subject_names = [*subject_names, *@explicit_subjects[node]]
|
142
|
+
subject_names -= @subject_overrides[node] if @subject_overrides[node]
|
118
143
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
# @param parent [RuboCop::Node,nil]
|
123
|
-
#
|
124
|
-
# @yieldparam subject_name [Symbol] name of subject being defined
|
125
|
-
# @yieldparam parent [RuboCop::Node] parent of subject definition
|
126
|
-
def find_subject(node, parent: nil, &block)
|
127
|
-
subject(node) { |name| yield(name, parent) }
|
144
|
+
names = Set[*subject_names, :subject]
|
145
|
+
expectation_detected = message_expectation?(node, names)
|
146
|
+
return yield(node) if expectation_detected
|
128
147
|
|
129
|
-
node.each_child_node do |child|
|
130
|
-
|
148
|
+
node.each_child_node(:send, :def, :block, :begin) do |child|
|
149
|
+
find_subject_expectations(child, subject_names, &block)
|
131
150
|
end
|
132
151
|
end
|
133
152
|
end
|