rubocop-rspec 1.37.1 → 1.38.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b83eaf98373fd696c64571d551394a287cecc7577b9b69ecf6b425a392b41e3
4
- data.tar.gz: 390966b2a4e34f20dadd3b6f162ee812a2386602b32d98de7d5fa62f63af3e3a
3
+ metadata.gz: '04629f5b3e67ce4028685a73823dbc4d4ec5b0507162c16c7d1ee2d524871277'
4
+ data.tar.gz: 0cd6e7fa477d73f7a0621b9cb80b440f32f96baf109a1eef87166e69efd34df9
5
5
  SHA512:
6
- metadata.gz: 6e4b4c02a0fb2987cb3fab05a26f557dc8f202262dc77202a1ba82916ac27c43d88d585218f6656c86dcfb1cd2e8c467e3b8ea2a50d790957401c2499d57a7d5
7
- data.tar.gz: c496849147815725376d0bfc43c3c6da0cd544144434980460366ee013136df4203992b6fe90a31468d4529af145ee3608de1d80827bb406c69bc371cfec42af
6
+ metadata.gz: a0c46cbd0c72758c7e15fde02483a00c8e67c85b2155cddb6b1a0ae77338e0ff12f26e5ba4bf495e828318254f970d374ab3504ab21adda414a0d3663d4f257a
7
+ data.tar.gz: 62e31b5d76328e94595256ddd1215d2ae526f5fe4df09ad704a46a3e2865a7eebfc60f64e1b868967315a8b4efd329b4f55e587b646353974c290cd3bd33c474
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 1.38.0 (2020-02-11)
6
+
7
+ * Fix `RSpec/InstanceVariable` detection inside custom matchers. ([@pirj][])
8
+ * Fix `RSpec/ScatteredSetup` to distinguish hooks with different metadata. ([@pirj][])
9
+ * Add autocorrect support for `RSpec/ExpectActual` cop. ([@dduugg][], [@pirj][])
10
+ * Add `RSpec/RepeatedExampleGroupBody` cop. ([@lazycoder9][])
11
+ * Add `RSpec/RepeatedExampleGroupDescription` cop. ([@lazycoder9][])
12
+ * Add block name and other lines to `RSpec/ScatteredSetup` message. ([@elebow][])
13
+ * Fix `RSpec/RepeatedDescription` to take into account example metadata. ([@lazycoder9][])
14
+
5
15
  ## 1.37.1 (2019-12-16)
6
16
 
7
17
  * Improve message and description of `FactoryBot/FactoryClassName`. ([@ybiquitous][])
@@ -472,3 +482,6 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
472
482
  [@mkrawc]: https://github.com/mkrawc
473
483
  [@jfragoulis]: https://github.com/jfragoulis
474
484
  [@ybiquitous]: https://github.com/ybiquitous
485
+ [@dduugg]: https://github.com/dduugg
486
+ [@lazycoder9]: https://github.com/lazycoder9
487
+ [@elebow]: https://github.com/elebow
data/README.md CHANGED
@@ -20,7 +20,7 @@ gem install rubocop-rspec
20
20
  or if you use bundler put this in your `Gemfile`
21
21
 
22
22
  ```
23
- gem 'rubocop-rspec'
23
+ gem 'rubocop-rspec', require: false
24
24
  ```
25
25
 
26
26
  ## Usage
data/config/default.yml CHANGED
@@ -394,6 +394,16 @@ RSpec/RepeatedExample:
394
394
  Description: Check for repeated examples within example groups.
395
395
  StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExample
396
396
 
397
+ RSpec/RepeatedExampleGroupBody:
398
+ Enabled: true
399
+ Description: Check for repeated describe and context block body.
400
+ StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupBody
401
+
402
+ RSpec/RepeatedExampleGroupDescription:
403
+ Enabled: true
404
+ Description: Check for repeated example group descriptions.
405
+ StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription
406
+
397
407
  RSpec/ReturnFromStub:
398
408
  Enabled: true
399
409
  Description: Checks for consistent style of stub's return setting.
@@ -5,7 +5,11 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks that `context` docstring starts with an allowed prefix.
7
7
  #
8
- # @see https://github.com/reachlocal/rspec-style-guide#context-descriptions
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
@@ -41,11 +41,31 @@ module RuboCop
41
41
  regexp
42
42
  ].freeze
43
43
 
44
- def_node_matcher :expect_literal, '(send _ :expect $#literal?)'
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
@@ -58,6 +58,13 @@ module RuboCop
58
58
  (block (send (const nil? :Class) :new ...) ...)
59
59
  PATTERN
60
60
 
