rubocop-rspec 1.37.1 → 1.41.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +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
|