Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Lint/ConstantResolution cop (#8142)
Ruby constant resolution sometimes gets things wrong. I think generally gems (that are meant for including as a library, not like rubocop itself) should fully qualify all constants to avoid stepping on any toes inadvertently. I would run this on my gems without using `Only`/`Ignore` Large projects will probably end up with one or two constant names that are problematic because of a conflict with a library or just internally having sensible names for directories that end up being used by e.g. rails as namespaces in multiple different places. I would run these with `Only: [The, Constant, Names, Causing Issues]` Sometimes a method refers to a constant that you want to refer to a different constant at different calls, e.g. the method is defined on a superclass and the constant differs between subclasses. (e.g. MSG in rubocop add_offense). I would use `Ignore: [The, Intentionally, Ambiguous, Constant]` in these cases. (or `self.class::MSG` now that I've looked up how add_offense works) I've left this `Enabled: false` because I'm 50-50 on whether it should be Enabled by default. I didn't bother creating an autocorrect because I see there being two options and thought I'd get feedback. 1. carelessly assume the written constant is fully qualified and just missing a ::` prefix. This has the advantage of probably looking correct more often, but also being wrong a lot. 2. look at the current namespace and prefix that. This has the advantage of not necessarily changing behaviour for classes. For modules leaving it unchanged or carelessly assuming, or checking if the constant is defined in the module or not before prefixing 3. just don't autocorrect
- Loading branch information
Showing
7 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Lint | ||
# Check that certain constants are fully qualified. | ||
# | ||
# This is not enabled by default because it would mark a lot of offenses | ||
# unnecessarily. | ||
# | ||
# Generally, gems should fully qualify all constants to avoid conflicts with | ||
# the code that uses the gem. Enable this cop without using `Only`/`Ignore` | ||
# | ||
# Large projects will over time end up with one or two constant names that | ||
# are problematic because of a conflict with a library or just internally | ||
# using the same name a namespace and a class. To avoid too many unnecessary | ||
# offenses, Enable this cop with `Only: [The, Constant, Names, Causing, Issues]` | ||
# | ||
# @example | ||
# # By default checks every constant | ||
# | ||
# # bad | ||
# User | ||
# | ||
# # bad | ||
# User::Login | ||
# | ||
# # good | ||
# ::User | ||
# | ||
# # good | ||
# ::User::Login | ||
# | ||
# @example Only: ['Login'] | ||
# # Restrict this cop to only being concerned about certain constants | ||
# | ||
# # bad | ||
# Login | ||
# | ||
# # good | ||
# ::Login | ||
# | ||
# # good | ||
# User::Login | ||
# | ||
# @example Ignore: ['Login'] | ||
# # Restrict this cop not being concerned about certain constants | ||
# | ||
# # bad | ||
# User | ||
# | ||
# # good | ||
# ::User::Login | ||
# | ||
# # good | ||
# Login | ||
# | ||
class ConstantResolution < Cop | ||
MSG = 'Fully qualify this constant to avoid possibly ambiguous resolution.' | ||
|
||
def_node_matcher :unqualified_const?, <<~PATTERN | ||
(const nil? #const_name?) | ||
PATTERN | ||
|
||
def on_const(node) | ||
return unless unqualified_const?(node) | ||
|
||
add_offense(node) | ||
end | ||
|
||
private | ||
|
||
def const_name?(name) | ||
name = name.to_s | ||
(allowed_names.empty? || allowed_names.include?(name)) && | ||
!ignored_names.include?(name) | ||
end | ||
|
||
def allowed_names | ||
cop_config['Only'] | ||
end | ||
|
||
def ignored_names | ||
cop_config['Ignore'] | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Lint::ConstantResolution, :config do | ||
it 'registers no offense when qualifying a const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst | ||
RUBY | ||
end | ||
|
||
it 'registers no offense qualifying a namespace const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst::MY_CONST | ||
RUBY | ||
end | ||
|
||
it 'registers an offense not qualifying a const' do | ||
expect_offense(<<~RUBY) | ||
MyConst | ||
^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense not qualifying a namespace const' do | ||
expect_offense(<<~RUBY) | ||
MyConst::MY_CONST | ||
^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
|
||
context 'with Only set' do | ||
let(:cop_config) { { 'Only' => ['MY_CONST'] } } | ||
|
||
it 'registers no offense when qualifying a const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst | ||
RUBY | ||
end | ||
|
||
it 'registers no offense qualifying a namespace const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst::MY_CONST | ||
RUBY | ||
end | ||
|
||
it 'registers no offense not qualifying another const' do | ||
expect_no_offenses(<<~RUBY) | ||
MyConst | ||
RUBY | ||
end | ||
|
||
it 'registers no with a namespace const' do | ||
expect_no_offenses(<<~RUBY) | ||
MyConst::MY_CONST | ||
RUBY | ||
end | ||
|
||
it 'registers an offense with an unqualified const' do | ||
expect_offense(<<~RUBY) | ||
MY_CONST | ||
^^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when an unqualified namespace const' do | ||
expect_offense(<<~RUBY) | ||
MY_CONST::B | ||
^^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
end | ||
|
||
context 'with Ignore set' do | ||
let(:cop_config) { { 'Ignore' => ['MY_CONST'] } } | ||
|
||
it 'registers no offense when qualifying a const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst | ||
RUBY | ||
end | ||
|
||
it 'registers no offense qualifying a namespace const' do | ||
expect_no_offenses(<<~RUBY) | ||
::MyConst::MY_CONST | ||
RUBY | ||
end | ||
|
||
it 'registers an offense not qualifying another const' do | ||
expect_offense(<<~RUBY) | ||
MyConst | ||
^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
|
||
it 'registers an with a namespace const' do | ||
expect_offense(<<~RUBY) | ||
MyConst::MY_CONST | ||
^^^^^^^ Fully qualify this constant to avoid possibly ambiguous resolution. | ||
RUBY | ||
end | ||
|
||
it 'registers no offense with an unqualified const' do | ||
expect_no_offenses(<<~RUBY) | ||
MY_CONST | ||
RUBY | ||
end | ||
|
||
it 'registers no offense when an unqualified namespace const' do | ||
expect_no_offenses(<<~RUBY) | ||
MY_CONST::B | ||
RUBY | ||
end | ||
end | ||
end |