rubocop-rspec 1.37.1 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/CODE_OF_CONDUCT.md +17 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +3 -1
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
- data/lib/rubocop/cop/rspec/cop.rb +9 -29
- data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
- data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
- data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +5 -2
- data/lib/rubocop/cop/rspec/file_path.rb +32 -4
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/instance_variable.rb +16 -11
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -2
- data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +3 -2
- data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
- data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
- data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
- data/lib/rubocop/cop/rspec_cops.rb +7 -1
- data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
- data/lib/rubocop/rspec/description_extractor.rb +2 -6
- data/lib/rubocop/rspec/example.rb +1 -1
- data/lib/rubocop/rspec/example_group.rb +21 -49
- data/lib/rubocop/rspec/factory_bot.rb +7 -1
- data/lib/rubocop/rspec/hook.rb +44 -11
- data/lib/rubocop/rspec/language.rb +20 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
- data/lib/rubocop/rspec/top_level_group.rb +44 -0
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +20 -11
- data/lib/rubocop/rspec/util.rb +0 -19
data/lib/rubocop-rspec.rb
CHANGED
@@ -11,18 +11,20 @@ require_relative 'rubocop/rspec/inject'
|
|
11
11
|
require_relative 'rubocop/rspec/node'
|
12
12
|
require_relative 'rubocop/rspec/top_level_describe'
|
13
13
|
require_relative 'rubocop/rspec/wording'
|
14
|
-
require_relative 'rubocop/rspec/util'
|
15
14
|
require_relative 'rubocop/rspec/language'
|
16
15
|
require_relative 'rubocop/rspec/language/node_pattern'
|
16
|
+
require_relative 'rubocop/rspec/top_level_group'
|
17
17
|
require_relative 'rubocop/rspec/concept'
|
18
18
|
require_relative 'rubocop/rspec/example_group'
|
19
19
|
require_relative 'rubocop/rspec/example'
|
20
20
|
require_relative 'rubocop/rspec/hook'
|
21
|
+
require_relative 'rubocop/rspec/variable'
|
21
22
|
require_relative 'rubocop/cop/rspec/cop'
|
22
23
|
require_relative 'rubocop/rspec/align_let_brace'
|
23
24
|
require_relative 'rubocop/rspec/factory_bot'
|
24
25
|
require_relative 'rubocop/rspec/final_end_location'
|
25
26
|
require_relative 'rubocop/rspec/blank_line_separation'
|
27
|
+
require_relative 'rubocop/rspec/corrector/move_node'
|
26
28
|
|
27
29
|
RuboCop::RSpec::Inject.defaults!
|
28
30
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Checks for boolean visibility in capybara finders.
|
8
|
+
#
|
9
|
+
# Capybara lets you find elements that match a certain visibility using
|
10
|
+
# the `:visible` option. `:visible` accepts both boolean and symbols as
|
11
|
+
# values, however using booleans can have unwanted effects. `visible:
|
12
|
+
# false` does not find just invisible elements, but both visible and
|
13
|
+
# invisible elements. For expressiveness and clarity, use one of the
|
14
|
+
# symbol values, `:all`, `:hidden` or `:visible`.
|
15
|
+
# (https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all)
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# expect(page).to have_selector('.foo', visible: false)
|
21
|
+
# expect(page).to have_css('.foo', visible: true)
|
22
|
+
# expect(page).to have_link('my link', visible: false)
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# expect(page).to have_selector('.foo', visible: :visible)
|
26
|
+
# expect(page).to have_css('.foo', visible: :all)
|
27
|
+
# expect(page).to have_link('my link', visible: :hidden)
|
28
|
+
#
|
29
|
+
class VisibilityMatcher < Cop
|
30
|
+
MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
|
31
|
+
MSG_TRUE = 'Use `:visible` instead of `true`.'
|
32
|
+
CAPYBARA_MATCHER_METHODS = %i[
|
33
|
+
have_selector
|
34
|
+
have_css
|
35
|
+
have_xpath
|
36
|
+
have_link
|
37
|
+
have_button
|
38
|
+
have_field
|
39
|
+
have_select
|
40
|
+
have_table
|
41
|
+
have_checked_field
|
42
|
+
have_unchecked_field
|
43
|
+
have_text
|
44
|
+
have_content
|
45
|
+
].freeze
|
46
|
+
|
47
|
+
def_node_matcher :visible_true?, <<~PATTERN
|
48
|
+
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
def_node_matcher :visible_false?, <<~PATTERN
|
52
|
+
(send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
def on_send(node)
|
56
|
+
visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
|
57
|
+
visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def capybara_matcher?(method_name)
|
63
|
+
CAPYBARA_MATCHER_METHODS.include? method_name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -5,7 +5,11 @@ module RuboCop
|
|
5
5
|
module RSpec
|
6
6
|
# Checks that `context` docstring starts with an allowed prefix.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# The default list of prefixes is minimal. Users are encouraged to tailor
|
9
|
+
# the configuration to meet project needs. Other acceptable prefixes may
|
10
|
+
# include `if`, `unless`, `for`, `before`, `after`, or `during`.
|
11
|
+
#
|
12
|
+
# @see https://rspec.rubystyle.guide/#context-descriptions
|
9
13
|
# @see http://www.betterspecs.org/#contexts
|
10
14
|
#
|
11
15
|
# @example `Prefixes` configuration
|
@@ -1,29 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RuboCop
|
4
|
-
module Cop
|
5
|
-
WorkaroundCop = Cop.dup
|
6
|
-
|
7
|
-
# Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
|
8
|
-
# the inherited method without breaking functionality
|
9
|
-
class WorkaroundCop
|
10
|
-
# Remove the Cop.inherited method to be a noop. Our RSpec::Cop
|
11
|
-
# class will invoke the inherited hook instead
|
12
|
-
class << self
|
13
|
-
undef inherited
|
14
|
-
def inherited(*) end
|
15
|
-
end
|
16
|
-
|
17
|
-
# Special case `Module#<` so that the rspec support rubocop exports
|
18
|
-
# is compatible with our subclass
|
19
|
-
def self.<(other)
|
20
|
-
other.equal?(RuboCop::Cop::Cop) || super
|
21
|
-
end
|
22
|
-
end
|
23
|
-
private_constant(:WorkaroundCop)
|
24
|
-
|
4
|
+
module Cop
|
25
5
|
module RSpec
|
26
|
-
# @abstract parent class to
|
6
|
+
# @abstract parent class to RSpec cops
|
27
7
|
#
|
28
8
|
# The criteria for whether rubocop-rspec analyzes a certain ruby file
|
29
9
|
# is configured via `AllCops/RSpec`. For example, if you want to
|
@@ -31,13 +11,13 @@ module RuboCop
|
|
31
11
|
# then you could add this to your configuration:
|
32
12
|
#
|
33
13
|
# @example configuring analyzed paths
|
34
|
-
#
|
35
|
-
# AllCops:
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
class Cop <
|
14
|
+
# # .rubocop.yml
|
15
|
+
# # AllCops:
|
16
|
+
# # RSpec:
|
17
|
+
# # Patterns:
|
18
|
+
# # - '_test.rb$'
|
19
|
+
# # - '(?:^|/)test/'
|
20
|
+
class Cop < ::RuboCop::Cop::Cop
|
41
21
|
include RuboCop::RSpec::Language
|
42
22
|
include RuboCop::RSpec::Language::NodePattern
|
43
23
|
|
@@ -12,6 +12,11 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# # good
|
14
14
|
# describe TestedClass do
|
15
|
+
# subject { described_class }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# describe 'TestedClass::VERSION' do
|
19
|
+
# subject { Object.const_get(self.class.description) }
|
15
20
|
# end
|
16
21
|
#
|
17
22
|
# describe "A feature example", type: :feature do
|
@@ -38,18 +43,28 @@ module RuboCop
|
|
38
43
|
def_node_matcher :rails_metadata?, <<-PATTERN
|
39
44
|
(pair
|
40
45
|
(sym :type)
|
41
|
-
(sym {
|
46
|
+
(sym {
|
47
|
+
:channel :controller :helper :job :mailer :model :request
|
48
|
+
:routing :view :feature :system :mailbox
|
49
|
+
}
|
50
|
+
)
|
42
51
|
)
|
43
52
|
PATTERN
|
44
53
|
|
45
|
-
|
46
|
-
|
47
|
-
def on_top_level_describe(node, args)
|
54
|
+
def on_top_level_describe(node, (described_value, _))
|
48
55
|
return if shared_group?(root_node)
|
49
56
|
return if valid_describe?(node)
|
50
57
|
return if describe_with_rails_metadata?(node)
|
58
|
+
return if string_constant_describe?(described_value)
|
59
|
+
|
60
|
+
add_offense(described_value)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
51
64
|
|
52
|
-
|
65
|
+
def string_constant_describe?(described_value)
|
66
|
+
described_value.str_type? &&
|
67
|
+
described_value.value =~ /^((::)?[A-Z]\w*)+$/
|
53
68
|
end
|
54
69
|
end
|
55
70
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for empty before and after hooks.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# before {}
|
11
|
+
# after do; end
|
12
|
+
# before(:all) do
|
13
|
+
# end
|
14
|
+
# after(:all) { }
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# before { create_users }
|
18
|
+
# after do
|
19
|
+
# cleanup_users
|
20
|
+
# end
|
21
|
+
# before(:all) do
|
22
|
+
# create_feed
|
23
|
+
# end
|
24
|
+
# after(:all) { cleanup_feed }
|
25
|
+
class EmptyHook < Cop
|
26
|
+
include RuboCop::Cop::RangeHelp
|
27
|
+
|
28
|
+
MSG = 'Empty hook detected.'
|
29
|
+
|
30
|
+
def_node_matcher :empty_hook?, <<~PATTERN
|
31
|
+
(block $#{Hooks::ALL.send_pattern} _ nil?)
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_block(node)
|
35
|
+
empty_hook?(node) do |hook|
|
36
|
+
add_offense(hook)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocorrect(node)
|
41
|
+
lambda do |corrector|
|
42
|
+
block = node.parent
|
43
|
+
range = range_with_surrounding_space(range: block.loc.expression)
|
44
|
+
corrector.remove(range)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -41,11 +41,31 @@ module RuboCop
|
|
41
41
|
regexp
|
42
42
|
].freeze
|
43
43
|
|
44
|
-
|
44
|
+
SUPPORTED_MATCHERS = %i[eq eql equal be].freeze
|
45
|
+
|
46
|
+
def_node_matcher :expect_literal, <<~PATTERN
|
47
|
+
(send
|
48
|
+
(send nil? :expect $#literal?)
|
49
|
+
#{Runners::ALL.node_pattern_union}
|
50
|
+
{
|
51
|
+
(send (send nil? $:be) :== $_)
|
52
|
+
(send nil? $_ $_ ...)
|
53
|
+
}
|
54
|
+
)
|
55
|
+
PATTERN
|
45
56
|
|
46
57
|
def on_send(node)
|
47
58
|
expect_literal(node) do |argument|
|
48
|
-
add_offense(argument)
|
59
|
+
add_offense(node, location: argument.source_range)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def autocorrect(node)
|
64
|
+
actual, matcher, expected = expect_literal(node)
|
65
|
+
lambda do |corrector|
|
66
|
+
return unless SUPPORTED_MATCHERS.include?(matcher)
|
67
|
+
|
68
|
+
swap(corrector, actual, expected)
|
49
69
|
end
|
50
70
|
end
|
51
71
|
|
@@ -65,6 +85,11 @@ module RuboCop
|
|
65
85
|
COMPLEX_LITERALS.include?(node.type) &&
|
66
86
|
node.each_child_node.all?(&method(:literal?))
|
67
87
|
end
|
88
|
+
|
89
|
+
def swap(corrector, actual, expected)
|
90
|
+
corrector.replace(actual.source_range, expected.source)
|
91
|
+
corrector.replace(expected.source_range, actual.source)
|
92
|
+
end
|
68
93
|
end
|
69
94
|
end
|
70
95
|
end
|
@@ -31,12 +31,15 @@ module RuboCop
|
|
31
31
|
(send _ !#reserved_method? $...)
|
32
32
|
PATTERN
|
33
33
|
|
34
|
-
|
34
|
+
def_node_matcher :factory_attributes, <<-PATTERN
|
35
35
|
(block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
|
36
36
|
PATTERN
|
37
37
|
|
38
38
|
def on_block(node)
|
39
|
-
factory_attributes(node)
|
39
|
+
attributes = factory_attributes(node) || []
|
40
|
+
attributes = [attributes] unless attributes.is_a?(Array)
|
41
|
+
|
42
|
+
attributes.each do |attribute|
|
40
43
|
next unless offensive_receiver?(attribute.receiver, node)
|
41
44
|
next if proc?(attribute) || association?(attribute.first_argument)
|
42
45
|
|
@@ -3,10 +3,11 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
|
-
# Checks that spec file paths are consistent
|
6
|
+
# Checks that spec file paths are consistent and well-formed.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# By default, this checks that spec file paths are consistent with the
|
9
|
+
# test subject and and enforces that it reflects the described
|
10
|
+
# class/module and its optionally called out method.
|
10
11
|
#
|
11
12
|
# With the configuration option `IgnoreMethods` the called out method will
|
12
13
|
# be ignored when determining the enforced path.
|
@@ -15,6 +16,10 @@ module RuboCop
|
|
15
16
|
# be specified that should not as usual be transformed from CamelCase to
|
16
17
|
# snake_case (e.g. 'RuboCop' => 'rubocop' ).
|
17
18
|
#
|
19
|
+
# With the configuration option `SpecSuffixOnly` test files will only
|
20
|
+
# be checked to ensure they end in '_spec.rb'. This option disables
|
21
|
+
# checking for consistency in the test subject or test methods.
|
22
|
+
#
|
18
23
|
# @example
|
19
24
|
# # bad
|
20
25
|
# whatever_spec.rb # describe MyClass
|
@@ -41,6 +46,16 @@ module RuboCop
|
|
41
46
|
# # good
|
42
47
|
# my_class_spec.rb # describe MyClass, '#method'
|
43
48
|
#
|
49
|
+
# @example when configuration is `SpecSuffixOnly: true`
|
50
|
+
# # good
|
51
|
+
# whatever_spec.rb # describe MyClass
|
52
|
+
#
|
53
|
+
# # good
|
54
|
+
# my_class_spec.rb # describe MyClass
|
55
|
+
#
|
56
|
+
# # good
|
57
|
+
# my_class_spec.rb # describe MyClass, '#method'
|
58
|
+
#
|
44
59
|
class FilePath < Cop
|
45
60
|
include RuboCop::RSpec::TopLevelDescribe
|
46
61
|
|
@@ -70,9 +85,15 @@ module RuboCop
|
|
70
85
|
end
|
71
86
|
|
72
87
|
def glob_for((described_class, method_name))
|
88
|
+
return glob_for_spec_suffix_only? if spec_suffix_only?
|
89
|
+
|
73
90
|
"#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
|
74
91
|
end
|
75
92
|
|
93
|
+
def glob_for_spec_suffix_only?
|
94
|
+
'*_spec.rb'
|
95
|
+
end
|
96
|
+
|
76
97
|
def name_glob(name)
|
77
98
|
return unless name&.str_type?
|
78
99
|
|
@@ -103,12 +124,19 @@ module RuboCop
|
|
103
124
|
end
|
104
125
|
|
105
126
|
def filename_ends_with?(glob)
|
106
|
-
|
127
|
+
filename =
|
128
|
+
RuboCop::PathUtil.relative_path(processed_source.buffer.name)
|
129
|
+
.gsub('../', '')
|
130
|
+
File.fnmatch?("*#{glob}", filename)
|
107
131
|
end
|
108
132
|
|
109
133
|
def relevant_rubocop_rspec_file?(_file)
|
110
134
|
true
|
111
135
|
end
|
136
|
+
|
137
|
+
def spec_suffix_only?
|
138
|
+
cop_config['SpecSuffixOnly']
|
139
|
+
end
|
112
140
|
end
|
113
141
|
end
|
114
142
|
end
|
@@ -24,9 +24,6 @@ module RuboCop
|
|
24
24
|
# end
|
25
25
|
#
|
26
26
|
class HooksBeforeExamples < Cop
|
27
|
-
include RangeHelp
|
28
|
-
include RuboCop::RSpec::FinalEndLocation
|
29
|
-
|
30
27
|
MSG = 'Move `%<hook>s` above the examples in the group.'
|
31
28
|
|
32
29
|
def_node_matcher :example_or_group?, <<-PATTERN
|
@@ -45,11 +42,9 @@ module RuboCop
|
|
45
42
|
def autocorrect(node)
|
46
43
|
lambda do |corrector|
|
47
44
|
first_example = find_first_example(node.parent)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
corrector.insert_before(first_example_pos, source(node) + indent)
|
52
|
-
corrector.remove(node_range_with_surrounding_space(node))
|
45
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
46
|
+
node, corrector, processed_source
|
47
|
+
).move_before(first_example)
|
53
48
|
end
|
54
49
|
end
|
55
50
|
|
@@ -77,19 +72,6 @@ module RuboCop
|
|
77
72
|
def find_first_example(node)
|
78
73
|
node.children.find { |sibling| example_or_group?(sibling) }
|
79
74
|
end
|
80
|
-
|
81
|
-
def node_range_with_surrounding_space(node)
|
82
|
-
range = node_range(node)
|
83
|
-
range_by_whole_lines(range, include_final_newline: true)
|
84
|
-
end
|
85
|
-
|
86
|
-
def source(node)
|
87
|
-
node_range(node).source
|
88
|
-
end
|
89
|
-
|
90
|
-
def node_range(node)
|
91
|
-
node.loc.expression.with(end_pos: final_end_location(node).end_pos)
|
92
|
-
end
|
93
75
|
end
|
94
76
|
end
|
95
77
|
end
|