diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bdc941f..154ddca0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Fix a false negative for constants with constant base in `RSpec/DescribedClass`. ([@lovro-bikic]) + ## 3.8.0 (2025-11-12) - Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic]) diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index c35d1c495..9e5bdba75 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -1031,6 +1031,11 @@ end describe MyClass do subject { MyClass::CONSTANT } end + +# good +describe MyClass do + subject { described_class::CONSTANT } +end ---- [#_enforcedstyle_-explicit_-rspecdescribedclass] diff --git a/lib/rubocop/cop/rspec/described_class.rb b/lib/rubocop/cop/rspec/described_class.rb index 6558f64d5..dcef03010 100644 --- a/lib/rubocop/cop/rspec/described_class.rb +++ b/lib/rubocop/cop/rspec/described_class.rb @@ -50,6 +50,11 @@ module RSpec # subject { MyClass::CONSTANT } # end # + # # good + # describe MyClass do + # subject { described_class::CONSTANT } + # end + # # @example `EnforcedStyle: explicit` # # bad # describe MyClass do @@ -205,13 +210,15 @@ def full_const_name(node) # @return [Array] # @example # # nil represents base constant - # collapse_namespace([], [:C]) # => [:C] + # collapse_namespace([], [:C]) # => [nil, :C] # collapse_namespace([:A, :B], [:C]) # => [:A, :B, :C] # collapse_namespace([:A, :B], [:B, :C]) # => [:A, :B, :C] # collapse_namespace([:A, :B], [nil, :C]) # => [nil, :C] # collapse_namespace([:A, :B], [nil, :B, :C]) # => [nil, :B, :C] def collapse_namespace(namespace, const) - return const if namespace.empty? || const.first.nil? + return const if const.nil? + + namespace = [nil] if namespace.empty? start = [0, (namespace.length - const.length)].max max = namespace.length diff --git a/spec/rubocop/cop/rspec/described_class_spec.rb b/spec/rubocop/cop/rspec/described_class_spec.rb index e20fb3bfb..f273d6024 100644 --- a/spec/rubocop/cop/rspec/described_class_spec.rb +++ b/spec/rubocop/cop/rspec/described_class_spec.rb @@ -130,6 +130,56 @@ RUBY end + it 'flags when references use constant base and described class does not' do + expect_offense(<<~RUBY) + describe MyClass do + include ::MyClass + ^^^^^^^^^ Use `described_class` instead of `MyClass`. + + subject { ::MyClass.do_something } + ^^^^^^^^^ Use `described_class` instead of `MyClass`. + + before { ::MyClass.do_something } + ^^^^^^^^^ Use `described_class` instead of `MyClass`. + end + RUBY + + expect_correction(<<~RUBY) + describe MyClass do + include described_class + + subject { described_class.do_something } + + before { described_class.do_something } + end + RUBY + end + + it 'flags when described class uses constant base and references do not' do + expect_offense(<<~RUBY) + describe ::MyClass do + include MyClass + ^^^^^^^ Use `described_class` instead of `MyClass`. + + subject { MyClass.do_something } + ^^^^^^^ Use `described_class` instead of `MyClass`. + + before { MyClass.do_something } + ^^^^^^^ Use `described_class` instead of `MyClass`. + end + RUBY + + expect_correction(<<~RUBY) + describe ::MyClass do + include described_class + + subject { described_class.do_something } + + before { described_class.do_something } + end + RUBY + end + it 'allows accessing constants from variables when in a nested namespace' do expect_no_offenses(<<~RUBY) module Foo @@ -438,6 +488,16 @@ module SomeGem end RUBY end + + it 'ignores parenthesized namespaces within modules - begin_types' do + expect_no_offenses(<<~RUBY) + module Foo + describe MyClass do + subject { (MyNamespace)::MyClass } + end + end + RUBY + end end context 'when EnforcedStyle is :explicit' do