rubocop-rspec 1.9.1 → 1.10.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 +11 -0
- data/Gemfile +2 -1
- data/config/default.yml +13 -1
- data/lib/rubocop-rspec.rb +5 -0
- data/lib/rubocop/cop/rspec/any_instance.rb +1 -1
- data/lib/rubocop/cop/rspec/cop.rb +1 -2
- data/lib/rubocop/cop/rspec/described_class.rb +1 -1
- data/lib/rubocop/cop/rspec/expect_output.rb +52 -0
- data/lib/rubocop/cop/rspec/implicit_expect.rb +3 -2
- data/lib/rubocop/cop/rspec/message_chain.rb +1 -1
- data/lib/rubocop/cop/rspec/message_spies.rb +2 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -4
- data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/nested_groups.rb +16 -1
- data/lib/rubocop/cop/rspec/repeated_example.rb +41 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +49 -0
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +17 -5
- data/lib/rubocop/rspec.rb +1 -1
- data/lib/rubocop/rspec/concept.rb +33 -0
- data/lib/rubocop/rspec/example.rb +1 -25
- data/lib/rubocop/rspec/example_group.rb +28 -12
- data/lib/rubocop/rspec/hook.rb +49 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +1 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/spec/rubocop/cop/rspec/cop_spec.rb +4 -1
- data/spec/rubocop/cop/rspec/expect_output_spec.rb +62 -0
- data/spec/rubocop/cop/rspec/message_spies_spec.rb +74 -2
- data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +2 -2
- data/spec/rubocop/cop/rspec/nested_groups_spec.rb +14 -2
- data/spec/rubocop/cop/rspec/repeated_example_spec.rb +65 -0
- data/spec/rubocop/cop/rspec/scattered_setup_spec.rb +96 -0
- data/spec/rubocop/cop/rspec/single_argument_message_chain_spec.rb +27 -3
- data/spec/rubocop/rspec/description_extractor_spec.rb +1 -1
- data/spec/rubocop/rspec/example_group_spec.rb +1 -1
- data/spec/rubocop/rspec/example_spec.rb +2 -2
- data/spec/rubocop/rspec/hook_spec.rb +53 -0
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afa52f47ad386fe20a2a338ff9da7c6a032c385b
|
4
|
+
data.tar.gz: 07143368c4d5e201d12bfba8379e5c2c79bcba99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89dfa9c530533694d03d670112ae870f9ba626875bcff32bb9ab4c708f393e99e1e4eb5562adb5ad2e8231c5366594fabbd431a8ec758ec856491fa747f84b66
|
7
|
+
data.tar.gz: 9517b60ae000d18800f11bb8e579d5889883eff511975ce4ff77a56e72cc0450e9f998b93694c3c90d9e47c9546aebb0fe8296fdbcfa4048e16cfddfc7fda5b1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
## Master (unreleased)
|
4
4
|
|
5
|
+
## 1.10.0 (2017-01-15)
|
6
|
+
|
7
|
+
* Fix false negative for `RSpec/MessageSpies` cop. ([@onk][])
|
8
|
+
* Fix internal dependencies on RuboCop to be compatible with 0.47 release. ([@backus][])
|
9
|
+
* Add autocorrect support for `SingleArgumentMessageChain` cop. ([@bquorning][])
|
10
|
+
* Rename `NestedGroups`' configuration key from `MaxNesting` to `Max` in order to be consistent with other cop configuration. ([@backus][])
|
11
|
+
* Add `RepeatedExample` cop for detecting repeated examples within example groups. ([@backus][])
|
12
|
+
* Add `ScatteredSetup` cop for enforcing that only one `before`, `around`, and `after` hook are used per example group scope. ([@backus][])
|
13
|
+
* Add `ExpectOutput` cop for recommending `expect { ... }.to output(...).to_stdout`. ([@backus][])
|
14
|
+
|
5
15
|
## 1.9.1 (2017-01-02)
|
6
16
|
|
7
17
|
* Fix unintentional regression change in `NestedGroups` reported in #270. ([@backus][])
|
@@ -161,3 +171,4 @@
|
|
161
171
|
[@baberthal]: https://github.com/baberthal
|
162
172
|
[@jeffreyc]: https://github.com/jeffreyc
|
163
173
|
[@clupprich]: https://github.com/clupprich
|
174
|
+
[@onk]: https://github.com/onk
|
data/Gemfile
CHANGED
@@ -4,11 +4,12 @@ gemspec
|
|
4
4
|
|
5
5
|
group :test do
|
6
6
|
gem 'codeclimate-test-reporter', '~> 1.0.0'
|
7
|
+
gem 'rubocop', '~> 0.47'
|
7
8
|
gem 'simplecov', '~> 0.12.0', require: false
|
8
9
|
end
|
9
10
|
|
10
11
|
local_gemfile = 'Gemfile.local'
|
11
12
|
|
12
13
|
if File.exist?(local_gemfile)
|
13
|
-
eval(File.read(local_gemfile)) # rubocop:disable
|
14
|
+
eval(File.read(local_gemfile)) # rubocop:disable Security/Eval
|
14
15
|
end
|
data/config/default.yml
CHANGED
@@ -49,6 +49,10 @@ RSpec/ExpectActual:
|
|
49
49
|
Description: Checks for `expect(...)` calls containing literal values.
|
50
50
|
Enabled: true
|
51
51
|
|
52
|
+
RSpec/ExpectOutput:
|
53
|
+
Description: Checks for opportunities to use `expect { ... }.to output`.
|
54
|
+
Enabled: true
|
55
|
+
|
52
56
|
RSpec/FilePath:
|
53
57
|
Description: Checks that spec file paths are consistent with the test subject.
|
54
58
|
Enabled: true
|
@@ -127,7 +131,7 @@ RSpec/NamedSubject:
|
|
127
131
|
RSpec/NestedGroups:
|
128
132
|
Description: Checks for nested example groups.
|
129
133
|
Enabled: true
|
130
|
-
|
134
|
+
Max: 3
|
131
135
|
|
132
136
|
RSpec/NotToNot:
|
133
137
|
Description: Checks for consistent method usage for negating expectations.
|
@@ -141,10 +145,18 @@ RSpec/RepeatedDescription:
|
|
141
145
|
Enabled: true
|
142
146
|
Description: Check for repeated description strings in example groups.
|
143
147
|
|
148
|
+
RSpec/RepeatedExample:
|
149
|
+
Enabled: true
|
150
|
+
Description: Check for repeated examples within example groups.
|
151
|
+
|
144
152
|
RSpec/SingleArgumentMessageChain:
|
145
153
|
Description: Checks that chains of messages contain more than one element.
|
146
154
|
Enabled: true
|
147
155
|
|
156
|
+
RSpec/ScatteredSetup:
|
157
|
+
Description: Checks for setup scattered across multiple hooks in an example group.
|
158
|
+
Enabled: true
|
159
|
+
|
148
160
|
RSpec/SubjectStub:
|
149
161
|
Description: Checks for stubbed test subjects.
|
150
162
|
Enabled: true
|
data/lib/rubocop-rspec.rb
CHANGED
@@ -11,8 +11,10 @@ require 'rubocop/rspec/wording'
|
|
11
11
|
require 'rubocop/rspec/util'
|
12
12
|
require 'rubocop/rspec/language'
|
13
13
|
require 'rubocop/rspec/language/node_pattern'
|
14
|
+
require 'rubocop/rspec/concept'
|
14
15
|
require 'rubocop/rspec/example_group'
|
15
16
|
require 'rubocop/rspec/example'
|
17
|
+
require 'rubocop/rspec/hook'
|
16
18
|
require 'rubocop/cop/rspec/cop'
|
17
19
|
|
18
20
|
RuboCop::RSpec::Inject.defaults!
|
@@ -27,6 +29,7 @@ require 'rubocop/cop/rspec/empty_example_group'
|
|
27
29
|
require 'rubocop/cop/rspec/example_length'
|
28
30
|
require 'rubocop/cop/rspec/example_wording'
|
29
31
|
require 'rubocop/cop/rspec/expect_actual'
|
32
|
+
require 'rubocop/cop/rspec/expect_output'
|
30
33
|
require 'rubocop/cop/rspec/file_path'
|
31
34
|
require 'rubocop/cop/rspec/focus'
|
32
35
|
require 'rubocop/cop/rspec/hook_argument'
|
@@ -43,6 +46,8 @@ require 'rubocop/cop/rspec/named_subject'
|
|
43
46
|
require 'rubocop/cop/rspec/nested_groups'
|
44
47
|
require 'rubocop/cop/rspec/not_to_not'
|
45
48
|
require 'rubocop/cop/rspec/repeated_description'
|
49
|
+
require 'rubocop/cop/rspec/repeated_example'
|
50
|
+
require 'rubocop/cop/rspec/scattered_setup'
|
46
51
|
require 'rubocop/cop/rspec/single_argument_message_chain'
|
47
52
|
require 'rubocop/cop/rspec/subject_stub'
|
48
53
|
require 'rubocop/cop/rspec/verified_doubles'
|
@@ -33,7 +33,7 @@ module RuboCop
|
|
33
33
|
_receiver, method_name, *_args = *node
|
34
34
|
return unless ANY_INSTANCE_METHODS.include?(method_name)
|
35
35
|
|
36
|
-
add_offense(node, :expression, MESSAGE
|
36
|
+
add_offense(node, :expression, format(MESSAGE, method: method_name))
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -7,8 +7,7 @@ module RuboCop
|
|
7
7
|
class WorkaroundCop
|
8
8
|
# Overwrite the cop inherited method to be a noop. Our RSpec::Cop
|
9
9
|
# class will invoke the inherited hook instead
|
10
|
-
def self.inherited(*)
|
11
|
-
end
|
10
|
+
def self.inherited(*); end
|
12
11
|
|
13
12
|
# Special case `Module#<` so that the rspec support rubocop exports
|
14
13
|
# is compatible with our subclass
|
@@ -54,7 +54,7 @@ module RuboCop
|
|
54
54
|
def find_constant_usage(node, described_class, &block)
|
55
55
|
yield(node) if node.eql?(described_class)
|
56
56
|
|
57
|
-
return unless node.
|
57
|
+
return unless node.is_a?(Parser::AST::Node)
|
58
58
|
return if scope_change?(node) || node.const_type?
|
59
59
|
|
60
60
|
node.children.each do |child|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for opportunities to use `expect { ... }.to output`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# $stdout = StringIO.new
|
11
|
+
# my_app.print_report
|
12
|
+
# $stdout = STDOUT
|
13
|
+
# expect($stdout.string).to eq('Hello World')
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# expect { my_app.print_report }.to output('Hello World').to_stdout
|
17
|
+
class ExpectOutput < Cop
|
18
|
+
MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
|
19
|
+
'instead of mutating $%<name>s'.freeze
|
20
|
+
|
21
|
+
def_node_matcher :hook?, Hooks::ALL.block_pattern
|
22
|
+
|
23
|
+
def on_gvasgn(node)
|
24
|
+
return unless inside_example_scope?(node)
|
25
|
+
|
26
|
+
variable_name, _rhs = *node
|
27
|
+
name = variable_name[1..-1]
|
28
|
+
return unless name.eql?('stdout') || name.eql?('stderr')
|
29
|
+
|
30
|
+
add_offense(node, :name, format(MSG, name: name))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Detect if we are inside the scope of a single example
|
36
|
+
#
|
37
|
+
# We want to encourage using `expect { ... }.to output` so
|
38
|
+
# we only care about situations where you would replace with
|
39
|
+
# an expectation. Therefore, assignments to stderr or stdout
|
40
|
+
# within a `before(:all)` or otherwise outside of an example
|
41
|
+
# don't matter.
|
42
|
+
def inside_example_scope?(node)
|
43
|
+
return false if node.nil? || example_group?(node)
|
44
|
+
return true if example?(node)
|
45
|
+
return RuboCop::RSpec::Hook.new(node).example? if hook?(node)
|
46
|
+
|
47
|
+
inside_example_scope?(node.parent)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -18,7 +18,7 @@ module RuboCop
|
|
18
18
|
_receiver, method_name, *_args = *node
|
19
19
|
return unless Matchers::MESSAGE_CHAIN.include?(method_name)
|
20
20
|
|
21
|
-
add_offense(node, :selector, MESSAGE
|
21
|
+
add_offense(node, :selector, format(MESSAGE, method: method_name))
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -36,7 +36,7 @@ module RuboCop
|
|
36
36
|
SUPPORTED_STYLES = %w(have_received receive).freeze
|
37
37
|
|
38
38
|
def_node_matcher :message_expectation, %(
|
39
|
-
(send (send nil :expect
|
39
|
+
(send (send nil :expect $_) {:to :to_not :not_to} ...)
|
40
40
|
)
|
41
41
|
|
42
42
|
def_node_search :receive_message, %(
|
@@ -70,7 +70,7 @@ module RuboCop
|
|
70
70
|
when :receive
|
71
71
|
MSG_RECEIVE
|
72
72
|
when :have_received
|
73
|
-
MSG_HAVE_RECEIVED % receiver
|
73
|
+
MSG_HAVE_RECEIVED % receiver.source
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -48,9 +48,7 @@ module RuboCop
|
|
48
48
|
class MultipleExpectations < Cop
|
49
49
|
include ConfigurableMax
|
50
50
|
|
51
|
-
MSG = '
|
52
|
-
|
53
|
-
def_node_matcher :example?, Examples::ALL.block_pattern
|
51
|
+
MSG = 'Example has too many expectations [%{total}/%{max}]'.freeze
|
54
52
|
|
55
53
|
def_node_search :expect, '(send _ :expect ...)'
|
56
54
|
|
@@ -72,7 +70,7 @@ module RuboCop
|
|
72
70
|
add_offense(
|
73
71
|
method,
|
74
72
|
:expression,
|
75
|
-
MSG
|
73
|
+
format(MSG, total: expectation_count, max: max_expectations)
|
76
74
|
)
|
77
75
|
end
|
78
76
|
|
@@ -89,6 +89,12 @@ module RuboCop
|
|
89
89
|
|
90
90
|
MSG = 'Maximum example group nesting exceeded'.freeze
|
91
91
|
|
92
|
+
DEPRECATED_MAX_KEY = 'MaxNesting'.freeze
|
93
|
+
|
94
|
+
DEPRECATION_WARNING =
|
95
|
+
"Configuration key `#{DEPRECATED_MAX_KEY}` for #{cop_name} is " \
|
96
|
+
'deprecated in favor of `Max`. Please use that instead.'.freeze
|
97
|
+
|
92
98
|
def_node_search :find_contexts, ExampleGroups::ALL.block_pattern
|
93
99
|
|
94
100
|
def on_top_level_describe(node, _)
|
@@ -110,7 +116,16 @@ module RuboCop
|
|
110
116
|
end
|
111
117
|
|
112
118
|
def max_nesting
|
113
|
-
Integer(
|
119
|
+
@max_nesting ||= Integer(max_nesting_config)
|
120
|
+
end
|
121
|
+
|
122
|
+
def max_nesting_config
|
123
|
+
if cop_config.key?(DEPRECATED_MAX_KEY)
|
124
|
+
warn DEPRECATION_WARNING
|
125
|
+
cop_config.fetch(DEPRECATED_MAX_KEY)
|
126
|
+
else
|
127
|
+
cop_config.fetch('Max', 3)
|
128
|
+
end
|
114
129
|
end
|
115
130
|
end
|
116
131
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module RSpec
|
4
|
+
# Check for repeated examples within example groups.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# it 'is valid' do
|
9
|
+
# expect(user).to be_valid
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# it 'validates the user' do
|
13
|
+
# expect(user).to be_valid
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
class RepeatedExample < Cop
|
17
|
+
MSG = "Don't repeat examples within an example group.".freeze
|
18
|
+
|
19
|
+
def on_block(node)
|
20
|
+
return unless example_group?(node)
|
21
|
+
|
22
|
+
repeated_examples(node).each do |repeated_example|
|
23
|
+
add_offense(repeated_example, :expression)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def repeated_examples(node)
|
30
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
31
|
+
.examples
|
32
|
+
.group_by { |example| [example.metadata, example.implementation] }
|
33
|
+
.values
|
34
|
+
.reject(&:one?)
|
35
|
+
.flatten
|
36
|
+
.map(&:to_node)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for setup scattered across multiple hooks in an example group.
|
7
|
+
#
|
8
|
+
# Unify `before`, `after`, and `around` hooks when possible.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# describe Foo do
|
13
|
+
# before { setup1 }
|
14
|
+
# before { setup2 }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# describe Foo do
|
19
|
+
# before do
|
20
|
+
# setup1
|
21
|
+
# setup2
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class ScatteredSetup < Cop
|
26
|
+
MSG = 'Do not define multiple hooks in the same example group.'.freeze
|
27
|
+
|
28
|
+
def on_block(node)
|
29
|
+
return unless example_group?(node)
|
30
|
+
|
31
|
+
analyzable_hooks(node).each do |repeated_hook|
|
32
|
+
add_offense(repeated_hook, :expression)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def analyzable_hooks(node)
|
37
|
+
RuboCop::RSpec::ExampleGroup.new(node)
|
38
|
+
.hooks
|
39
|
+
.select { |hook| hook.knowable_scope? && hook.valid_scope? }
|
40
|
+
.group_by { |hook| [hook.name, hook.scope] }
|
41
|
+
.values
|
42
|
+
.reject(&:one?)
|
43
|
+
.flatten
|
44
|
+
.map(&:to_node)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -27,6 +27,16 @@ module RuboCop
|
|
27
27
|
add_offense(node, :selector, message(method_name))
|
28
28
|
end
|
29
29
|
|
30
|
+
def autocorrect(node)
|
31
|
+
_receiver, method_name, *_args = *node
|
32
|
+
lambda do |corrector|
|
33
|
+
corrector.replace(
|
34
|
+
node.loc.selector,
|
35
|
+
method_name.equal?(:receive_message_chain) ? 'receive' : 'stub'
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
30
40
|
private
|
31
41
|
|
32
42
|
def multi_argument_string?(args)
|
@@ -36,11 +46,13 @@ module RuboCop
|
|
36
46
|
end
|
37
47
|
|
38
48
|
def message(method)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
MESSAGE
|
43
|
-
|
49
|
+
recommendation = method == :receive_message_chain ? :receive : :stub
|
50
|
+
|
51
|
+
format(
|
52
|
+
MESSAGE,
|
53
|
+
recommended_method: recommendation,
|
54
|
+
called_method: method
|
55
|
+
)
|
44
56
|
end
|
45
57
|
end
|
46
58
|
end
|
data/lib/rubocop/rspec.rb
CHANGED
@@ -3,7 +3,7 @@ module RuboCop
|
|
3
3
|
module RSpec
|
4
4
|
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
5
5
|
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
6
|
-
CONFIG = YAML.
|
6
|
+
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
7
7
|
|
8
8
|
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
9
9
|
end
|