rubocop-rspec 1.37.0 → 1.40.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 +52 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +2 -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 +15 -2
- 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/factory_class_name.rb +16 -1
- 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 +13 -4
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- 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/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 +25 -47
- 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/hook.rb +44 -11
- data/lib/rubocop/rspec/language.rb +20 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +1 -1
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +16 -9
- data/lib/rubocop/rspec/util.rb +0 -19
data/lib/rubocop-rspec.rb
CHANGED
@@ -11,18 +11,19 @@ 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'
|
17
16
|
require_relative 'rubocop/rspec/concept'
|
18
17
|
require_relative 'rubocop/rspec/example_group'
|
19
18
|
require_relative 'rubocop/rspec/example'
|
20
19
|
require_relative 'rubocop/rspec/hook'
|
20
|
+
require_relative 'rubocop/rspec/variable'
|
21
21
|
require_relative 'rubocop/cop/rspec/cop'
|
22
22
|
require_relative 'rubocop/rspec/align_let_brace'
|
23
23
|
require_relative 'rubocop/rspec/factory_bot'
|
24
24
|
require_relative 'rubocop/rspec/final_end_location'
|
25
25
|
require_relative 'rubocop/rspec/blank_line_separation'
|
26
|
+
require_relative 'rubocop/rspec/corrector/move_node'
|
26
27
|
|
27
28
|
RuboCop::RSpec::Inject.defaults!
|
28
29
|
|
@@ -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
|
@@ -44,12 +49,20 @@ module RuboCop
|
|
44
49
|
|
45
50
|
def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
|
46
51
|
|
47
|
-
def on_top_level_describe(node,
|
52
|
+
def on_top_level_describe(node, (described_value, _))
|
48
53
|
return if shared_group?(root_node)
|
49
54
|
return if valid_describe?(node)
|
50
55
|
return if describe_with_rails_metadata?(node)
|
56
|
+
return if string_constant_describe?(described_value)
|
57
|
+
|
58
|
+
add_offense(described_value)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
51
62
|
|
52
|
-
|
63
|
+
def string_constant_describe?(described_value)
|
64
|
+
described_value.str_type? &&
|
65
|
+
described_value.value =~ /^((::)?[A-Z]\w*)+$/
|
53
66
|
end
|
54
67
|
end
|
55
68
|
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
|
@@ -6,6 +6,11 @@ module RuboCop
|
|
6
6
|
module FactoryBot
|
7
7
|
# Use string value when setting the class attribute explicitly.
|
8
8
|
#
|
9
|
+
# This cop would promote faster tests by lazy-loading of
|
10
|
+
# application files. Also, this could help you suppress potential bugs
|
11
|
+
# in combination with external libraries by avoiding a preload of
|
12
|
+
# application files from the factory files.
|
13
|
+
#
|
9
14
|
# @example
|
10
15
|
# # bad
|
11
16
|
# factory :foo, class: Foo do
|
@@ -15,7 +20,9 @@ module RuboCop
|
|
15
20
|
# factory :foo, class: 'Foo' do
|
16
21
|
# end
|
17
22
|
class FactoryClassName < Cop
|
18
|
-
MSG = "Pass '%<class_name>s' instead of
|
23
|
+
MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
|
24
|
+
'constant.'
|
25
|
+
ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
|
19
26
|
|
20
27
|
def_node_matcher :class_name, <<~PATTERN
|
21
28
|
(send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>))
|
@@ -23,6 +30,8 @@ module RuboCop
|
|
23
30
|
|
24
31
|
def on_send(node)
|
25
32
|
class_name(node) do |cn|
|
33
|
+
next if allowed?(cn.const_name)
|
34
|
+
|
26
35
|
add_offense(cn, message: format(MSG, class_name: cn.const_name))
|
27
36
|
end
|
28
37
|
end
|
@@ -32,6 +41,12 @@ module RuboCop
|
|
32
41
|
corrector.replace(node.loc.expression, "'#{node.source}'")
|
33
42
|
end
|
34
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def allowed?(const_name)
|
48
|
+
ALLOWED_CONSTANTS.include?(const_name)
|
49
|
+
end
|
35
50
|
end
|
36
51
|
end
|
37
52
|
end
|
@@ -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
|