rubocop-rspec 1.44.0 → 2.1.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +170 -81
  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 +6 -55
  10. data/lib/rubocop/cop/rspec/be.rb +2 -2
  11. data/lib/rubocop/cop/rspec/before_after_all.rb +3 -3
  12. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +2 -2
  13. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +3 -2
  14. data/lib/rubocop/cop/rspec/describe_class.rb +2 -2
  15. data/lib/rubocop/cop/rspec/describe_method.rb +2 -2
  16. data/lib/rubocop/cop/rspec/described_class.rb +1 -2
  17. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -2
  18. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  19. data/lib/rubocop/cop/rspec/empty_example_group.rb +6 -45
  20. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
  21. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  22. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  23. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -1
  24. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -1
  25. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -2
  26. data/lib/rubocop/cop/rspec/expect_actual.rb +2 -1
  27. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  28. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  29. data/lib/rubocop/cop/rspec/file_path.rb +24 -17
  30. data/lib/rubocop/cop/rspec/focus.rb +43 -8
  31. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -4
  32. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +2 -2
  33. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
  34. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  35. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  36. data/lib/rubocop/cop/rspec/it_behaves_like.rb +1 -1
  37. data/lib/rubocop/cop/rspec/iterated_expectation.rb +1 -1
  38. data/lib/rubocop/cop/rspec/let_before_examples.rb +2 -2
  39. data/lib/rubocop/cop/rspec/let_setup.rb +7 -4
  40. data/lib/rubocop/cop/rspec/message_spies.rb +3 -3
  41. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +51 -0
  42. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  43. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  44. data/lib/rubocop/cop/rspec/mixin/variable.rb +20 -0
  45. data/lib/rubocop/cop/rspec/multiple_describes.rb +2 -3
  46. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
  47. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +3 -1
  48. data/lib/rubocop/cop/rspec/named_subject.rb +8 -12
  49. data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
  50. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -1
  51. data/lib/rubocop/cop/rspec/pending.rb +13 -5
  52. data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
  53. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -2
  54. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  55. data/lib/rubocop/cop/rspec/shared_context.rb +18 -11
  56. data/lib/rubocop/cop/rspec/shared_examples.rb +3 -1
  57. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -1
  58. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
  59. data/lib/rubocop/cop/rspec/subject_stub.rb +16 -6
  60. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
  61. data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
  62. data/lib/rubocop/cop/rspec_cops.rb +0 -1
  63. data/lib/rubocop/rspec/align_let_brace.rb +1 -1
  64. data/lib/rubocop/rspec/concept.rb +2 -2
  65. data/lib/rubocop/rspec/config_formatter.rb +5 -3
  66. data/lib/rubocop/rspec/corrector/move_node.rb +1 -1
  67. data/lib/rubocop/rspec/example_group.rb +15 -5
  68. data/lib/rubocop/rspec/hook.rb +1 -1
  69. data/lib/rubocop/rspec/inject.rb +4 -2
  70. data/lib/rubocop/rspec/language.rb +143 -109
  71. data/lib/rubocop/rspec/language/node_pattern.rb +7 -24
  72. data/lib/rubocop/rspec/node.rb +1 -1
  73. data/lib/rubocop/rspec/version.rb +1 -1
  74. metadata +13 -17
  75. data/lib/rubocop/cop/rspec/cop.rb +0 -10
  76. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +0 -41
  77. data/lib/rubocop/rspec.rb +0 -12
  78. data/lib/rubocop/rspec/empty_line_separation.rb +0 -48
  79. data/lib/rubocop/rspec/final_end_location.rb +0 -17
  80. data/lib/rubocop/rspec/top_level_describe.rb +0 -52
  81. data/lib/rubocop/rspec/top_level_group.rb +0 -57
  82. data/lib/rubocop/rspec/variable.rb +0 -16
@@ -23,7 +23,7 @@ module RuboCop
23
23
  class ExpectInHook < Base
24
24
  MSG = 'Do not use `%<expect>s` in `%<hook>s` hook'
25
25
 
26
- def_node_search :expectation, Expectations::ALL.send_pattern
26
+ def_node_search :expectation, send_pattern('#Expectations.all')
27
27
 
28
28
  def on_block(node)
29
29
  return unless hook?(node)
@@ -15,7 +15,7 @@ module RuboCop
15
15
  # # good
16
16
  # expect { my_app.print_report }.to output('Hello World').to_stdout
17
17
  class ExpectOutput < Base
18
- MSG = 'Use `expect { ... }.to output(...).to_%<name>s` '\
18
+ MSG = 'Use `expect { ... }.to output(...).to_%<name>s` ' \
19
19
  'instead of mutating $%<name>s.'
20
20
 
21
21
  def on_gvasgn(node)
