rubocop-rspec 1.43.1 → 2.0.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -4
  3. data/README.md +4 -0
  4. data/config/default.yml +141 -25
  5. data/lib/rubocop-rspec.rb +7 -8
  6. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +7 -3
  7. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +7 -3
  8. data/lib/rubocop/cop/rspec/around_block.rb +1 -1
  9. data/lib/rubocop/cop/rspec/base.rb +7 -54
  10. data/lib/rubocop/cop/rspec/be.rb +1 -1
  11. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +2 -2
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +3 -2
  13. data/lib/rubocop/cop/rspec/describe_class.rb +33 -14
  14. data/lib/rubocop/cop/rspec/describe_method.rb +1 -1
  15. data/lib/rubocop/cop/rspec/described_class.rb +1 -2
  16. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -2
  17. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  18. data/lib/rubocop/cop/rspec/empty_example_group.rb +33 -38
  19. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
  20. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  21. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  22. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -1
  23. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -1
  24. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -2
  25. data/lib/rubocop/cop/rspec/expect_actual.rb +1 -1
  26. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  27. data/lib/rubocop/cop/rspec/file_path.rb +2 -2
  28. data/lib/rubocop/cop/rspec/focus.rb +13 -7
  29. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -4
  30. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +2 -2
  31. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +1 -2
  32. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
  33. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  34. data/lib/rubocop/cop/rspec/leading_subject.rb +5 -1
  35. data/lib/rubocop/cop/rspec/let_before_examples.rb +2 -2
  36. data/lib/rubocop/cop/rspec/let_setup.rb +7 -4
  37. data/lib/rubocop/cop/rspec/message_spies.rb +1 -1
  38. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +51 -0
  39. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  40. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  41. data/lib/rubocop/cop/rspec/mixin/variable.rb +20 -0
  42. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -1
  43. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
  44. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +3 -1
  45. data/lib/rubocop/cop/rspec/named_subject.rb +8 -12
  46. data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
  47. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
  48. data/lib/rubocop/cop/rspec/pending.rb +13 -5
  49. data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
  50. data/lib/rubocop/cop/rspec/repeated_include_example.rb +104 -0
  51. data/lib/rubocop/cop/rspec/shared_context.rb +16 -6
  52. data/lib/rubocop/cop/rspec/shared_examples.rb +3 -1
  53. data/lib/rubocop/cop/rspec/stubbed_mock.rb +172 -0
  54. data/lib/rubocop/cop/rspec/subject_stub.rb +6 -6
  55. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
  56. data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
  57. data/lib/rubocop/cop/rspec_cops.rb +2 -1
  58. data/lib/rubocop/rspec/align_let_brace.rb +1 -1
  59. data/lib/rubocop/rspec/concept.rb +2 -2
  60. data/lib/rubocop/rspec/config_formatter.rb +3 -3
  61. data/lib/rubocop/rspec/corrector/move_node.rb +1 -1
  62. data/lib/rubocop/rspec/example_group.rb +15 -5
  63. data/lib/rubocop/rspec/hook.rb +2 -6
  64. data/lib/rubocop/rspec/inject.rb +4 -2
  65. data/lib/rubocop/rspec/language.rb +144 -105
  66. data/lib/rubocop/rspec/language/node_pattern.rb +7 -24
  67. data/lib/rubocop/rspec/version.rb +1 -1
  68. metadata +25 -13
  69. data/lib/rubocop/cop/rspec/cop.rb +0 -10
  70. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +0 -41
  71. data/lib/rubocop/rspec.rb +0 -12
  72. data/lib/rubocop/rspec/empty_line_separation.rb +0 -48
  73. data/lib/rubocop/rspec/final_end_location.rb +0 -17
  74. data/lib/rubocop/rspec/top_level_describe.rb +0 -52
  75. data/lib/rubocop/rspec/top_level_group.rb +0 -57
  76. data/lib/rubocop/rspec/variable.rb +0 -16
@@ -22,8 +22,7 @@ module RuboCop
22
22
  def_node_matcher :lambda?, <<-PATTERN