61
+ def_node_matcher :custom_matcher?, <<-PATTERN
62
+ (block {
63
+ (send nil? :matcher sym)
64
+ (send (const (const nil? :RSpec) :Matchers) :define sym)
65
+ } ...)
66
+ PATTERN
67
+
61
68
  def_node_search :ivar_usage, '$(ivar $_)'
62
69
 
63
70
  def_node_search :ivar_assigned?, '(ivasgn % ...)'
@@ -66,8 +73,8 @@ module RuboCop
66
73
  return unless spec_group?(node)
67
74
 
68
75
  ivar_usage(node) do |ivar, name|
69
- return if inside_dynamic_class?(ivar)
70
- return if assignment_only? && !ivar_assigned?(node, name)
76
+ next if valid_usage?(ivar)
77
+ next if assignment_only? && !ivar_assigned?(node, name)
71
78
 
72
79
  add_offense(ivar)
73
80
  end
@@ -75,8 +82,10 @@ module RuboCop
75
82
 
76
83
  private
77
84
 
78
- def inside_dynamic_class?(node)
79
- node.each_ancestor(:block).any? { |block| dynamic_class?(block) }
85
+ def valid_usage?(node)
86
+ node.each_ancestor(:block).any? do |block|
87
+ dynamic_class?(block) || custom_matcher?(block)
88
+ end
80
89
  end
81
90
 
82
91
  def assignment_only?
@@ -29,6 +29,17 @@ module RuboCop
29
29
  # end
30
30
  # end
31
31
  #
32
+ # # good
33
+ # RSpec.describe User do
34
+ # it 'is valid' do
35
+ # # ...
36
+ # end
37
+ #
38
+ # it 'is valid', :flag do
39
+ # # ...
40
+ # end
41
+ # end
42
+ #
32
43
  class RepeatedDescription < Cop
33
44
  MSG = "Don't repeat descriptions within an example group."
34
45
 
@@ -47,14 +58,18 @@ module RuboCop
47
58
  grouped_examples =
48
59
  RuboCop::RSpec::ExampleGroup.new(node)
49
60
  .examples
50
- .group_by(&:doc_string)
61
+ .group_by { |example| example_signature(example) }
51
62
 
52
63
  grouped_examples
53
- .select { |description, group| description && group.size > 1 }
64
+ .select { |signatures, group| signatures.any? && group.size > 1 }
54
65
  .values
55
66
  .flatten
56
67
  .map(&:definition)
57
68
  end
69
+
70
+ def example_signature(example)
71
+ [example.metadata, example.doc_string]
72
+ end
58
73
  end
59
74
  end