@@ -57,7 +57,7 @@ module RuboCop
57
57
  # my_class_spec.rb # describe MyClass, '#method'
58
58
  #
59
59
  class FilePath < Base
60
- include RuboCop::RSpec::TopLevelGroup
60
+ include TopLevelGroup
61
61
 
62
62
  MSG = 'Spec path should end with `%<suffix>s`.'
63
63
 
@@ -82,30 +82,39 @@ module RuboCop
82
82
  private
83
83
 
84
84
  def ensure_correct_file_path(send_node, described_class, arguments)
85
- glob = glob_for(described_class, arguments.first)
86
- return if filename_ends_with?(glob)
87
-
88
- add_offense(send_node, message: format(MSG, suffix: glob))
85
+ pattern = pattern_for(described_class, arguments.first)
86
+ return if filename_ends_with?(pattern)
87
+
88
+ # For the suffix shown in the offense message, modify the regular
89
+ # expression pattern to resemble a glob pattern for clearer error
90
+ # messages.
91
+ offense_suffix = pattern.gsub('.*', '*').sub('[^/]', '')
92
+ .sub('\.', '.')
93
+ add_offense(send_node, message: format(MSG, suffix: offense_suffix))
89
94
  end
90
95
 
91
96
  def routing_spec?(args)
92
97
  args.any?(&method(:routing_metadata?))
93
98
  end
94
99
 
95
- def glob_for(described_class, method_name)
96
- return glob_for_spec_suffix_only? if spec_suffix_only?
100
+ def pattern_for(described_class, method_name)
101
+ return pattern_for_spec_suffix_only? if spec_suffix_only?
97
102
 
98
- "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
103
+ [
104
+ expected_path(described_class),
105
+ name_pattern(method_name),
106
+ '[^/]*_spec\.rb'
107
+ ].join
99
108
  end
100
109
 
101
- def glob_for_spec_suffix_only?
102
- '*_spec.rb'
110
+ def pattern_for_spec_suffix_only?
111
+ '.*_spec\.rb'
103
112
  end
104
113
 
105
- def name_glob(method_name)
114
+ def name_pattern(method_name)
106
115
  return unless method_name&.str_type?
107
116
 
108
- "*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
117
+ ".*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
109
118
  end
110
119
 
111
120
  def expected_path(constant)
@@ -131,11 +140,9 @@ module RuboCop
131
140
  cop_config['IgnoreMethods']
132
141
  end
133
142
 
134
- def filename_ends_with?(glob)
135
- filename =
136
- RuboCop::PathUtil.relative_path(processed_source.buffer.name)
137
- .gsub('../', '')
138
- File.fnmatch?("*#{glob}", filename)
143
+ def filename_ends_with?(pattern)
144
+ filename = File.expand_path(processed_source.buffer.name)
145
+ filename.match?("#{pattern}$")
139
146
  end
140
147
 
141
148
  def relevant_rubocop_rspec_file?(_file)
@@ -20,25 +20,40 @@ module RuboCop
20
20
  # describe MyClass do
21
21
  # end
22
22
  class Focus < Base
23
- MSG = 'Focused spec found.'
23
+ extend AutoCorrector
24
+ include RangeHelp
24
25
 
25
- focused = ExampleGroups::FOCUSED + Examples::FOCUSED
26
+ MSG = 'Focused spec found.'
26
27
 
27
- def_node_matcher :focusable_selector?,
28
- (ExampleGroups::GROUPS + ExampleGroups::SKIPPED +
29
- Examples::EXAMPLES + Examples::SKIPPED +
30
- Examples::PENDING).node_pattern_union
28
+ def_node_matcher :focusable_selector?, <<-PATTERN
29
+ {
30
+ #ExampleGroups.regular
31
+ #ExampleGroups.skipped
32
+ #Examples.regular
33
+ #Examples.skipped
34
+ #Examples.pending
35
+ }
36
+ PATTERN
31
37
 
32
38
  def_node_matcher :metadata, <<-PATTERN
