rubocop-rspec 1.44.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 =