rubocop-rspec 2.0.0.pre → 2.3.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -2
  3. data/README.md +7 -3
  4. data/config/default.yml +159 -73
  5. data/lib/rubocop-rspec.rb +8 -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/any_instance.rb +6 -10
  9. data/lib/rubocop/cop/rspec/around_block.rb +3 -1
  10. data/lib/rubocop/cop/rspec/base.rb +6 -55
  11. data/lib/rubocop/cop/rspec/be.rb +3 -2
  12. data/lib/rubocop/cop/rspec/be_eql.rb +2 -0
  13. data/lib/rubocop/cop/rspec/before_after_all.rb +6 -3
  14. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +7 -2
  15. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +6 -2
  16. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +4 -0
  17. data/lib/rubocop/cop/rspec/context_method.rb +1 -0
  18. data/lib/rubocop/cop/rspec/context_wording.rb +7 -1
  19. data/lib/rubocop/cop/rspec/describe_class.rb +5 -2
  20. data/lib/rubocop/cop/rspec/describe_method.rb +3 -2
  21. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -0
  22. data/lib/rubocop/cop/rspec/described_class.rb +6 -2
  23. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +3 -3
  24. data/lib/rubocop/cop/rspec/dialect.rb +2 -1
  25. data/lib/rubocop/cop/rspec/empty_example_group.rb +6 -45
  26. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -1
  27. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  28. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  29. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +1 -1
  30. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -1
  31. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +2 -2
  32. data/lib/rubocop/cop/rspec/example_length.rb +26 -12
  33. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -0
  34. data/lib/rubocop/cop/rspec/example_wording.rb +1 -0
  35. data/lib/rubocop/cop/rspec/expect_actual.rb +3 -1
  36. data/lib/rubocop/cop/rspec/expect_change.rb +3 -0
  37. data/lib/rubocop/cop/rspec/expect_in_hook.rb +2 -1
  38. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  39. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +3 -0
  40. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +4 -0
  41. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +2 -0
  42. data/lib/rubocop/cop/rspec/file_path.rb +26 -17
  43. data/lib/rubocop/cop/rspec/focus.rb +46 -8
  44. data/lib/rubocop/cop/rspec/hook_argument.rb +4 -4
  45. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -2
  46. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +4 -0
  47. data/lib/rubocop/cop/rspec/implicit_expect.rb +2 -1
  48. data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -0
  49. data/lib/rubocop/cop/rspec/instance_spy.rb +3 -1
  50. data/lib/rubocop/cop/rspec/instance_variable.rb +5 -1
  51. data/lib/rubocop/cop/rspec/it_behaves_like.rb +3 -1
  52. data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -1
  53. data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -2
  54. data/lib/rubocop/cop/rspec/let_setup.rb +10 -4
  55. data/lib/rubocop/cop/rspec/message_chain.rb +4 -10
  56. data/lib/rubocop/cop/rspec/message_expectation.rb +3 -0
  57. data/lib/rubocop/cop/rspec/message_spies.rb +5 -3
  58. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  59. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +51 -0
  60. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  61. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  62. data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
  63. data/lib/rubocop/cop/rspec/multiple_describes.rb +2 -3
  64. data/lib/rubocop/cop/rspec/multiple_expectations.rb +4 -1
  65. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +3 -1
  66. data/lib/rubocop/cop/rspec/named_subject.rb +11 -12
  67. data/lib/rubocop/cop/rspec/nested_groups.rb +1 -1
  68. data/lib/rubocop/cop/rspec/not_to_not.rb +2 -0
  69. data/lib/rubocop/cop/rspec/overwriting_setup.rb +4 -1
  70. data/lib/rubocop/cop/rspec/pending.rb +17 -5
  71. data/lib/rubocop/cop/rspec/predicate_matcher.rb +8 -3
  72. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  73. data/lib/rubocop/cop/rspec/receive_counts.rb +4 -0
  74. data/lib/rubocop/cop/rspec/receive_never.rb +2 -0
  75. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +8 -1
  76. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +4 -0
  77. data/lib/rubocop/cop/rspec/repeated_include_example.rb +6 -2
  78. data/lib/rubocop/cop/rspec/return_from_stub.rb +6 -0
  79. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  80. data/lib/rubocop/cop/rspec/shared_context.rb +22 -11
  81. data/lib/rubocop/cop/rspec/shared_examples.rb +4 -1
  82. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +4 -1
  83. data/lib/rubocop/cop/rspec/stubbed_mock.rb +2 -1
  84. data/lib/rubocop/cop/rspec/subject_stub.rb +17 -6
  85. data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -0
  86. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
  87. data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
  88. data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -0
  89. data/lib/rubocop/cop/rspec/void_expect.rb +3 -0
  90. data/lib/rubocop/cop/rspec/yield.rb +3 -0
  91. data/lib/rubocop/cop/rspec_cops.rb +0 -1
  92. data/lib/rubocop/rspec/align_let_brace.rb +1 -1
  93. data/lib/rubocop/rspec/concept.rb +2 -2
  94. data/lib/rubocop/rspec/config_formatter.rb +5 -3
  95. data/lib/rubocop/rspec/corrector/move_node.rb +7 -10
  96. data/lib/rubocop/rspec/example.rb +5 -0
  97. data/lib/rubocop/rspec/example_group.rb +15 -5
  98. data/lib/rubocop/rspec/hook.rb +2 -1
  99. data/lib/rubocop/rspec/inject.rb +4 -2
  100. data/lib/rubocop/rspec/language.rb +159 -115
  101. data/lib/rubocop/rspec/language/node_pattern.rb +7 -24
  102. data/lib/rubocop/rspec/node.rb +1 -1
  103. data/lib/rubocop/rspec/version.rb +1 -1
  104. metadata +27 -16
  105. data/lib/rubocop/cop/rspec/cop.rb +0 -10
  106. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +0 -41
  107. data/lib/rubocop/rspec.rb +0 -12
  108. data/lib/rubocop/rspec/empty_line_separation.rb +0 -48
  109. data/lib/rubocop/rspec/final_end_location.rb +0 -17
  110. data/lib/rubocop/rspec/top_level_describe.rb +0 -52
  111. data/lib/rubocop/rspec/top_level_group.rb +0 -57
  112. data/lib/rubocop/rspec/variable.rb +0 -16
