rubocop-rspec 3.8.0 → 3.10.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/config/default.yml +22 -0
  4. data/lib/rubocop/cop/rspec/around_block.rb +22 -0
  5. data/lib/rubocop/cop/rspec/contain_exactly.rb +8 -22
  6. data/lib/rubocop/cop/rspec/context_method.rb +1 -1
  7. data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
  8. data/lib/rubocop/cop/rspec/described_class.rb +3 -1
  9. data/lib/rubocop/cop/rspec/discarded_matcher.rb +113 -0
  10. data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -2
  11. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
  12. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  13. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  14. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +7 -2
  15. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -0
  16. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
  17. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  18. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
  19. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  20. data/lib/rubocop/cop/rspec/expect_actual.rb +33 -13
  21. data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
  22. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -0
  23. data/lib/rubocop/cop/rspec/expect_in_let.rb +1 -1
  24. data/lib/rubocop/cop/rspec/hook_argument.rb +1 -0
  25. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -0
  26. data/lib/rubocop/cop/rspec/indexed_let.rb +1 -1
  27. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  28. data/lib/rubocop/cop/rspec/iterated_expectation.rb +13 -0
  29. data/lib/rubocop/cop/rspec/leading_subject.rb +1 -1
  30. data/lib/rubocop/cop/rspec/leaky_local_variable.rb +18 -0
  31. data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -1
  32. data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
  33. data/lib/rubocop/cop/rspec/match_with_simple_regex.rb +89 -0
  34. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
  35. data/lib/rubocop/cop/rspec/mixin/inside_example.rb +16 -0
  36. data/lib/rubocop/cop/rspec/mixin/metadata.rb +1 -0
  37. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
  38. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -1
  39. data/lib/rubocop/cop/rspec/multiple_subjects.rb +1 -1
  40. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  41. data/lib/rubocop/cop/rspec/no_expectation_example.rb +1 -0
  42. data/lib/rubocop/cop/rspec/output.rb +78 -0
  43. data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
  44. data/lib/rubocop/cop/rspec/predicate_matcher.rb +1 -1
  45. data/lib/rubocop/cop/rspec/redundant_around.rb +1 -0
  46. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  47. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  48. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  49. data/lib/rubocop/cop/rspec/scattered_let.rb +11 -5
  50. data/lib/rubocop/cop/rspec/scattered_setup.rb +12 -2
  51. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  52. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +3 -6
  53. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +17 -10
  54. data/lib/rubocop/cop/rspec/subject_declaration.rb +17 -1
  55. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +1 -1
  56. data/lib/rubocop/cop/rspec/void_expect.rb +3 -5
  57. data/lib/rubocop/cop/rspec/yield.rb +1 -1
  58. data/lib/rubocop/cop/rspec_cops.rb +3 -0
  59. data/lib/rubocop/rspec/description_extractor.rb +1 -1
  60. data/lib/rubocop/rspec/hook.rb +13 -0
  61. data/lib/rubocop/rspec/version.rb +1 -1
  62. data/lib/rubocop-rspec.rb +1 -0
  63. metadata +28 -4
@@ -57,6 +57,13 @@ module RuboCop
57
57
  # expectations
58
58
  # end
59
59
  #
60
+ # # good - when variable is used only in example metadata
61
+ # skip_message = 'not yet implemented'
62
+ #
63
+ # it 'does something', skip: skip_message do
64
+ # expectations
65
+ # end
66
+ #
60
67
  # # good - when variable is used only to include other examples
61
68
  # examples = foo ? 'some examples' : 'other examples'
62
69
  #
@@ -103,6 +110,8 @@ module RuboCop
103
110
  def allowed_reference?(node)
104
111
  node.each_ancestor.any? do |ancestor|
105
112
  next true if example_method?(ancestor)
113
+ next true if in_example_arguments?(ancestor, node)
114
+
106
115
  if includes_method?(ancestor)
107
116
  next allowed_includes_arguments?(ancestor, node)
108
117
  end
@@ -111,6 +120,15 @@ module RuboCop
111
120
  end
112
121
  end
113
122
 
