rubocop-rspec 1.8.0 → 1.9.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 +8 -0
- data/Gemfile +2 -1
- data/Rakefile +3 -1
- data/config/default.yml +16 -0
- data/lib/rubocop-rspec.rb +6 -1
- data/lib/rubocop/cop/rspec/any_instance.rb +0 -2
- data/lib/rubocop/cop/rspec/be_eql.rb +1 -2
- data/lib/rubocop/cop/rspec/cop.rb +66 -0
- data/lib/rubocop/cop/rspec/describe_class.rb +6 -1
- data/lib/rubocop/cop/rspec/describe_method.rb +1 -2
- data/lib/rubocop/cop/rspec/described_class.rb +3 -6
- data/lib/rubocop/cop/rspec/empty_example_group.rb +1 -11
- data/lib/rubocop/cop/rspec/example_length.rb +1 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +0 -2
- data/lib/rubocop/cop/rspec/expect_actual.rb +0 -2
- data/lib/rubocop/cop/rspec/file_path.rb +1 -1
- data/lib/rubocop/cop/rspec/focus.rb +4 -9
- data/lib/rubocop/cop/rspec/hook_argument.rb +3 -5
- data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_variable.rb +1 -5
- data/lib/rubocop/cop/rspec/leading_subject.rb +0 -2
- data/lib/rubocop/cop/rspec/let_setup.rb +1 -4
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -8
- data/lib/rubocop/cop/rspec/message_expectation.rb +1 -1
- data/lib/rubocop/cop/rspec/message_spies.rb +79 -0
- data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -6
- data/lib/rubocop/cop/rspec/named_subject.rb +0 -2
- data/lib/rubocop/cop/rspec/nested_groups.rb +2 -6
- data/lib/rubocop/cop/rspec/not_to_not.rb +1 -2
- data/lib/rubocop/cop/rspec/repeated_description.rb +59 -0
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +48 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +1 -4
- data/lib/rubocop/cop/rspec/verified_doubles.rb +0 -2
- data/lib/rubocop/rspec.rb +1 -1
- data/lib/rubocop/rspec/description_extractor.rb +55 -18
- data/lib/rubocop/rspec/example.rb +56 -0
- data/lib/rubocop/rspec/example_group.rb +57 -0
- data/lib/rubocop/rspec/language.rb +28 -1
- data/lib/rubocop/rspec/language/node_pattern.rb +1 -3
- data/lib/rubocop/rspec/top_level_describe.rb +6 -10
- data/lib/rubocop/rspec/version.rb +1 -1
- data/spec/project/default_config_spec.rb +8 -5
- data/spec/project/project_requires_spec.rb +1 -1
- data/spec/rubocop/{rspec/spec_only_spec.rb → cop/rspec/cop_spec.rb} +2 -4
- data/spec/rubocop/cop/rspec/describe_class_spec.rb +36 -0
- data/spec/rubocop/cop/rspec/described_class_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/example_length_spec.rb +48 -60
- data/spec/rubocop/cop/rspec/implicit_expect_spec.rb +1 -1
- data/spec/rubocop/cop/rspec/message_spies_spec.rb +93 -0
- data/spec/rubocop/cop/rspec/repeated_description_spec.rb +76 -0
- data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +75 -0
- data/spec/rubocop/rspec/description_extractor_spec.rb +46 -24
- data/spec/rubocop/rspec/example_group_spec.rb +44 -0
- data/spec/rubocop/rspec/example_spec.rb +62 -0
- data/spec/rubocop/rspec/language/selector_set_spec.rb +22 -2
- data/spec/spec_helper.rb +2 -9
- data/spec/support/expect_violation.rb +4 -2
- metadata +21 -8
- data/lib/rubocop/rspec/spec_only.rb +0 -61
- data/spec/shared/rspec_only_cop_behavior.rb +0 -68
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that message expectations are set using spies.
|
7
|
+
#
|
8
|
+
# This cop can be configured in your configuration using the
|
9
|
+
# `EnforcedStyle` option and supports `--auto-gen-config`.
|
10
|
+
#
|
11
|
+
# @example `EnforcedStyle: have_received`
|
12
|
+
#
|
13
|
+
# # bad
|
14
|
+
# expect(foo).to receive(:bar)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# expect(foo).to have_received(:bar)
|
18
|
+
#
|
19
|
+
# @example `EnforcedStyle: receive`
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# expect(foo).to have_received(:bar)
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# expect(foo).to receive(:bar)
|
26
|
+
#
|
27
|
+
class MessageSpies < Cop
|
28
|
+
include ConfigurableEnforcedStyle
|
29
|
+
|
30
|
+
MSG_RECEIVE = 'Prefer `receive` for setting message ' \
|
31
|
+
'expectations.'.freeze
|
32
|
+
MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
|
33
|
+
'expectations. Setup `%s` as a spy using `allow` or `instance_spy`.'
|
34
|
+
.freeze
|
35
|
+
|
36
|
+
SUPPORTED_STYLES = %w(have_received receive).freeze
|
37
|
+
|
38
|
+
def_node_matcher :message_expectation, %(
|
39
|
+
(send (send nil :expect (send nil $_)) :to ...)
|
40
|
+
)
|
41
|
+
|
42
|
+
def_node_search :receive_message, %(
|
43
|
+
$(send nil {:receive :have_received} ...)
|
44
|
+
)
|
45
|
+
|
46
|
+
def on_send(node)
|
47
|
+
receive_message_matcher(node) do |receiver, message_matcher|
|
48
|
+
return correct_style_detected if preferred_style?(message_matcher)
|
49
|
+
|
50
|
+
add_offense(message_matcher, :selector, error_message(receiver)) do
|
51
|
+
opposite_style_detected
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def receive_message_matcher(node)
|
59
|
+
return unless (receiver = message_expectation(node))
|
60
|
+
|
61
|
+
receive_message(node) { |match| yield(receiver, match) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def preferred_style?(expectation)
|
65
|
+
expectation.method_name.equal?(style)
|
66
|
+
end
|
67
|
+
|
68
|
+
def error_message(receiver)
|
69
|
+
case style
|
70
|
+
when :receive
|
71
|
+
MSG_RECEIVE
|
72
|
+
when :have_received
|
73
|
+
MSG_HAVE_RECEIVED % receiver
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -23,8 +23,7 @@ module RuboCop
|
|
23
23
|
# end
|
24
24
|
# end
|
25
25
|
class MultipleDescribes < Cop
|
26
|
-
include RuboCop::RSpec::
|
27
|
-
RuboCop::RSpec::TopLevelDescribe
|
26
|
+
include RuboCop::RSpec::TopLevelDescribe
|
28
27
|
|
29
28
|
MSG = 'Do not use multiple top level describes - ' \
|
30
29
|
'try to nest them.'.freeze
|
@@ -46,15 +46,11 @@ module RuboCop
|
|
46
46
|
# end
|
47
47
|
#
|
48
48
|
class MultipleExpectations < Cop
|
49
|
-
include
|
50
|
-
RuboCop::RSpec::Language,
|
51
|
-
ConfigurableMax
|
49
|
+
include ConfigurableMax
|
52
50
|
|
53
51
|
MSG = 'Too many expectations.'.freeze
|
54
52
|
|
55
|
-
def_node_matcher :example?,
|
56
|
-
(block (send _ {#{Examples::ALL.to_node_pattern}} ...) ...)
|
57
|
-
PATTERN
|
53
|
+
def_node_matcher :example?, Examples::ALL.block_pattern
|
58
54
|
|
59
55
|
def_node_search :expect, '(send _ :expect ...)'
|
60
56
|
|
@@ -85,15 +85,11 @@ module RuboCop
|
|
85
85
|
# end
|
86
86
|
#
|
87
87
|
class NestedGroups < Cop
|
88
|
-
include RuboCop::RSpec::
|
89
|
-
RuboCop::RSpec::TopLevelDescribe,
|
90
|
-
RuboCop::RSpec::Language
|
88
|
+
include RuboCop::RSpec::TopLevelDescribe
|
91
89
|
|
92
90
|
MSG = 'Maximum example group nesting exceeded'.freeze
|
93
91
|
|
94
|
-
def_node_search :find_contexts,
|
95
|
-
(block (send nil {#{ExampleGroups::ALL.to_node_pattern}} ...) (args) ...)
|
96
|
-
PATTERN
|
92
|
+
def_node_search :find_contexts, ExampleGroups::ALL.block_pattern
|
97
93
|
|
98
94
|
def on_block(node)
|
99
95
|
describe, = described_constant(node)
|
@@ -14,8 +14,7 @@ module RuboCop
|
|
14
14
|
# expect(false).not_to be_true
|
15
15
|
# end
|
16
16
|
class NotToNot < Cop
|
17
|
-
include RuboCop::
|
18
|
-
RuboCop::Cop::ConfigurableEnforcedStyle
|
17
|
+
include RuboCop::Cop::ConfigurableEnforcedStyle
|
19
18
|
|
20
19
|
MSG = 'Prefer `%s` over `%s`'.freeze
|
21
20
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module RSpec
|
4
|
+
# Check for repeated description strings in example groups.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# # bad
|
9
|
+
# RSpec.describe User do
|
10
|
+
# it 'is valid' do
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# it 'is valid' do
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# RSpec.describe User do
|
21
|
+
# it 'is valid when first and last name are present' do
|
22
|
+
# # ...
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# it 'is valid when last name only is present' do
|
26
|
+
# # ...
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
class RepeatedDescription < Cop
|
31
|
+
MSG = "Don't repeat descriptions within an example group.".freeze
|
32
|
+
|
33
|
+
def on_block(node)
|
34
|
+
return unless example_group?(node)
|
35
|
+
|
36
|
+
repeated_descriptions(node).each do |repeated_description|
|
37
|
+
add_offense(repeated_description, :expression)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Select examples in the current scope with repeated description strings
|
44
|
+
def repeated_descriptions(node)
|
45
|
+
grouped_examples =
|
46
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
47
|
+
.examples
|
48
|
+
.group_by(&:doc_string)
|
49
|
+
|
50
|
+
grouped_examples
|
51
|
+
.select { |description, group| description && group.size > 1 }
|
52
|
+
.values
|
53
|
+
.flatten
|
54
|
+
.map(&:definition)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module RSpec
|
4
|
+
# Checks that chains of messages contain more than one element.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# # bad
|
8
|
+
# allow(foo).to receive_message_chain(:bar).and_return(42)
|
9
|
+
#
|
10
|
+
# # good
|
11
|
+
# allow(foo).to receive(:bar).and_return(42)
|
12
|
+
#
|
13
|
+
# # also good
|
14
|
+
# allow(foo).to receive(:bar, :baz)
|
15
|
+
# allow(foo).to receive("bar.baz")
|
16
|
+
#
|
17
|
+
class SingleArgumentMessageChain < Cop
|
18
|
+
MESSAGE = 'Use `%<recommended_method>s` instead of calling ' \
|
19
|
+
'`%<called_method>s` with a single argument'.freeze
|
20
|
+
|
21
|
+
def on_send(node)
|
22
|
+
_receiver, method_name, *args = *node
|
23
|
+
return unless Matchers::MESSAGE_CHAIN.include?(method_name)
|
24
|
+
return if args.size > 1
|
25
|
+
return if multi_argument_string?(args)
|
26
|
+
|
27
|
+
add_offense(node, :selector, message(method_name))
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def multi_argument_string?(args)
|
33
|
+
args.size == 1 &&
|
34
|
+
args.first.type == :str &&
|
35
|
+
args.first.children.first.include?('.')
|
36
|
+
end
|
37
|
+
|
38
|
+
def message(method)
|
39
|
+
if method == :receive_message_chain
|
40
|
+
MESSAGE % { recommended_method: :receive, called_method: method }
|
41
|
+
else
|
42
|
+
MESSAGE % { recommended_method: :stub, called_method: method }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -18,10 +18,7 @@ module RuboCop
|
|
18
18
|
# end
|
19
19
|
#
|
20
20
|
class SubjectStub < Cop
|
21
|
-
include RuboCop::RSpec::
|
22
|
-
RuboCop::RSpec::TopLevelDescribe,
|
23
|
-
RuboCop::RSpec::Language,
|
24
|
-
RuboCop::RSpec::Language::NodePattern
|
21
|
+
include RuboCop::RSpec::TopLevelDescribe
|
25
22
|
|
26
23
|
MSG = 'Do not stub your test subject.'.freeze
|
27
24
|
|
data/lib/rubocop/rspec.rb
CHANGED
@@ -2,34 +2,71 @@ module RuboCop
|
|
2
2
|
module RSpec
|
3
3
|
# Extracts cop descriptions from YARD docstrings
|
4
4
|
class DescriptionExtractor
|
5
|
-
COP_NAMESPACE = 'RuboCop::Cop::RSpec'.freeze
|
6
|
-
COP_FORMAT = 'RSpec/%s'.freeze
|
7
|
-
|
8
5
|
def initialize(yardocs)
|
9
|
-
@
|
6
|
+
@code_objects = yardocs.map(&CodeObject.public_method(:new))
|
10
7
|
end
|
11
8
|
|
12
9
|
def to_h
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
10
|
+
code_objects
|
11
|
+
.select(&:rspec_cop?)
|
12
|
+
.map(&:configuration)
|
13
|
+
.reduce(:merge)
|
18
14
|
end
|
19
15
|
|
20
16
|
private
|
21
17
|
|
22
|
-
|
23
|
-
yardocs
|
24
|
-
.select(&method(:cop?))
|
25
|
-
.map { |doc| [doc.name, doc.docstring] }
|
26
|
-
end
|
18
|
+
attr_reader :code_objects
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
# Decorator of a YARD code object for working with documented rspec cops
|
21
|
+
class CodeObject
|
22
|
+
COP_NAMESPACE = 'RuboCop::Cop::RSpec'.freeze
|
23
|
+
|
24
|
+
def initialize(yardoc)
|
25
|
+
@yardoc = yardoc
|
26
|
+
end
|
27
|
+
|
28
|
+
# Test if the YARD code object documents a concrete rspec cop class
|
29
|
+
#
|
30
|
+
# @return [Boolean]
|
31
|
+
def rspec_cop?
|
32
|
+
class_documentation? && rspec_cop_namespace? && !abstract?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Configuration for the documented cop that would live in default.yml
|
36
|
+
#
|
37
|
+
# @return [Hash]
|
38
|
+
def configuration
|
39
|
+
{ cop_name => { 'Description' => description } }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def cop_name
|
45
|
+
Object.const_get(documented_constant).cop_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def description
|
49
|
+
yardoc.docstring.split("\n\n").first.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def class_documentation?
|
53
|
+
yardoc.type.equal?(:class)
|
54
|
+
end
|
55
|
+
|
56
|
+
def rspec_cop_namespace?
|
57
|
+
documented_constant.start_with?(COP_NAMESPACE)
|
58
|
+
end
|
59
|
+
|
60
|
+
def documented_constant
|
61
|
+
yardoc.to_s
|
62
|
+
end
|
31
63
|
|
32
|
-
|
64
|
+
def abstract?
|
65
|
+
yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_reader :yardoc
|
69
|
+
end
|
33
70
|
end
|
34
71
|
end
|
35
72
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module RSpec
|
5
|
+
# Wrapper for RSpec examples
|
6
|
+
class Example
|
7
|
+
extend RuboCop::NodePattern::Macros
|
8
|
+
|
9
|
+
def_node_matcher :extract_doc_string, '(send _ _ $str ...)'
|
10
|
+
def_node_matcher :extract_metadata, '(send _ _ _ $...)'
|
11
|
+
def_node_matcher :extract_implementation, '(block send args $_)'
|
12
|
+
|
13
|
+
def initialize(node)
|
14
|
+
@node = node
|
15
|
+
end
|
16
|
+
|
17
|
+
def doc_string
|
18
|
+
extract_doc_string(definition)
|
19
|
+
end
|
20
|
+
|
21
|
+
def metadata
|
22
|
+
extract_metadata(definition)
|
23
|
+
end
|
24
|
+
|
25
|
+
def implementation
|
26
|
+
extract_implementation(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def eql?(other)
|
30
|
+
node.eql?(other.node)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias == eql?
|
34
|
+
|
35
|
+
def hash
|
36
|
+
[self.class, node].hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_node
|
40
|
+
node
|
41
|
+
end
|
42
|
+
|
43
|
+
def definition
|
44
|
+
if node.send_type?
|
45
|
+
node
|
46
|
+
else
|
47
|
+
node.children.first
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
attr_reader :node
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|