rubocop-rspec 1.37.1 → 1.38.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 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