diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c69da08..70a7e9018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Add support for correcting "it will" (future tense) for `RSpec/ExampleWording`. ([@jdufresne]) - Add support for `symbol` style for `RSpec/SharedExamples`. ([@jessieay]) - Ensure `PendingWithoutReason` can detect violations inside shared groups. ([@robinaugh]) +- Add configuration option `CustomTransformPatterns` to `RSpec/SpecFilePathFormat`. ([@ydah]) ## 2.25.0 (2023-10-27) diff --git a/config/default.yml b/config/default.yml index e60e75ffd..a2cbf053a 100644 --- a/config/default.yml +++ b/config/default.yml @@ -887,12 +887,15 @@ RSpec/SpecFilePathFormat: Exclude: - "**/spec/routing/**/*" CustomTransform: - RuboCop: rubocop RSpec: rspec + RuboCop: rubocop + CustomTransformPatterns: + API: api IgnoreMethods: false IgnoreMetadata: type: routing VersionAdded: '2.24' + VersionChanged: "<>" Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathFormat RSpec/SpecFilePathSuffix: diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index b8c9c1a1d..51bf868dc 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -5270,7 +5270,7 @@ it 'works', :a, :b, baz: true, foo: 'bar' | Yes | No | 2.24 -| - +| <> |=== Checks that spec file paths are consistent and well-formed. @@ -5298,6 +5298,15 @@ rubocop_spec.rb # describe RuboCop rspec_spec.rb # describe RSpec ---- +==== `CustomTransformPatterns: {API=>api}` (default) + +[source,ruby] +---- +# good +apis_spec.rb # describe APIs +foo_apis_spec.rb # describe Foo::APIs +---- + ==== `IgnoreMethods: false` (default) [source,ruby] @@ -5336,7 +5345,11 @@ whatever_spec.rb # describe MyClass, type: :routing do; end | Array | CustomTransform -| `{"RuboCop"=>"rubocop", "RSpec"=>"rspec"}` +| `{"RSpec"=>"rspec", "RuboCop"=>"rubocop"}` +| + +| CustomTransformPatterns +| `{"API"=>"api"}` | | IgnoreMethods diff --git a/lib/rubocop/cop/rspec/spec_file_path_format.rb b/lib/rubocop/cop/rspec/spec_file_path_format.rb index 21466c064..2ef9edb1a 100644 --- a/lib/rubocop/cop/rspec/spec_file_path_format.rb +++ b/lib/rubocop/cop/rspec/spec_file_path_format.rb @@ -20,6 +20,11 @@ module RSpec # rubocop_spec.rb # describe RuboCop # rspec_spec.rb # describe RSpec # + # @example `CustomTransformPatterns: {API=>api}` (default) + # # good + # apis_spec.rb # describe APIs + # foo_apis_spec.rb # describe Foo::APIs + # # @example `IgnoreMethods: false` (default) # # bad # my_class_spec.rb # describe MyClass, '#method' @@ -100,11 +105,19 @@ def expected_path(constant) File.join( constants.map do |name| + pattern, transformed = transformed_pattern(name) + name.gsub!(pattern, transformed.capitalize) if transformed custom_transform.fetch(name) { camel_to_snake_case(name) } end ) end + def transformed_pattern(name) + custom_transform_patterns.find do |pattern, _| + name.include?(pattern) + end + end + def camel_to_snake_case(string) string .gsub(/([^A-Z])([A-Z]+)/, '\1_\2') @@ -116,6 +129,10 @@ def custom_transform cop_config.fetch('CustomTransform', {}) end + def custom_transform_patterns + cop_config.fetch('CustomTransformPatterns', {}) + end + def ignore_methods? cop_config['IgnoreMethods'] end diff --git a/spec/rubocop/cop/rspec/spec_file_path_format_spec.rb b/spec/rubocop/cop/rspec/spec_file_path_format_spec.rb index 6104595bc..83a4f8d52 100644 --- a/spec/rubocop/cop/rspec/spec_file_path_format_spec.rb +++ b/spec/rubocop/cop/rspec/spec_file_path_format_spec.rb @@ -233,19 +233,66 @@ class Foo RUBY end - context 'when configured with `CustomTransform: { "FooFoo" => "foofoo" }`' do - let(:cop_config) { { 'CustomTransform' => { 'FooFoo' => 'foofoo' } } } + context 'when configured with `CustomTransform: { "RSpec" => "rspec" }`' do + let(:cop_config) do + { + 'CustomTransform' => { 'RSpec' => 'rspec' }, + 'CustomTransformPatterns' => {} + } + end + + it 'registers an offense when not an exact match to custom ' \ + 'module name transformation' do + expect_offense(<<-RUBY, 'rspec_foo_spec.rb') + RSpec.describe RSpecFoo do; end + ^^^^^^^^^^^^^^^^^^^^^^^ Spec path should end with `r_spec_foo*_spec.rb`. + RUBY + end + + it 'registers an offense when not an exact match to custom ' \ + 'module name transformation with nested directories' do + expect_offense(<<-RUBY, 'foo/some/rspec_foo/bar_spec.rb') + describe Foo::Some::RSpecFoo, '#bar' do; end + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Spec path should end with `foo/some/r_spec_foo*bar*_spec.rb`. + RUBY + end + + it 'does not register an offense when an exact match to custom ' \ + 'module name transformation' do + expect_no_offenses(<<-RUBY, 'rspec/some/foo/bar_spec.rb') + describe RSpec::Some::Foo, '#bar' do; end + RUBY + end + end + + context 'when configured with `CustomTransformPatterns: ' \ + '{ "API" => "api" }`' do + let(:cop_config) { { 'CustomTransformPatterns' => { 'API' => 'api' } } } + + it 'does not register an offense when not an exact match to custom ' \ + 'module name transformation' do + expect_no_offenses(<<-RUBY, 'foo/some/apis_foo/bar_spec.rb') + describe Foo::Some::APIsFoo, '#bar' do; end + RUBY + end + + it 'does not register an offense when an exact match to custom ' \ + 'module name transformation' do + expect_no_offenses(<<-RUBY, 'foo_apis_spec.rb') + RSpec.describe FooAPIs do; end + RUBY + end - it 'does not register an offense for custom module name transformation' do - expect_no_offenses(<<~RUBY, 'foofoo/some/class/bar_spec.rb') - describe FooFoo::Some::Class, '#bar' do; end + it 'does not register an offense when an exact match to custom ' \ + 'module name transformation with nested directories' do + expect_no_offenses(<<-RUBY, 'apis/some/foo/bar_spec.rb') + describe APIs::Some::Foo, '#bar' do; end RUBY end end context 'when configured with `IgnoreMethods: false`' do let(:cop_config) { { 'IgnoreMethods' => false } } - let(:suffix) { 'my_class*look_here_a_method*_spec.rb' } it 'registers an offense when file path not include method name' do expect_offense(<<~RUBY, 'my_class_spec.rb')