33
39
  {(send #rspec? #focusable_selector? <$(sym :focus) ...>)
34
40
  (send #rspec? #focusable_selector? ... (hash <$(pair (sym :focus) true) ...>))}
35
41
  PATTERN
36
42
 
37
- def_node_matcher :focused_block?, focused.send_pattern
43
+ def_node_matcher :focused_block?,
44
+ send_pattern(<<~PATTERN)
45
+ {#ExampleGroups.focused #Examples.focused}
46
+ PATTERN
38
47
 
39
48
  def on_send(node)
40
49
  focus_metadata(node) do |focus|
41
- add_offense(focus)
50
+ add_offense(focus) do |corrector|
51
+ if focus.pair_type? || focus.str_type? || focus.sym_type?
52
+ corrector.remove(with_surrounding(focus))
53
+ elsif focus.send_type?
54
+ correct_send(corrector, focus)
55
+ end
56
+ end
42
57
  end
43
58
  end
44
59
 
@@ -49,6 +64,26 @@ module RuboCop
49
64
 
50
65
  metadata(node, &block)
51
66
  end
67
+
68
+ def with_surrounding(focus)
69
+ range_with_space = range_with_surrounding_space(
70
+ range: focus.loc.expression,
71
+ side: :left
72
+ )
73
+
74
+ range_with_surrounding_comma(range_with_space, :left)
75
+ end
76
+
77
+ def correct_send(corrector, focus)
78
+ range = focus.loc.selector
79
+ unfocused = focus.method_name.to_s.sub(/^f/, '')
80
+ unless Examples.regular(unfocused) || ExampleGroups.regular(unfocused)
81
+ return
82
+ end
83
+
84
+ corrector.replace(range,
85
+ range.source.sub(focus.method_name.to_s, unfocused))
86
+ end
52
87
  end
53
88
  end
54
89
  end
@@ -64,13 +64,11 @@ module RuboCop
64
64
  IMPLICIT_MSG = 'Omit the default `%<scope>p` argument for RSpec hooks.'
65
65
  EXPLICIT_MSG = 'Use `%<scope>p` for RSpec hooks.'
66
66
 
67
- def_node_matcher :hook?, Hooks::ALL.node_pattern_union
68
-
69
67
  def_node_matcher :scoped_hook, <<-PATTERN
70
- (block $(send _ #hook? (sym ${:each :example})) ...)
68
+ (block $(send _ #Hooks.all (sym ${:each :example})) ...)
71
69
  PATTERN
72
70
 
73
- def_node_matcher :unscoped_hook, '(block $(send _ #hook?) ...)'
71
+ def_node_matcher :unscoped_hook, '(block $(send _ #Hooks.all) ...)'
74
72
 
75
73
  def on_block(node)
76
74
  hook(node) do |method_send, scope_name|
@@ -30,8 +30,8 @@ module RuboCop
30
30
 
31
31
  def_node_matcher :example_or_group?, <<-PATTERN
32
32
  {
33
- #{(Examples::ALL + ExampleGroups::ALL).block_pattern}
34
- #{Includes::EXAMPLES.send_pattern}
33
+ #{block_pattern('{#ExampleGroups.all #Examples.all}')}
34
+ #{send_pattern('#Includes.examples')}
35
35
  }
36
36
  PATTERN
37
37
 
@@ -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
 
@@ -21,7 +21,7 @@ module RuboCop
21
21
  class InstanceSpy < Base
22
22
  extend AutoCorrector
23
23
 
24
- MSG = 'Use `instance_spy` when you check your double '\
24
+ MSG = 'Use `instance_spy` when you check your double ' \
25
25
  'with `have_received`.'
26
26
 
27
27
  def_node_search :null_double, <<-PATTERN
@@ -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).'
@@ -22,7 +22,7 @@ module RuboCop
22
22
  extend AutoCorrector
23
23
  include ConfigurableEnforcedStyle
24
24
 
25
- MSG = 'Prefer `%<replacement>s` over `%<original>s` when including '\
25
+ MSG = 'Prefer `%<replacement>s` over `%<original>s` when including ' \
26
26
  'examples in a nested context.'
27
27
 
28
28
  def_node_matcher :example_inclusion_offense, '(send _ % ...)'
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # end
18
18
  class IteratedExpectation < Base
19
19
  MSG = 'Prefer using the `all` matcher instead ' \
20
- 'of iterating over an array.'
20
+ 'of iterating over an array.'
21
21
 
22
22
  def_node_matcher :each?, <<-PATTERN
23
23
  (block
@@ -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
  {
@@ -29,14 +29,14 @@ module RuboCop
29
29
 
30
30
  MSG_RECEIVE = 'Prefer `receive` for setting message expectations.'
31
31
 
32
- MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message '\
33
- 'expectations. Setup `%<source>s` as a spy using '\
32
+ MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
33
+ 'expectations. Setup `%<source>s` as a spy using ' \
34
34
  '`allow` or `instance_spy`.'
35
35
 
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,10 +23,9 @@ module RuboCop
23
23
  # end
24
24
  # end
25
25
  class MultipleDescribes < Base
26
- include RuboCop::RSpec::TopLevelGroup
26
+ include TopLevelGroup
27
27
 
28
- MSG = 'Do not use multiple top-level example groups - '\
29
- 'try to nest them.'
28
+ MSG = 'Do not use multiple top-level example groups - try to nest them.'
30
29
 
31
30
  def on_top_level_group(node)
32
31
  top_level_example_groups =