60
75
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated describe and context block body.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # describe 'cool feature x' do
12
+ # it { cool_predicate }
13
+ # end
14
+ #
15
+ # describe 'cool feature y' do
16
+ # it { cool_predicate }
17
+ # end
18
+ #
19
+ # # good
20
+ # describe 'cool feature' do
21
+ # it { cool_predicate }
22
+ # end
23
+ #
24
+ # describe 'another cool feature' do
25
+ # it { another_predicate }
26
+ # end
27
+ #
28
+ # # good
29
+ # context 'when case x', :tag do
30
+ # it { cool_predicate }
31
+ # end
32
+ #
33
+ # context 'when case y' do
34
+ # it { cool_predicate }
35
+ # end
36
+ #
37
+ class RepeatedExampleGroupBody < Cop
38
+ MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
39
+
40
+ def_node_matcher :several_example_groups?, <<-PATTERN
41
+ (begin <#example_group_with_body? #example_group_with_body? ...>)
42
+ PATTERN
43
+
44
+ def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
45
+ def_node_matcher :body, '(block _ args $...)'
46
+
47
+ def_node_matcher :skip_or_pending?, <<-PATTERN
48
+ (block <(send nil? {:skip :pending}) ...>)
49
+ PATTERN
50
+
51
+ def on_begin(node)
52
+ return unless several_example_groups?(node)
53
+
54
+ repeated_group_bodies(node).each do |group, repeats|
55
+ add_offense(group, message: message(group, repeats))
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def repeated_group_bodies(node)
62
+ node
63
+ .children
64
+ .select { |child| example_group_with_body?(child) }
65
+ .reject { |child| skip_or_pending?(child) }
66
+ .group_by { |group| signature_keys(group) }
67
+ .values
68
+ .reject(&:one?)
69
+ .flat_map { |groups| add_repeated_lines(groups) }
70
+ end
71
+
72
+ def add_repeated_lines(groups)
73
+ repeated_lines = groups.map(&:first_line)
74
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
75
+ end
76
+
77
+ def signature_keys(group)
78
+ [metadata(group), body(group)]
79
+ end
80
+
81
+ def message(group, repeats)
82
+ format(MSG, group: group.method_name, loc: repeats)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated example group descriptions.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # describe 'cool feature' do
12
+ # # example group
13
+ # end
14
+ #
15
+ # describe 'cool feature' do
16
+ # # example group
17
+ # end
18
+ #
19
+ # # bad
20
+ # context 'when case x' do
21
+ # # example group
22
+ # end
23
+ #
24
+ # describe 'when case x' do
25
+ # # example group
26
+ # end
27
+ #
28
+ # # good
29
+ # describe 'cool feature' do
30
+ # # example group
31
+ # end
32
+ #
33
+ # describe 'another cool feature' do
34
+ # # example group
35
+ # end
36
+ #
37
+ # # good
38
+ # context 'when case x' do
39
+ # # example group
40
+ # end
41
+ #
42
+ # context 'when another case' do
43
+ # # example group
44
+ # end
45
+ #
46
+ class RepeatedExampleGroupDescription < Cop
47
+ MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
48
+
49
+ def_node_matcher :several_example_groups?, <<-PATTERN
50
+ (begin <#example_group? #example_group? ...>)
51
+ PATTERN
52
+
53
+ def_node_matcher :doc_string_and_metadata, <<-PATTERN
54
+ (block (send _ _ $_ $...) ...)
55
+ PATTERN
56
+
57
+ def_node_matcher :skip_or_pending?, <<-PATTERN
58
+ (block <(send nil? {:skip :pending}) ...>)
59
+ PATTERN
60
+
61
+ def_node_matcher :empty_description?, '(block (send _ _) ...)'
62
+
63
+ def on_begin(node)
64
+ return unless several_example_groups?(node)
65
+
66
+ repeated_group_descriptions(node).each do |group, repeats|
67
+ add_offense(group, message: message(group, repeats))
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def repeated_group_descriptions(node)
74
+ node
75
+ .children
76
+ .select { |child| example_group?(child) }
77
+ .reject { |child| skip_or_pending?(child) }
78
+ .reject { |child| empty_description?(child) }
79
+ .group_by { |group| doc_string_and_metadata(group) }
80
+ .values
81
+ .reject(&:one?)
82
+ .flat_map { |groups| add_repeated_lines(groups) }
83
+ end
84
+
85
+ def add_repeated_lines(groups)
86
+ repeated_lines = groups.map(&:first_line)
87
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
88
+ end
89
+
90
+ def message(group, repeats)
91
+ format(MSG, group: group.method_name, loc: repeats)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -23,25 +23,43 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class ScatteredSetup < Cop
26
- MSG = 'Do not define multiple hooks in the same example group.'
26
+ MSG = 'Do not define multiple `%<hook_name>s` hooks in the same '\
27
+ 'example group (also defined on %<lines>s).'
27
28
 
28
29
  def on_block(node)
29
30
  return unless example_group?(node)
30
31
 
31
- analyzable_hooks(node).each do |repeated_hook|
32
- add_offense(repeated_hook)
32
+ repeated_hooks(node).each do |occurrences|
33
+ lines = occurrences.map(&:first_line)
34
+
35
+ occurrences.each do |occurrence|
36
+ lines_except_current = lines - [occurrence.first_line]
37
+ message = format(MSG, hook_name: occurrences.first.method_name,
38
+ lines: lines_msg(lines_except_current))
39
+ add_offense(occurrence, message: message)
40
+ end
33
41
  end
34
42
  end
35
43
 
36
- def analyzable_hooks(node)
37
- RuboCop::RSpec::ExampleGroup.new(node)
44
+ def repeated_hooks(node)
45
+ hooks = RuboCop::RSpec::ExampleGroup.new(node)
38
46
  .hooks
39
- .select { |hook| hook.knowable_scope? && hook.valid_scope? }
40
- .group_by { |hook| [hook.name, hook.scope] }
47
+ .select(&:knowable_scope?)
48
+ .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
41
49
  .values
42
50
  .reject(&:one?)
43
- .flatten
44
- .map(&:to_node)
51
+
52
+ hooks.map do |hook|
53
+ hook.map(&:to_node)
54
+ end
55
+ end
56
+
57
+ def lines_msg(numbers)
58
+ if numbers.size == 1
59
+ "line #{numbers.first}"
60
+ else
61
+ "lines #{numbers.join(', ')}"
62
+ end
45
63
  end
46
64
  end
47
65
  end
@@ -74,6 +74,8 @@ require_relative 'rspec/receive_counts'
74
74
  require_relative 'rspec/receive_never'
75
75
  require_relative 'rspec/repeated_description'
76
76
  require_relative 'rspec/repeated_example'
77
+ require_relative 'rspec/repeated_example_group_body'
78
+ require_relative 'rspec/repeated_example_group_description'
77
79
  require_relative 'rspec/return_from_stub'
78
80
  require_relative 'rspec/scattered_let'
79
81
  require_relative 'rspec/scattered_setup'
@@ -4,21 +4,24 @@ module RuboCop
4
4
  module RSpec
5
5
  # Wrapper for RSpec hook
6
6
  class Hook < Concept
7
- STANDARDIZED_SCOPES = %i[each context suite].freeze
8
- private_constant(:STANDARDIZED_SCOPES)
7
+ def_node_matcher :extract_metadata, <<~PATTERN
8
+ (block
9
+ {
10
+ (send _ _ #valid_scope? $...)
11
+ (send _ _ $...)
12
+ }
13
+ ...
14
+ )
15
+ PATTERN
9
16
 
10
17
  def name
11
18
  node.method_name
12
19
  end
13
20
 
14
21
  def knowable_scope?
15
- return true unless scope_argument
16
-
17
- scope_argument.sym_type?
18
- end
19
-
20
- def valid_scope?
21
- STANDARDIZED_SCOPES.include?(scope)
22
+ scope_argument.nil? ||
23
+ scope_argument.sym_type? ||
24
+ scope_argument.hash_type?
22
25
  end
23
26
 
24
27
  def example?
@@ -26,17 +29,47 @@ module RuboCop
26
29
  end
27
30
 
28
31
  def scope
32
+ return :each if scope_argument&.hash_type?
33
+
29
34
  case scope_name
30
35
  when nil, :each, :example then :each
31
36
  when :context, :all then :context
32
37
  when :suite then :suite
33
- else
34
- scope_name
35
38
  end
36
39
  end
37
40
 
41
+ def metadata
42
+ (extract_metadata(node) || [])
43
+ .map { |meta| transform_metadata(meta) }
44
+ .flatten
45
+ .inject(&:merge)
46
+ end
47
+
38
48
  private
39
49
 
50
+ def valid_scope?(node)
51
+ node&.sym_type? && Hooks::Scopes::ALL.include?(node.value)
52
+ end
53
+
54
+ def transform_metadata(meta)
55
+ if meta.sym_type?
56
+ { meta => true }
57
+ else
58
+ # This check is to be able to compare those two hooks:
59
+ #
60
+ # before(:example, :special) { ... }
61
+ # before(:example, special: true) { ... }
62
+ #
63
+ # In the second case it's a node with a pair that has a value
64
+ # of a `true_type?`.
65
+ meta.pairs.map { |pair| { pair.key => transform_true(pair.value) } }
66
+ end
67
+ end
68
+
69
+ def transform_true(node)
70
+ node.true_type? ? true : node
71
+ end
72
+
40
73
  def scope_name
41
74
  scope_argument.to_a.first
42
75
  end
@@ -94,6 +94,18 @@ module RuboCop
94
94
  append_after
95
95
  ]
96
96
  )
97
+
98
+ module Scopes
99
+ ALL = SelectorSet.new(
100
+ %i[
101
+ each
102
+ example
103
+ context
104
+ all
105
+ suite
106
+ ]
107
+ )
108
+ end
97
109
  end
98
110
 
99
111
  module Helpers
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '1.37.1'
7
+ STRING = '1.38.0'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.37.1
4
+ version: 1.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-12-16 00:00:00.000000000 Z
13
+ date: 2020-02-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -72,16 +72,16 @@ dependencies:
72
72
  name: simplecov
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
- - - ">="
75
+ - - "<"
76
76
  - !ruby/object:Gem::Version
77
- version: '0'
77
+ version: '0.18'
78
78
  type: :development
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - ">="
82
+ - - "<"
83
83
  - !ruby/object:Gem::Version
84
- version: '0'
84
+ version: '0.18'
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: yard
87
87
  requirement: !ruby/object:Gem::Requirement
@@ -182,6 +182,8 @@ files:
182
182
  - lib/rubocop/cop/rspec/receive_never.rb
183
183
  - lib/rubocop/cop/rspec/repeated_description.rb
184
184
  - lib/rubocop/cop/rspec/repeated_example.rb
185
+ - lib/rubocop/cop/rspec/repeated_example_group_body.rb
186
+ - lib/rubocop/cop/rspec/repeated_example_group_description.rb
185
187
  - lib/rubocop/cop/rspec/return_from_stub.rb
186
188
  - lib/rubocop/cop/rspec/scattered_let.rb
187
189
  - lib/rubocop/cop/rspec/scattered_setup.rb
@@ -234,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
236
  - !ruby/object:Gem::Version
235
237
  version: '0'
236
238
  requirements: []
237
- rubygems_version: 3.0.3
239
+ rubygems_version: 3.1.1
238
240
  signing_key:
239
241
  specification_version: 4
240
242
  summary: Code style checking for RSpec files