123
+ def in_example_arguments?(ancestor, node)
124
+ return false unless ancestor.send_type?
125
+ return false unless Examples.all(ancestor.method_name)
126
+
127
+ ancestor.arguments.any? do |arg|
128
+ arg.equal?(node) || arg.each_descendant.any?(node)
129
+ end
130
+ end
131
+
114
132
  def allowed_includes_arguments?(node, argument)
115
133
  node.arguments[1..].all? do |argument_node|
116
134
  next true if argument_node.type?(:dstr, :dsym)
@@ -55,7 +55,7 @@ module RuboCop
55
55
  [RSpec::ScatteredLet]
56
56
  end
57
57
 
58
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
58
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
59
59
  return unless example_group_with_body?(node)
60
60
 
61
61
  check_let_declarations(node.body) if multiline_block?(node.body)
@@ -59,7 +59,7 @@ module RuboCop
59
59
  # @!method method_called?(node)
60
60
  def_node_search :method_called?, '(send nil? %)'
61
61
 
62
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
62
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
63
63
  return unless example_or_shared_group_or_including?(node)
64
64
 
65
65
  unused_let_bang(node) do |let|
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'regexp_parser'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module RSpec
8
+ # Enforces the use of `include` matcher instead of `match` when the
9
+ # matcher is a simple string literal without regex-specific features.
10
+ #
11
+ # When `match` is used with a regex that contains only literal characters
12
+ # (no anchors, character classes, quantifiers, alternations, or
13
+ # metacharacters), it's clearer to use the `include` matcher instead.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # expect('foobar').to match(/foo/)
18
+ # expect(response.body).to match(/http:\/\/example\.com/)
19
+ #
20
+ # # good
21
+ # expect('foobar').to include('foo')
22
+ # expect(response.body).to include('http://example.com')
23
+ #
24
+ # # good - regex features needed
25
+ # expect('foobar').to match(/^foo/) # anchor
26
+ # expect('foobar').to match(/foo\d+/) # quantifier
27
+ # expect('foobar').to match(/foo[ob]/) # character class
28
+ #
29
+ class MatchWithSimpleRegex < Base
30
+ extend AutoCorrector
31
+
32
+ MSG = 'Prefer using `include(%<string>s)` when the regex is a simple ' \
33
+ 'string literal.'
34
+ RESTRICT_ON_SEND = %i[match].freeze
35
+
36
+ # @!method match_with_regexp?(node)
37
+ def_node_matcher :match_with_regexp?, <<~PATTERN
38
+ (send nil? :match $regexp)
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ match_with_regexp?(node) do |regexp|
43
+ next unless simple_regexp?(regexp)
44
+
45
+ string_literal = regexp_to_string(regexp)
46
+ message = format(MSG, string: string_literal)
47
+
48
+ add_offense(node, message: message) do |corrector|
49
+ corrector.replace(node, "include(#{string_literal})")
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def simple_regexp?(node)
57
+ parsed = Regexp::Parser.parse(node.content)
58
+ parsed.expressions.all? { |expr| simple_expression?(expr) }
59
+ end
60
+
61
+ def simple_expression?(expr)
62
+ return false if expr.quantified?
63
+ return true if expr.is_a?(Regexp::Expression::Literal)
64
+ return true if expr.is_a?(Regexp::Expression::EscapeSequence::Literal)
65
+
66
+ false
67
+ end
68
+
69
+ # Reconstruct the literal string that the regex matches
70
+ def regexp_to_string(node)
71
+ parsed = Regexp::Parser.parse(node.content)
72
+ string_content = parsed.expressions.map do |expr|
73
+ expr.respond_to?(:char) ? expr.char : expr.text
74
+ end.join
75
+
76
+ to_string_literal(string_content)
77
+ end
78
+
79
+ def to_string_literal(string)
80
+ if string.include?("'")
81
+ %("#{string.gsub('"', '\"')}")
82
+ else
83
+ "'#{string}'"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  class MissingExampleGroupArgument < Base
24
24
  MSG = 'The first argument to `%<method>s` should not be empty.'
25
25
 
26
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
26
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
27
27
  return unless example_group?(node)
28
28
  return if node.send_node.arguments?