@@ -18,12 +18,12 @@ module RuboCop
18
18
  # # ...
19
19
  # end
20
20
  #
21
- # @see https://github.com/rubocop-hq/rubocop-rspec/issues/735
21
+ # @see https://github.com/rubocop/rubocop-rspec/issues/735
22
22
  class DescribedClassModuleWrapping < Base
23
23
  MSG = 'Avoid opening modules and defining specs within them.'
24
24
 
25
- def_node_search :find_rspec_blocks,
26
- ExampleGroups::ALL.block_pattern
25
+ # @!method find_rspec_blocks(node)
26
+ def_node_search :find_rspec_blocks, block_pattern('#ExampleGroups.all')
27
27
 
28
28
  def on_module(node)
29
29
  find_rspec_blocks(node) do
@@ -47,7 +47,8 @@ module RuboCop
47
47
 
48
48
  MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
49
49
 
50
- def_node_matcher :rspec_method?, ALL.send_pattern
50
+ # @!method rspec_method?(node)
51
+ def_node_matcher :rspec_method?, send_pattern('#ALL.all')
51
52
 
52
53
  def on_send(node)
53
54
  return unless rspec_method?(node)
@@ -5,8 +5,6 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks if an example group does not include any tests.
7
7
  #
8
- # This cop is configurable using the `CustomIncludeMethods` option
9
- #
10
8
  # @example usage
11
9
  #
12
10
  # # bad
