Skip to content

Commit 5bb9847

Browse files
authored
Merge pull request #2176 from rubocop/issue-2167-spec-file-path-format
Fix false negatives for `RSpec/SpecFilePathFormat` when the expected class path only partially matches a path segment
2 parents b8af4aa + e7b6e4c commit 5bb9847

3 files changed

Lines changed: 55 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Fix a false negative for `RSpec/EmptyLineAfterFinalLet` inside `shared_examples` / `include_examples` / `it_behaves_like` blocks. ([@Darhazer])
99
- Fix a false positive for `RSpec/ContainExactly` when `contain_exactly` has multiple splat arguments. ([@ydah])
1010
- Add autocorrect support for `RSpec/SubjectDeclaration`. ([@eugeneius])
11+
- Fix false negatives for `RSpec/SpecFilePathFormat` when the expected class path only partially matches a path segment. ([@ydah])
1112

1213
## 3.9.0 (2026-01-07)
1314

lib/rubocop/cop/rspec/spec_file_path_format.rb

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class SpecFilePathFormat < Base
4444
include FileHelp
4545

4646
MSG = 'Spec path should end with `%<suffix>s`.'
47+
PATH_NAME_BOUNDARY = '(?![[:alnum:]])'
4748

4849
# @!method example_group_arguments(node)
4950
def_node_matcher :example_group_arguments, <<~PATTERN
@@ -119,7 +120,11 @@ def ensure_correct_file_path(send_node, class_name, arguments)
119120
# For the suffix shown in the offense message, modify the regular
120121
# expression pattern to resemble a glob pattern for clearer error
121122
# messages.
122-
suffix = pattern.sub('.*', '*').sub('[^/]*', '*').sub('\.', '.')
123+
suffix = pattern
124+
.sub(PATH_NAME_BOUNDARY, '')
125+
.sub('.*', '*')
126+
.sub('[^/]*', '*')
127+
.sub('\.', '.')
123128
add_offense(send_node, message: format(MSG, suffix: suffix))
124129
end
125130

@@ -132,19 +137,21 @@ def ignore_metadata?(arguments)
132137
end
133138

134139
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
139146
end
140147

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)
143150

144-
method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')
151+
".*#{method_name.str_content.gsub(/\s/, '_').gsub(/\W/, '')}"
145152
end
146153

147-
def ignore?(method_name)
154+
def ignore_method_name?(method_name)
148155
!method_name&.str_type? || ignore_methods?
149156
end
150157

@@ -175,7 +182,7 @@ def ignore_metadata
175182
end
176183

177184
def filename_ends_with?(pattern)
178-
expanded_file_path.match?("#{pattern}$")
185+
expanded_file_path.match?(%r{(?:\A|/)#{pattern}$})
179186
end
180187
end
181188
end

spec/rubocop/cop/rspec/spec_file_path_format_spec.rb

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@
3636
RUBY
3737
end
3838

39+
it 'registers an offense when the class name is only a file name prefix' do
40+
expect_offense(<<~RUBY, 'spec/models/random/userx_spec.rb')
41+
RSpec.describe User, type: :model do; end
42+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Spec path should end with `user*_spec.rb`.
43+
RUBY
44+
end
45+
46+
it 'registers an offense when the class name is only a file name suffix' do
47+
expect_offense(<<~RUBY, 'spec/models/my_user_spec.rb')
48+
describe User do; end
49+
^^^^^^^^^^^^^ Spec path should end with `user*_spec.rb`.
50+
RUBY
51+
end
52+
53+
it 'registers an offense when namespaced class name is only a ' \
54+
'file name prefix' do
55+
expect_offense(<<~RUBY, 'some/classy_spec.rb')
56+
describe Some::Class do; end
57+
^^^^^^^^^^^^^^^^^^^^ Spec path should end with `some/class*_spec.rb`.
58+
RUBY
59+
end
60+
3961
it 'registers an offense when wrong top-level class name' do
4062
expect_offense(<<~RUBY, 'wrong_class_spec.rb')
4163
describe ::MyClass do; end
@@ -113,12 +135,26 @@
113135
RUBY
114136
end
115137

138+
it 'does not register an offense when the class name has a suffix' do
139+
expect_no_offenses(<<~RUBY, 'user_validations_spec.rb')
140+
describe User do; end
141+
RUBY
142+
end
143+
116144
it 'does not register an offense when instance methods' do
117145
expect_no_offenses(<<~RUBY, 'some/class/inst_spec.rb')
118146
describe Some::Class, '#inst' do; end
119147
RUBY
120148
end
121149

150+
it 'registers an offense when the method path uses only a class name ' \
151+
'prefix' do
152+
expect_offense(<<~RUBY, 'some/classx/inst_spec.rb')
153+
describe Some::Class, '#inst' do; end
154+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Spec path should end with `some/class*inst*_spec.rb`.
155+
RUBY
156+
end
157+
122158
it 'does not register an offense when class methods' do
123159
expect_no_offenses(<<~RUBY, 'some/class/inst_spec.rb')
124160
describe Some::Class, '.inst' do; end
@@ -402,7 +438,7 @@ def reset_activesupport_cache!
402438
allow(described_class::ActiveSupportInflector).to receive(:require)
403439
.with(cop_config['InflectorPath'])
404440
allow(ActiveSupport::Inflector).to receive(:underscore)
405-
.and_return('')
441+
.with('HTTPClient').and_return('http_client')
406442
end
407443

408444
it 'reads the InflectorPath configuration correctly and does not ' \

0 commit comments

Comments
 (0)