29
29
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Helps check if a given node is within an example block.
7
+ module InsideExample
8
+ private
9
+
10
+ def inside_example?(node)
11
+ node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -39,6 +39,7 @@ module RuboCop
39
39
  end
40
40
  end
41
41
  alias on_numblock on_block
42
+ alias on_itblock on_block
42
43
 
43
44
  def on_metadata(_symbols, _hash)
44
45
  raise ::NotImplementedError
@@ -90,7 +90,7 @@ module RuboCop
90
90
  (block (send nil? :aggregate_failures ...) ...)
91
91
  PATTERN
92
92
 
93
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
93
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
94
94
  return unless example?(node)
95
95
 
96
96
  return if example_with_aggregate_failures?(node)
@@ -88,7 +88,7 @@ module RuboCop
88
88
 
89
89
  exclude_limit 'Max'
90
90
 
91
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
91
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
92
92
  return unless spec_group?(node)
93
93
 
94
94
  count = all_helpers(node).uniq.count
@@ -54,7 +54,7 @@ module RuboCop
54
54
 
55
55
  MSG = 'Do not set more than one subject per example group'
56
56
 
57
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
57
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
58
58
  return unless example_group?(node)
59
59
 
60
60
  subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
@@ -94,7 +94,7 @@ module RuboCop
94
94
  # @!method subject_usage(node)
95
95
  def_node_search :subject_usage, '$(send nil? :subject)'
96
96
 
97
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
97
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
98
98
  if !example_or_hook_block?(node) || ignored_shared_example?(node)
99
99
  return
100
100
  end
@@ -96,6 +96,7 @@ module RuboCop
96
96
  end
97
97
 
98
98
  alias on_numblock on_block
99
+ alias on_itblock on_block
99
100
  end
100
101
  end
101
102
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # NOTE: Originally based on the `Rails/Output` cop.
6
+ module RSpec
7
+ # Checks for the use of output calls like puts and print in specs.
8
+ #
9
+ # @safety
10
+ # This autocorrection is marked as unsafe because, in rare cases, print
11
+ # statements can be used on purpose for integration testing and deleting
12
+ # them will cause tests to fail.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # puts 'A debug message'
17
+ # pp 'A debug message'
18
+ # print 'A debug message'
19
+ class Output < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Do not write to stdout in specs.'
23
+
24
+ KERNEL_METHODS = %i[
25
+ ap
26
+ p
27
+ pp
28
+ pretty_print
29
+ print
30
+ puts
31
+ ].to_set.freeze
32
+ private_constant :KERNEL_METHODS
33
+
34
+ IO_METHODS = %i[
35
+ binwrite
36
+ syswrite
37
+ write
38
+ write_nonblock
39
+ ].to_set.freeze
40
+ private_constant :IO_METHODS
41
+
42
+ RESTRICT_ON_SEND = (KERNEL_METHODS + IO_METHODS).to_a.freeze
43
+
44
+ # @!method output?(node)
45
+ def_node_matcher :output?, <<~PATTERN
46
+ (send nil? KERNEL_METHODS ...)
47
+ PATTERN
48
+
49
+ # @!method io_output?(node)
50
+ def_node_matcher :io_output?, <<~PATTERN
51
+ (send
52
+ {
53
+ (gvar #match_gvar?)
54
+ (const {nil? cbase} {:STDOUT :STDERR})
55
+ }
56
+ IO_METHODS
57
+ ...)
58
+ PATTERN
59
+
60
+ def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity
61
+ return if node.parent&.call_type? || node.block_node
62
+ return if !output?(node) && !io_output?(node)
63
+ return if node.arguments.any? { |arg| arg.type?(:hash, :block_pass) }
64
+
65
+ add_offense(node) do |corrector|
66
+ corrector.remove(node)
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def match_gvar?(sym)
73
+ %i[$stdout $stderr].include?(sym)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -33,7 +33,7 @@ module RuboCop
33
33
  # @!method first_argument_name(node)
34
34
  def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))'
35
35
 
36
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
36
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
37
37
  return unless example_group_with_body?(node)
38
38
 
39
39
  find_duplicates(node.body) do |duplicate, name|