@@ -37,31 +35,6 @@ module RuboCop
37
35
  # describe Bacon do
38
36
  # pending 'will add tests later'
39
37
  # end
40
- #
41
- # @example configuration
42
- #
43
- # # .rubocop.yml
44
- # # RSpec/EmptyExampleGroup:
45
- # # CustomIncludeMethods:
46
- # # - include_tests
47
- #
48
- # # spec_helper.rb
49
- # RSpec.configure do |config|
50
- # config.alias_it_behaves_like_to(:include_tests)
51
- # end
52
- #
53
- # # bacon_spec.rb
54
- # describe Bacon do
55
- # let(:bacon) { Bacon.new(chunkiness) }
56
- # let(:chunkiness) { false }
57
- #
58
- # context 'extra chunky' do # not flagged by rubocop
59
- # let(:chunkiness) { true }
60
- #
61
- # include_tests 'shared tests'
62
- # end
63
- # end
64
- #
65
38
  class EmptyExampleGroup < Base
66
39
  MSG = 'Empty example group detected.'
67
40
 
@@ -76,7 +49,7 @@ module RuboCop
76
49
  # @param node [RuboCop::AST::Node]
77
50
  # @yield [RuboCop::AST::Node] example group body
78
51
  def_node_matcher :example_group_body, <<~PATTERN