23
23
  {
24
24
  (send (const nil? :Proc) :new)
25
- (send nil? :proc)
26
- (send nil? :lambda)
25
+ (send nil? {:proc :lambda})
27
26
  }
28
27
  PATTERN
29
28
 
@@ -33,7 +33,7 @@ module RuboCop
33
33
  def_node_matcher :implicit_expect, <<-PATTERN
34
34
  {
35
35
  (send nil? ${:should :should_not} ...)
36
- (send (send nil? $:is_expected) #{Runners::ALL.node_pattern_union} ...)
36
+ (send (send nil? $:is_expected) #Runners.all ...)
37
37
  }
38
38
  PATTERN
39
39
 
@@ -47,7 +47,7 @@ module RuboCop
47
47
  # end
48
48
  #
49
49
  class InstanceVariable < Base
50
- include RuboCop::RSpec::TopLevelGroup
50
+ include TopLevelGroup
51
51
 
52
52
  MSG = 'Avoid instance variables – use let, ' \
53
53
  'a method call, or a local variable (if possible).'
@@ -54,13 +54,17 @@ module RuboCop
54
54
  private
55
55
 
56
56
  def offending_node(node)
57
- node.parent.each_child_node.find do |sibling|
57
+ parent(node).each_child_node.find do |sibling|
58
58
  break if sibling.equal?(node)
59
59
 
60
60
  yield sibling if offending?(sibling)
61
61
  end
62
62
  end
63
63
 
64
+ def parent(node)
65
+ node.each_ancestor(:block).first.body
66
+ end
67
+
64
68
  def autocorrect(corrector, node, sibling)
65
69
  RuboCop::RSpec::Corrector::MoveNode.new(
66
70
  node, corrector, processed_source
@@ -37,8 +37,8 @@ module RuboCop
37
37
 
38
38
  def_node_matcher :example_or_group?, <<-PATTERN
39
39
  {
40
- #{(Examples::ALL + ExampleGroups::ALL).block_pattern}
41
- #{Includes::EXAMPLES.send_pattern}
40
+ #{block_pattern('{#ExampleGroups.all #Examples.all}')}
41
+ #{send_pattern('#Includes.examples')}
42
42
  }
43
43
  PATTERN
44
44
 
@@ -29,10 +29,13 @@ module RuboCop
29
29
  MSG = 'Do not use `let!` to setup objects not referenced in tests.'
30
30
 
31
31
  def_node_matcher :example_or_shared_group_or_including?,
32
- (
33
- ExampleGroups::ALL + SharedGroups::ALL +
34
- Includes::ALL
35
- ).block_pattern
32
+ block_pattern(<<~PATTERN)
33
+ {
34
+ #SharedGroups.all
35
+ #ExampleGroups.all
36
+ #Includes.all
37
+ }
38
+ PATTERN
36
39
 
37
40
  def_node_matcher :let_bang, <<-PATTERN
38
41
  {
@@ -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 $_) #{Runners::ALL.node_pattern_union} ...)
39
+ (send (send nil? :expect $_) #Runners.all ...)
40
40
  )
41
41
 
42
42
  def_node_search :receive_message, %(
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps determine the offending location if there is not an empty line
7
+ # following the node. Allows comments to follow directly after.
8
+ module EmptyLineSeparation
9
+ include FinalEndLocation
10
+ include RangeHelp
11
+
12
+ def missing_separating_line_offense(node)
13
+ return if last_child?(node)
14
+
15
+ missing_separating_line(node) do |location|
16
+ msg = yield(node.method_name)
17
+ add_offense(location, message: msg) do |corrector|
18
+ corrector.insert_after(location.end, "\n")
19
+ end
20
+ end
21
+ end
22
+
23
+ def missing_separating_line(node)
24
+ line = final_end_location(node).line
25
+
26
+ line += 1 while comment_line?(processed_source[line])
27
+
28
+ return if processed_source[line].blank?
29
+
30
+ yield offending_loc(line)
31
+ end
32
+
33
+ def offending_loc(last_line)
34
+ offending_line = processed_source[last_line - 1]
35
+
36
+ content_length = offending_line.lstrip.length
37
+ start = offending_line.length - content_length
38
+
39
+ source_range(processed_source.buffer,
40
+ last_line, start, content_length)
41
+ end
42
+
43
+ def last_child?(node)
44
+ return true unless node.parent&.begin_type?
45
+
46
+ node.equal?(node.parent.children.last)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps find the true end location of nodes which might contain heredocs.
7
+ module FinalEndLocation
8
+ def final_end_location(start_node)
9
+ heredoc_endings =
10
+ start_node.each_node(:str, :dstr, :xstr)
11
+ .select(&:heredoc?)
12
+ .map { |node| node.loc.heredoc_end }
13
+
14
+ [start_node.loc.end, *heredoc_endings].max_by(&:line)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helper methods for top level example group cops
7
+ module TopLevelGroup
8
+ extend RuboCop::NodePattern::Macros
9
+
10
+ def on_new_investigation
11
+ super
12
+
13
+ top_level_groups.each do |node|
14
+ on_top_level_example_group(node) if example_group?(node)
15
+ on_top_level_group(node)
16
+ end
17
+ end
18
+
19
+ def top_level_groups
20
+ @top_level_groups ||=
21
+ top_level_nodes(root_node).select { |n| spec_group?(n) }
22
+ end
23
+
24
+ private
25
+
26
+ # Dummy methods to be overridden in the consumer
27
+ def on_top_level_example_group(_node); end
28
+
29
+ def on_top_level_group(_node); end
30
+
31
+ def top_level_group?(node)
32
+ top_level_groups.include?(node)
33
+ end
34
+
35
+ def top_level_nodes(node)
36
+ return [] if node.nil?
37
+
38
+ case node.type
39
+ when :begin
40
+ node.children
41
+ when :module, :class
42
+ top_level_nodes(node.body)
43
+ else
44
+ [node]
45
+ end
46
+ end
47
+
48
+ def root_node
49
+ processed_source.ast
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps check offenses with variable definitions
7
+ module Variable
8
+ extend RuboCop::NodePattern::Macros
9
+
10
+ Subjects = RuboCop::RSpec::Language::Subjects
11
+ Helpers = RuboCop::RSpec::Language::Helpers
12
+
13
+ def_node_matcher :variable_definition?, <<~PATTERN
14
+ (send nil? {#Subjects.all #Helpers.all}
15
+ $({sym str dsym dstr} ...) ...)
16
+ PATTERN
17
+ end
18
+ end
19
+ end
20
+ end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # end
24
24
  # end
25
25
  class MultipleDescribes < Base
26
- include RuboCop::RSpec::TopLevelGroup
26
+ include TopLevelGroup
27
27
 
28
28
  MSG = 'Do not use multiple top-level example groups - '\
29
29
  'try to nest them.'
@@ -60,7 +60,7 @@ module RuboCop
60
60
  } ...)
61
61
  PATTERN
62
62
 
63
- def_node_matcher :expect?, Expectations::ALL.send_pattern
63
+ def_node_matcher :expect?, send_pattern('#Expectations.all')
64
64
  def_node_matcher :aggregate_failures_block?, <<-PATTERN
65
65
  (block (send nil? :aggregate_failures ...) ...)
66
66
  PATTERN
@@ -85,7 +85,7 @@ module RuboCop
85
85
  #
86
86
  class MultipleMemoizedHelpers < Base
87
87
  include ConfigurableMax
88
- include RuboCop::RSpec::Variable
88
+ include Variable
89
89
 
90
90
  MSG = 'Example group has too many memoized helpers [%<count>d/%<max>d]'
91
91
 
@@ -101,6 +101,7 @@ module RuboCop
101
101
  end
102
102
 
103
103
  def on_new_investigation
104
+ super
104
105
  @example_group_memoized_helpers = {}
105
106
  end
106
107
 
@@ -128,6 +129,7 @@ module RuboCop
128
129
 
129
130
  def variable_nodes(node)
130
131
  example_group = RuboCop::RSpec::ExampleGroup.new(node)
132
+
131
133
  if allow_subject?
132
134
  example_group.lets
133
135
  else
@@ -42,24 +42,20 @@ module RuboCop
42
42
  # it { is_expected.to be_valid }
43
43
  # end
44
44
  class NamedSubject < Base
45
- MSG = 'Name your test subject if you need '\
46
- 'to reference it explicitly.'
45
+ MSG = 'Name your test subject if you need to reference it explicitly.'
47
46
 
48
- def_node_matcher :rspec_block?, <<-PATTERN
49
- {
50
- #{Examples::ALL.block_pattern}
51
- #{Hooks::ALL.block_pattern}
52
- }
53
- PATTERN
47
+ def_node_matcher :example_or_hook_block?,
48
+ block_pattern('{#Examples.all #Hooks.all}')
54
49
 
55
- def_node_matcher :shared_example?, <<-PATTERN
56
- #{SharedGroups::EXAMPLES.block_pattern}
57
- PATTERN
50
+ def_node_matcher :shared_example?,
51
+ block_pattern('#SharedGroups.examples')
58
52
 
59
53
  def_node_search :subject_usage, '$(send nil? :subject)'
60
54
 
61
55
  def on_block(node)
62
- return if !rspec_block?(node) || ignored_shared_example?(node)
56
+ if !example_or_hook_block?(node) || ignored_shared_example?(node)
57
+ return
58
+ end
63
59
 
64
60
  subject_usage(node) do |subject_node|
65
61
  add_offense(subject_node.loc.selector)
@@ -87,7 +87,7 @@ module RuboCop
87
87
  #
88
88
  class NestedGroups < Base
89
89
  include ConfigurableMax
90
- include RuboCop::RSpec::TopLevelGroup
90
+ include TopLevelGroup
91
91
 
92
92
  MSG = 'Maximum example group nesting exceeded [%<total>d/%<max>d].'
93
93
 
@@ -24,7 +24,8 @@ module RuboCop
24
24
  class OverwritingSetup < Base
25
25
  MSG = '`%<name>s` is already defined.'
26
26
 
27
- def_node_matcher :setup?, (Helpers::ALL + Subject::ALL).block_pattern
27
+ def_node_matcher :setup?, block_pattern('{#Helpers.all #Subjects.all}')
28
+
28
29
  def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))'
29
30
 
30
31
  def on_block(node)
@@ -34,10 +34,10 @@ module RuboCop
34
34
  class Pending < Base
35
35
  MSG = 'Pending spec found.'
36
36
 
37
- PENDING = Examples::PENDING + Examples::SKIPPED + ExampleGroups::SKIPPED
38
- SKIPPABLE = ExampleGroups::GROUPS + Examples::EXAMPLES
39
-
40
- def_node_matcher :skippable?, SKIPPABLE.send_pattern
37
+ def_node_matcher :skippable?,
38
+ send_pattern(<<~PATTERN)
39
+ {#ExampleGroups.regular #Examples.regular}
40
+ PATTERN
41
41
 
42
42
  def_node_matcher :skipped_in_metadata?, <<-PATTERN
43
43
  {
@@ -47,7 +47,15 @@ module RuboCop
47
47
  PATTERN
48
48
 
49
49
  def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
50
- def_node_matcher :pending_block?, PENDING.send_pattern
50
+
51
+ def_node_matcher :pending_block?,
52
+ send_pattern(<<~PATTERN)
53
+ {
54
+ #ExampleGroups.skipped
55
+ #Examples.skipped
56
+ #Examples.pending
57
+ }
58
+ PATTERN
51
59
 
52
60
  def on_send(node)
53
61
  return unless pending_block?(node) || skipped?(node)
@@ -30,7 +30,7 @@ module RuboCop
30
30
  (send nil? :expect {
31
31
  (block $(send !nil? #predicate? ...) ...)
32
32
  $(send !nil? #predicate? ...)})
33
- $#{Runners::ALL.node_pattern_union}
33
+ $#Runners.all
34
34
  $#boolean_matcher?)
35
35
  PATTERN
36
36
 
@@ -155,7 +155,7 @@ module RuboCop
155
155
  def_node_matcher :predicate_matcher?, <<-PATTERN
156
156
  (send
157
157
  (send nil? :expect $!nil?)
158
- #{Runners::ALL.node_pattern_union}
158
+ #Runners.all
159
159
  {$(send nil? #predicate_matcher_name? ...)
160
160
  (block $(send nil? #predicate_matcher_name? ...) ...)})
161
161
  PATTERN
@@ -164,7 +164,7 @@ module RuboCop
164
164
  (block
165
165
  (send
166
166
  (send nil? :expect $!nil?)
167
- #{Runners::ALL.node_pattern_union}
167
+ #Runners.all
168
168
  $(send nil? #predicate_matcher_name?))
169
169
  ...)
170
170
  PATTERN
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated include of shared examples.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # describe 'foo' do
12
+ # include_examples 'cool stuff'
13
+ # include_examples 'cool stuff'
14
+ # end
15
+ #
16
+ # # bad
17
+ # describe 'foo' do
18
+ # it_behaves_like 'a cool', 'thing'
19
+ # it_behaves_like 'a cool', 'thing'
20
+ # end
21
+ #
22
+ # # bad
23
+ # context 'foo' do
24
+ # it_should_behave_like 'a duck'
25
+ # it_should_behave_like 'a duck'
26
+ # end
27
+ #
28
+ # # good
29
+ # describe 'foo' do
30
+ # include_examples 'cool stuff'
31
+ # end
32
+ #
33
+ # describe 'bar' do
34
+ # include_examples 'cool stuff'
35
+ # end
36
+ #
37
+ # # good
38
+ # describe 'foo' do
39
+ # it_behaves_like 'a cool', 'thing'
40
+ # it_behaves_like 'a cool', 'person'
41
+ # end
42
+ #
43
+ # # good
44
+ # context 'foo' do
45
+ # it_should_behave_like 'a duck'
46
+ # it_should_behave_like 'a goose'
47
+ # end
48
+ #
49
+ class RepeatedIncludeExample < Base
50
+ MSG = 'Repeated include of shared_examples %<name>s ' \
51
+ 'on line(s) %<repeat>s'
52
+
53
+ def_node_matcher :several_include_examples?, <<-PATTERN
54
+ (begin <#include_examples? #include_examples? ...>)
55
+ PATTERN
56
+
57
+ def_node_matcher :include_examples?,
58
+ send_pattern('#Includes.examples')
59
+
60
+ def_node_matcher :shared_examples_name, <<-PATTERN
61
+ (send _ #Includes.examples $_ ...)
62
+ PATTERN
63
+
64
+ def on_begin(node)
65
+ return unless several_include_examples?(node)
66
+
67
+ repeated_include_examples(node).each do |item, repeats|
68
+ add_offense(item, message: message(item, repeats))
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def repeated_include_examples(node)
75
+ node
76
+ .children
77
+ .select { |child| literal_include_examples?(child) }
78
+ .group_by { |child| signature_keys(child) }
79
+ .values
80
+ .reject(&:one?)
81
+ .flat_map { |items| add_repeated_lines(items) }
82
+ end
83
+
84
+ def literal_include_examples?(node)
85
+ include_examples?(node) &&
86
+ node.arguments.all?(&:recursive_literal_or_const?)
87
+ end
88
+
89
+ def add_repeated_lines(items)
90
+ repeated_lines = items.map(&:first_line)
91
+ items.map { |item| [item, repeated_lines - [item.first_line]] }
92
+ end
93
+
94
+ def signature_keys(item)
95
+ item.arguments
96
+ end
97
+
98
+ def message(item, repeats)
99
+ format(MSG, name: shared_examples_name(item).source, repeat: repeats)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end