@@ -340,7 +340,7 @@ module RuboCop
340
340
  end
341
341
  end
342
342
 
343
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
343
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
344
344
  check_explicit(node) if style == :explicit
345
345
  end
346
346
  end
@@ -28,6 +28,7 @@ module RuboCop
28
28
  end
29
29
  end
30
30
  alias on_numblock on_block
31
+ alias on_itblock on_block
31
32
 
32
33
  def on_send(node)
33
34
  return unless match_redundant_around_hook_send?(node)
@@ -42,7 +42,7 @@ module RuboCop
42
42
  class RepeatedDescription < Base
43
43
  MSG = "Don't repeat descriptions within an example group."
44
44
 
45
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
45
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
46
46
  return unless example_group?(node)
47
47
 
48
48
  repeated_descriptions(node).each do |description|
@@ -19,7 +19,7 @@ module RuboCop
19
19
  MSG = "Don't repeat examples within an example group. " \
20
20
  'Repeated on line(s) %<lines>s.'
21
21
 
22
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
22
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
23
23
  return unless example_group?(node)
24
24
 
25
25
  find_repeated_examples(node).each do |repeated_examples|
@@ -59,7 +59,7 @@ module RuboCop
59
59
  check_and_return_call(node)
60
60
  end
61
61
 
62
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
62
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
63
63
  return unless style == :and_return
64
64
  return unless stub_with_block?(node)
65
65
 
@@ -19,11 +19,11 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # describe Foo do
22
- # subject { Foo }
23
- # before { prepare }
24
22
  # let(:foo) { 1 }
25
23
  # let(:bar) { 2 }
26
24
  # let!(:baz) { 3 }
25
+ # subject { Foo }
26
+ # before { prepare }
27
27
  # end
28
28
  #
29
29
  class ScatteredLet < Base
@@ -31,7 +31,7 @@ module RuboCop
31
31
 
32
32
  MSG = 'Group all let/let! blocks in the example group together.'
33
33
 
34
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
34
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
35
35
  return unless example_group_with_body?(node)
36
36
 
37
37
  check_let_declarations(node.body)
@@ -41,15 +41,21 @@ module RuboCop
41
41
 
42
42
  def check_let_declarations(body)
43
43
  lets = body.each_child_node.select { |node| let?(node) }
44
+ return if lets.empty?
44
45
 
45
46
  first_let = lets.first
47
+ reference_let = first_let
48
+
46
49
  lets.each_with_index do |node, idx|
47
- next if node.sibling_index == first_let.sibling_index + idx
50
+ if node.sibling_index == first_let.sibling_index + idx
51
+ reference_let = node
52
+ next
53
+ end
48
54
 
49
55
  add_offense(node) do |corrector|
50
56
  RuboCop::RSpec::Corrector::MoveNode.new(
51
57
  node, corrector, processed_source
52
- ).move_after(first_let)
58
+ ).move_after(reference_let)
53
59
  end
54
60
  end
55
61
  end
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # Unify `before` and `after` hooks when possible.
9
9
  # However, `around` hooks are allowed to be defined multiple times,
10
10
  # as unifying them would typically make the code harder to read.
11
+ # Hooks defined in class methods are also ignored.
11
12
  #
12
13
  # @example
13
14
  # # bad
@@ -30,6 +31,14 @@ module RuboCop
30
31
  # around { |example| before2; example.call; after2 }
31
32
  # end
32
33
  #
34
+ # # good
35
+ # describe Foo do
36
+ # before { setup1 }
37
+ # def self.setup
38
+ # before { setup2 }
39
+ # end
40
+ # end
41
+ #
33
42
  class ScatteredSetup < Base
34
43
  include FinalEndLocation
35
44
  include RangeHelp
@@ -38,7 +47,7 @@ module RuboCop
38
47
  MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
39
48
  'example group (also defined on %<lines>s).'
40
49
 
41
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
50
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
42
51
  return unless example_group?(node)
43
52
 
44
53
  repeated_hooks(node).each do |occurrences|
@@ -53,9 +62,10 @@ module RuboCop
53
62
 
54
63
  private
55
64
 