79
- (block #{ExampleGroups::ALL.send_pattern} args $_)
52
+ (block #{send_pattern('#ExampleGroups.all')} args $_)
80
53
  PATTERN
81
54
 
82
55
  # @!method example_or_group_or_include?(node)
@@ -95,12 +68,10 @@ module RuboCop
95
68
  # @return [Array<RuboCop::AST::Node>] matching nodes
96
69
  def_node_matcher :example_or_group_or_include?, <<~PATTERN
97
70
  {
98
- #{Examples::ALL.send_pattern}
99
- #{Examples::ALL.block_pattern}
100
- #{ExampleGroups::ALL.block_pattern}
101
- #{Includes::ALL.send_pattern}
102
- #{Includes::ALL.block_pattern}
103
- (send nil? #custom_include? ...)
71
+ #{block_pattern(
72
+ '{#Examples.all #ExampleGroups.all #Includes.all}'
73
+ )}
74
+ #{send_pattern('{#Examples.all #Includes.all}')}
104
75
  }
105
76
  PATTERN
106
77
 
@@ -120,7 +91,7 @@ module RuboCop
120
91
  # @param node [RuboCop::AST::Node]
121
92
  # @return [Array<RuboCop::AST::Node>] matching nodes
122
93
  def_node_matcher :examples_inside_block?, <<~PATTERN
123
- (block !#{Hooks::ALL.send_pattern} _ #examples?)
94
+ (block !#{send_pattern('#Hooks.all')} _ #examples?)
124
95
  PATTERN
125
96
 
126
97
  # @!method examples_directly_or_in_block?(node)
@@ -192,16 +163,6 @@ module RuboCop
192
163
  def examples_in_branches?(if_node)
193
164
  if_node.branches.any? { |branch| examples?(branch) }
194
165
  end
195
-
196
- def custom_include?(method_name)
197
- custom_include_methods.include?(method_name)
198
- end
199
-
200
- def custom_include_methods
201
- cop_config
202
- .fetch('CustomIncludeMethods', [])
203
- .map(&:to_sym)
204
- end
205
166
  end
206
167
  end
207
168
  end
@@ -28,8 +28,9 @@ module RuboCop
28
28
 
29
29
  MSG = 'Empty hook detected.'
30
30
 
31
+ # @!method empty_hook?(node)
31
32
  def_node_matcher :empty_hook?, <<~PATTERN
32
- (block $#{Hooks::ALL.send_pattern} _ nil?)
33
+ (block $#{send_pattern('#Hooks.all')} _ nil?)
33
34
  PATTERN
34
35
 
35
36
  def on_block(node)
@@ -43,7 +43,7 @@ module RuboCop
43
43
  #
44
44
  class EmptyLineAfterExample < Base
45
45
  extend AutoCorrector
46
- include RuboCop::RSpec::EmptyLineSeparation
46
+ include EmptyLineSeparation
47
47
 
48
48
  MSG = 'Add an empty line after `%<example>s`.'
49
49
 
@@ -25,7 +25,7 @@ module RuboCop
25
25
  #
26
26
  class EmptyLineAfterExampleGroup < Base
27
27
  extend AutoCorrector
28
- include RuboCop::RSpec::EmptyLineSeparation
28
+ include EmptyLineSeparation
29
29
 
30
30
  MSG = 'Add an empty line after `%<example_group>s`.'
31
31
 
@@ -18,7 +18,7 @@ module RuboCop
18
18
  # it { does_something }
19
19
  class EmptyLineAfterFinalLet < Base
20
20
  extend AutoCorrector
21
- include RuboCop::RSpec::EmptyLineSeparation
21
+ include EmptyLineSeparation
22
22
 
23
23
  MSG = 'Add an empty line after the last `%<let>s`.'
24
24
 
@@ -35,7 +35,7 @@ module RuboCop
35
35
  #
36
36
  class EmptyLineAfterHook < Base
37
37
  extend AutoCorrector
38
- include RuboCop::RSpec::EmptyLineSeparation
38
+ include EmptyLineSeparation
39
39
 
40
40
  MSG = 'Add an empty line after `%<hook>s`.'
41
41
 
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # let(:foo) { bar }
17
17
  class EmptyLineAfterSubject < Base
18
18
  extend AutoCorrector
19
- include RuboCop::RSpec::EmptyLineSeparation
19
+ include EmptyLineSeparation
20
20
 
21
21
  MSG = 'Add an empty line after `%<subject>s`.'
22
22
 
@@ -32,7 +32,7 @@ module RuboCop
32
32
 
33
33
  def in_spec_block?(node)
34
34
  node.each_ancestor(:block).any? do |ancestor|
35
- Examples::ALL.include?(ancestor.method_name)
35
+ Examples.all(ancestor.method_name)
36
36
  end
37
37
  end
38
38
  end
@@ -25,29 +25,43 @@ module RuboCop
25
25
  # result = service.call
26
26
  # expect(result).to be(true)
27
27
  # end
28
+ #
29
+ # You can set literals you want to fold with `CountAsOne`.
30
+ # Available are: 'array', 'hash', and 'heredoc'. Each literal
31
+ # will be counted as one line regardless of its actual size.
32
+ #
33
+ # @example CountAsOne: ['array', 'heredoc']
34
+ #
35
+ # it do
36
+ # array = [ # +1
37
+ # 1,
38
+ # 2
39
+ # ]
40
+ #
41
+ # hash = { # +3
42
+ # key: 'value'
43
+ # }
44
+ #
45
+ # msg = <<~HEREDOC # +1
46
+ # Heredoc
47
+ # content.
48
+ # HEREDOC
49
+ # end # 5 points
28
50
  class ExampleLength < Base
29
51
  include CodeLength
30
52
 
31
- MSG = 'Example has too many lines [%<total>d/%<max>d].'
53
+ LABEL = 'Example'
32
54
 
33
55
  def on_block(node)
34
56
  return unless example?(node)
35
57
 
36
- length = code_length(node)
37
-
38
- return unless length > max_length
39
-
40
- add_offense(node, message: message(length))
58
+ check_code_length(node)
41
59
  end
42
60
 
43
61
  private
44
62
 
45
- def code_length(node)
46
- node.source.lines[1..-2].count { |line| !irrelevant_line(line) }
47
- end
48
-
49
- def message(length)
50
- format(MSG, total: length, max: max_length)
63
+ def cop_label
64
+ LABEL
51
65
  end
52
66
  end
53
67
  end
@@ -54,6 +54,7 @@ module RuboCop
54
54
  'have auto-generated description.'
55
55
  MSG_ADD_DESCRIPTION = 'Add a description.'
56
56
 
57
+ # @!method example_description(node)
57
58
  def_node_matcher :example_description, '(send nil? _ $(str $_))'
58
59
 
59
60
  def on_block(node)
@@ -38,6 +38,7 @@ module RuboCop
38
38
  SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
39
39
  IT_PREFIX = /\Ait /i.freeze
40
40
 
41
+ # @!method it_description(node)
41
42
  def_node_matcher :it_description, <<-PATTERN
42
43
  (block (send _ :it ${
43
44
  (str $_)
@@ -45,10 +45,11 @@ module RuboCop
45
45
 
46
46
  SUPPORTED_MATCHERS = %i[eq eql equal be].freeze
47
47
 
48
+ # @!method expect_literal(node)
48
49
  def_node_matcher :expect_literal, <<~PATTERN
49
50
  (send
50
51
  (send nil? :expect $#literal?)
51
- #{Runners::ALL.node_pattern_union}
52
+ #Runners.all
52
53
  {
53
54
  (send (send nil? $:be) :== $_)
54
55
  (send nil? $_ $_ ...)
@@ -60,6 +61,7 @@ module RuboCop
60
61
  expect_literal(node) do |actual, matcher, expected|
61
62
  add_offense(actual.source_range) do |corrector|
62
63
  next unless SUPPORTED_MATCHERS.include?(matcher)
64
+ next if literal?(expected)
63
65
 
64
66
  swap(corrector, actual, expected)
65
67
  end
@@ -35,11 +35,14 @@ module RuboCop
35
35
 
36
36
  MSG_BLOCK = 'Prefer `change(%<obj>s, :%<attr>s)`.'
37
37
  MSG_CALL = 'Prefer `change { %<obj>s.%<attr>s }`.'
38
+ RESTRICT_ON_SEND = %i[change].freeze
38
39
 
40
+ # @!method expect_change_with_arguments(node)
39
41
  def_node_matcher :expect_change_with_arguments, <<-PATTERN
40
42
  (send nil? :change ({const send} nil? $_) (sym $_))
41
43
  PATTERN
42
44
 
45
+ # @!method expect_change_with_block(node)
43
46
  def_node_matcher :expect_change_with_block, <<-PATTERN
44
47
  (block
45
48
  (send nil? :change)
@@ -23,7 +23,8 @@ 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
+ # @!method expectation(node)
27
+ def_node_search :expectation, send_pattern('#Expectations.all')
27
28
 
28
29
  def on_block(node)
29
30
  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)
@@ -29,10 +29,12 @@ module RuboCop
29
29
 
30
30
  MSG = 'Use a block to declare attribute values.'
31
31
 
32
+ # @!method value_matcher(node)
32
33
  def_node_matcher :value_matcher, <<-PATTERN
33
34
  (send _ !#reserved_method? $...)
34
35
  PATTERN
35
36
 
37
+ # @!method factory_attributes(node)
36
38
  def_node_matcher :factory_attributes, <<-PATTERN
37
39
  (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
38
40
  PATTERN
@@ -79,6 +81,7 @@ module RuboCop
79
81
  value_matcher(attribute).to_a.all?(&:block_pass_type?)
80
82
  end
81
83
 
84
+ # @!method association?(node)
82
85
  def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)'
83
86
 
84
87
  def autocorrect_replacing_parens(corrector, node)
@@ -30,7 +30,9 @@ module RuboCop
30
30
 
31
31
  MSG_CREATE_LIST = 'Prefer create_list.'
32
32
  MSG_N_TIMES = 'Prefer %<number>s.times.'
33
+ RESTRICT_ON_SEND = %i[create_list].freeze
33
34
 
35
+ # @!method n_times_block_without_arg?(node)
34
36
  def_node_matcher :n_times_block_without_arg?, <<-PATTERN
35
37
  (block
36
38
  (send (int _) :times)
@@ -39,10 +41,12 @@ module RuboCop
39
41
  )
40
42
  PATTERN
41
43
 
44
+ # @!method factory_call(node)
42
45
  def_node_matcher :factory_call, <<-PATTERN
43
46
  (send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create (sym $_) $...)
44
47
  PATTERN
45
48
 
49
+ # @!method factory_list_call(node)
46
50
  def_node_matcher :factory_list_call, <<-PATTERN
47
51
  (send {(const nil? {:FactoryGirl :FactoryBot}) nil?} :create_list (sym _) (int $_) ...)
48
52
  PATTERN
@@ -25,7 +25,9 @@ module RuboCop
25
25
  MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
26
26
  'constant.'
27
27
  ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
28
+ RESTRICT_ON_SEND = %i[factory].freeze
28
29
 
30
+ # @!method class_name(node)
29
31
  def_node_matcher :class_name, <<~PATTERN
30
32
  (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>))
31
33
  PATTERN
@@ -57,16 +57,18 @@ 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
 
64
+ # @!method const_described(node)
64
65
  def_node_matcher :const_described, <<~PATTERN
65
66
  (block
66
67
  $(send #rspec? _example_group $(const ...) $...) ...
67
68
  )
68
69
  PATTERN
69
70
 
71
+ # @!method routing_metadata?(node)
70
72
  def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))'
71
73
 
72
74
  def on_top_level_example_group(node)
@@ -82,30 +84,39 @@ module RuboCop
82
84
  private
83
85
 
84
86
  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))
87
+ pattern = pattern_for(described_class, arguments.first)
88
+ return if filename_ends_with?(pattern)
89
+
90
+ # For the suffix shown in the offense message, modify the regular
91
+ # expression pattern to resemble a glob pattern for clearer error
92
+ # messages.
93
+ offense_suffix = pattern.gsub('.*', '*').sub('[^/]', '')
94
+ .sub('\.', '.')
95
+ add_offense(send_node, message: format(MSG, suffix: offense_suffix))
89
96
  end
90
97
 
91
98
  def routing_spec?(args)
92
99
  args.any?(&method(:routing_metadata?))
93
100
  end
94
101
 
95
- def glob_for(described_class, method_name)
96
- return glob_for_spec_suffix_only? if spec_suffix_only?
102
+ def pattern_for(described_class, method_name)
103
+ return pattern_for_spec_suffix_only? if spec_suffix_only?
97
104
 
98
- "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
105
+ [
106
+ expected_path(described_class),
107
+ name_pattern(method_name),
108
+ '[^/]*_spec\.rb'
109
+ ].join
99
110
  end
100
111
 
101
- def glob_for_spec_suffix_only?
102
- '*_spec.rb'
112
+ def pattern_for_spec_suffix_only?
113
+ '.*_spec\.rb'
103
114
  end
104
115
 
105
- def name_glob(method_name)
116
+ def name_pattern(method_name)
106
117
  return unless method_name&.str_type?
107
118
 
108
- "*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
119
+ ".*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
109
120
  end
110
121
 
111
122
  def expected_path(constant)
@@ -131,11 +142,9 @@ module RuboCop
131
142
  cop_config['IgnoreMethods']
132
143
  end
133
144
 
134
- def filename_ends_with?(glob)
135
- filename =
136
- RuboCop::PathUtil.relative_path(processed_source.buffer.name)
137
- .gsub('../', '')
138
- File.fnmatch?("*#{glob}", filename)
145
+ def filename_ends_with?(pattern)
146
+ filename = File.expand_path(processed_source.buffer.name)
147
+ filename.match?("#{pattern}$")
139
148
  end
140
149
 
141
150
  def relevant_rubocop_rspec_file?(_file)