rubocop-rspec 1.9.1 → 1.10.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 +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
|