56
- def repeated_hooks(node)
65
+ def repeated_hooks(node) # rubocop:disable Metrics/CyclomaticComplexity
57
66
  hooks = RuboCop::RSpec::ExampleGroup.new(node)
58
67
  .hooks
68
+ .reject(&:inside_class_method?)
59
69
  .select { |hook| hook.knowable_scope? && hook.name != :around }
60
70
  .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
61
71
  .values
@@ -78,7 +78,7 @@ module RuboCop
78
78
  (block (send #rspec? #SharedGroups.examples ...) ...)
79
79
  PATTERN
80
80
 
81
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
81
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
82
82
  context_with_only_examples(node) do
83
83
  add_offense(node.send_node, message: MSG_EXAMPLES) do |corrector|
84
84
  corrector.replace(node.send_node.loc.selector, 'shared_examples')
@@ -24,6 +24,8 @@ module RuboCop
24
24
  # end
25
25
  #
26
26
  class SkipBlockInsideExample < Base
27
+ include InsideExample
28
+
27
29
  MSG = "Don't pass a block to `skip` inside examples."
28
30
 
29
31
  def on_block(node)
@@ -34,12 +36,7 @@ module RuboCop
34
36
  end
35
37
 
36
38
  alias on_numblock on_block
37
-
38
- private
39
-
40
- def inside_example?(node)
41
- node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
42
- end
39
+ alias on_itblock on_block
43
40
  end
44
41
  end
45
42
  end
@@ -44,6 +44,7 @@ module RuboCop
44
44
  include FileHelp
45
45
 
46
46
  MSG = 'Spec path should end with `%<suffix>s`.'
47
+ PATH_NAME_BOUNDARY = '(?![[:alnum:]])'
47
48
 
48
49
  # @!method example_group_arguments(node)
49
50
  def_node_matcher :example_group_arguments, <<~PATTERN
@@ -119,7 +120,11 @@ module RuboCop
119
120
  # For the suffix shown in the offense message, modify the regular
120
121
  # expression pattern to resemble a glob pattern for clearer error
121
122
  # messages.
122
- suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
123
+ suffix = pattern
124
+ .sub(PATH_NAME_BOUNDARY, '')
125
+ .sub('.*', '*')
126
+ .sub('[^/]*', '*')
127
+ .sub('\.', '.')
123
128
  add_offense(send_node, message: format(MSG, suffix: suffix))
124
129
  end
125
130
 
@@ -132,19 +137,21 @@ module RuboCop
132
137
  end
133
138
 
134
139
  def correct_path_pattern(class_name, arguments)
135
- path = [expected_path(class_name)]
136
- path << '.*' unless ignore?(arguments.first)
137
- path << [name_pattern(arguments.first), '[^/]*_spec\.rb']
138
- path.join
140
+ [
141
+ expected_path(class_name),
142
+ PATH_NAME_BOUNDARY,
143
+ method_name_pattern(arguments.first),
144
+ '[^/]*_spec\.rb'
145
+ ].join
139
146
  end
140
147
 
141
- def name_pattern(method_name)
142
- return if ignore?(method_name)
148
+ def method_name_pattern(method_name)
149
+ return if ignore_method_name?(method_name)
143
150
 
144
- method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
151
+ ".*#{method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')}"
145
152
  end
146
153
 
147
- def ignore?(method_name)
154
+ def ignore_method_name?(method_name)
148
155
  !method_name&.str_type? || ignore_methods?
149
156
  end
150
157
 
@@ -175,7 +182,7 @@ module RuboCop
175
182
  end
176
183
 
177
184
  def filename_ends_with?(pattern)
178
- expanded_file_path.match?("#{pattern}$")
185
+ expanded_file_path.match?(%r{(?:\A|/)#{pattern}$})
179
186
  end
180
187
  end
181
188
  end
@@ -20,6 +20,8 @@ module RuboCop
20
20
  # subject(:test_subject) { foo }
21
21
  #
22
22
  class SubjectDeclaration < Base
23
+ extend AutoCorrector
24
+
23
25
  MSG_LET = 'Use subject explicitly rather than using let'
24
26
  MSG_REDUNDANT = 'Ambiguous declaration of subject'
25
27
 
@@ -32,7 +34,11 @@ module RuboCop
32
34
  offense = offensive_subject_declaration?(node)
33
35
  return unless offense
34
36
 
35
- add_offense(node, message: message_for(offense))
37
+ add_offense(node, message: message_for(offense)) do |corrector|
38
+ corrector.replace(node.loc.selector,
39
+ node.method_name.to_s.sub('let', 'subject'))
40
+ corrector.remove(first_argument_range(node))
41
+ end
36
42
  end
37
43
 
38
44
  private
@@ -40,6 +46,16 @@ module RuboCop
40
46
  def message_for(offense)
41
47
  Helpers.all(offense) ? MSG_LET : MSG_REDUNDANT
42
48
  end
49
+
50
+ def first_argument_range(node)
51
+ if node.arguments.one?
52
+ node.loc.begin.join(node.loc.end)
53
+ else
54
+ node.first_argument.source_range.with(
55
+ end_pos: node.arguments[1].source_range.begin_pos
56
+ )
57
+ end
58
+ end
43
59
  end
44
60
  end
45
61
  end
@@ -52,7 +52,7 @@ module RuboCop
52
52
  (block (send #rspec? {#ExampleGroups.all #Examples.all} $_) ...)
53
53
  PATTERN
54
54
 
55
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
55
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
56
56
  example_groups_or_example?(node) do |arg|
57
57
  add_offense(arg) if offense?(arg)
58
58
  end
@@ -13,6 +13,8 @@ module RuboCop
13
13
  # expect(something).to be(1)
14
14
  #
15
15
  class VoidExpect < Base
16
+ include InsideExample
17
+
16
18
  MSG = 'Do not use `expect()` without `.to` or `.not_to`. ' \
17
19
  'Chain the methods or remove it.'
18
20
  RESTRICT_ON_SEND = %i[expect].freeze
@@ -34,7 +36,7 @@ module RuboCop
34
36
  check_expect(node)
35
37
  end
36
38
 
37
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
39
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
38
40
  return unless expect_block?(node)
39
41
  return unless inside_example?(node)
40
42
 
@@ -55,10 +57,6 @@ module RuboCop
55
57
 
56
58
  parent.block_type? && parent.body == expect
57
59
  end
58
-
59
- def inside_example?(node)
60
- node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
61
- end
62
60
  end
63
61
  end
64
62
  end
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # @!method block_call?(node)
28
28
  def_node_matcher :block_call?, '(send (lvar %) :call ...)'
29
29
 
30
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
30
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
31
31
  return unless method_on_stub?(node.send_node)
32
32
 
33
33
  block_arg(node.arguments) do |block|
@@ -21,6 +21,7 @@ require_relative 'rspec/describe_symbol'
21
21
  require_relative 'rspec/described_class'
22
22
  require_relative 'rspec/described_class_module_wrapping'
23
23
  require_relative 'rspec/dialect'
24
+ require_relative 'rspec/discarded_matcher'
24
25
  require_relative 'rspec/duplicated_metadata'
25
26
  require_relative 'rspec/empty_example_group'
26
27
  require_relative 'rspec/empty_hook'
@@ -61,6 +62,7 @@ require_relative 'rspec/leaky_local_variable'
61
62
  require_relative 'rspec/let_before_examples'
62
63
  require_relative 'rspec/let_setup'
63
64
  require_relative 'rspec/match_array'
65
+ require_relative 'rspec/match_with_simple_regex'
64
66
  require_relative 'rspec/message_chain'
65
67
  require_relative 'rspec/message_expectation'
66
68
  require_relative 'rspec/message_spies'
@@ -75,6 +77,7 @@ require_relative 'rspec/named_subject'
75
77
  require_relative 'rspec/nested_groups'
76
78
  require_relative 'rspec/no_expectation_example'
77
79
  require_relative 'rspec/not_to_not'
80
+ require_relative 'rspec/output'
78
81
  require_relative 'rspec/overwriting_setup'
79
82
  require_relative 'rspec/pending'
80
83
  require_relative 'rspec/pending_without_reason'