Skip to content

Commit

Permalink
Merge pull request rubocop#9987 from dvandersluis/internal/undefined-…
Browse files Browse the repository at this point in the history
…config

Add `InternalAffairs/UndefinedConfig` cop.
  • Loading branch information
koic committed Aug 10, 2021
2 parents 58c267f + 295f94d commit bb5d832
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .rubocop.yml
Expand Up @@ -140,3 +140,10 @@ RSpec/StubbedMock:
InternalAffairs/ExampleDescription:
Include:
- 'spec/rubocop/cop/**/*.rb'

InternalAffairs/UndefinedConfig:
Include:
- 'lib/rubocop/cop/**/*.rb'
Exclude:
- 'lib/rubocop/cop/correctors/**/*.rb'
- 'lib/rubocop/cop/mixin/**/*.rb'
3 changes: 2 additions & 1 deletion config/default.yml
Expand Up @@ -1824,7 +1824,6 @@ Lint/MultipleComparison:
Enabled: true
VersionAdded: '0.47'
VersionChanged: '1.1'
AllowMethodComparison: true

Lint/NestedMethodDefinition:
Description: 'Do not use nested method definitions.'
Expand Down Expand Up @@ -3931,6 +3930,7 @@ Style/MultipleComparison:
Enabled: true
VersionAdded: '0.49'
VersionChanged: '1.1'
AllowMethodComparison: true

Style/MutableConstant:
Description: 'Do not assign mutable objects to constants.'
Expand Down Expand Up @@ -4154,6 +4154,7 @@ Style/OptionHash:
- args
- params
- parameters
Allowlist: []

Style/OptionalArguments:
Description: >-
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/internal_affairs.rb
Expand Up @@ -13,4 +13,5 @@
require_relative 'internal_affairs/redundant_location_argument'
require_relative 'internal_affairs/redundant_message_argument'
require_relative 'internal_affairs/style_detected_api_use'
require_relative 'internal_affairs/undefined_config'
require_relative 'internal_affairs/useless_message_assertion'
71 changes: 71 additions & 0 deletions lib/rubocop/cop/internal_affairs/undefined_config.rb
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module RuboCop
module Cop
module InternalAffairs
# Looks for references to a cop configuration key that isn't defined in config/default.yml.
class UndefinedConfig < Base
ALLOWED_CONFIGURATIONS = %w[
Safe SafeAutoCorrect AutoCorrect Severity StyleGuide Details Reference Include Exclude
].freeze
RESTRICT_ON_SEND = %i[[] fetch].freeze
MSG = '`%<name>s` is not defined in the configuration for `%<cop>s` ' \
'in `config/default.yml`.'

# @!method cop_class_def(node)
def_node_search :cop_class_def, <<~PATTERN
(class _ (const _ {:Base :Cop}) ...)
PATTERN

# @!method cop_config_accessor?(node)
def_node_matcher :cop_config_accessor?, <<~PATTERN
(send (send nil? :cop_config) {:[] :fetch} ${str sym}...)
PATTERN

def on_new_investigation
super
return unless processed_source.ast

cop_class = cop_class_def(processed_source.ast).first
return unless (@cop_class_name = extract_cop_name(cop_class))

@config_for_cop = RuboCop::ConfigLoader.default_configuration.for_cop(@cop_class_name)
end

def on_send(node)
return unless cop_class_name
return unless (config_name_node = cop_config_accessor?(node))
return if always_allowed?(config_name_node)
return if configuration_key_defined?(config_name_node)

message = format(MSG, name: config_name_node.value, cop: cop_class_name)
add_offense(config_name_node, message: message)
end

private

attr_reader :config_for_cop, :cop_class_name

def extract_cop_name(class_node)
return unless class_node

segments = [class_node].concat(
class_node.each_ancestor(:class, :module).take_while do |n|
n.identifier.short_name != :Cop
end
)

segments.reverse_each.map { |s| s.identifier.short_name }.join('/')
end

def always_allowed?(node)
ALLOWED_CONFIGURATIONS.include?(node.value)
end

def configuration_key_defined?(node)
config_for_cop.key?(node.value)
end
end
end
end
end
234 changes: 234 additions & 0 deletions spec/rubocop/cop/internal_affairs/undefined_config_spec.rb
@@ -0,0 +1,234 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::InternalAffairs::UndefinedConfig, :config, :isolated_environment do
include FileHelper

before do
create_file('config/default.yml', <<~YAML)
Test/Foo:
Defined: true
X/Y/Z:
Defined: true
YAML

allow(RuboCop::ConfigLoader).to receive(:default_configuration).and_return(
RuboCop::ConfigLoader.load_file('config/default.yml', check: false)
)
end

it 'does not register an offense for implicit configuration keys' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config['Safe']
cop_config['SafeAutoCorrect']
cop_config['AutoCorrect']
cop_config['Severity']
end
end
end
end
end
RUBY
end

it 'registers an offense when the cop has no configuration at all' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Test
class Bar < Base
def configured?
cop_config['Missing']
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Bar` in `config/default.yml`.
end
end
end
end
end
RUBY
end

it 'registers an offense when the cop is not within the `RuboCop::Cop` namespace' do
expect_offense(<<~RUBY)
module Test
class Foo < Base
def configured?
cop_config['Defined']
cop_config['Missing']
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Foo` in `config/default.yml`.
end
end
end
RUBY
end

context 'element lookup' do
it 'does not register an offense for defined configuration keys' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config['Defined']
end
end
end
end
end
RUBY
end

it 'registers an offense for missing configuration keys' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config['Missing']
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Foo` in `config/default.yml`.
end
end
end
end
end
RUBY
end
end

context 'fetch' do
it 'does not register an offense for defined configuration keys' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config.fetch('Defined')
end
end
end
end
end
RUBY
end

it 'registers an offense for missing configuration keys' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config.fetch('Missing')
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Foo` in `config/default.yml`.
end
end
end
end
end
RUBY
end

context 'with a default value' do
it 'does not register an offense for defined configuration keys' do
expect_no_offenses(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config.fetch('Defined', default)
end
end
end
end
end
RUBY
end

it 'registers an offense for missing configuration keys' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Base
def configured?
cop_config.fetch('Missing', default)
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Foo` in `config/default.yml`.
end
end
end
end
end
RUBY
end
end
end

it 'works with deeper nested cop names' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module X
module Y
class Z < Base
def configured?
cop_config['Defined']
cop_config['Missing']
^^^^^^^^^ `Missing` is not defined in the configuration for `X/Y/Z` in `config/default.yml`.
end
end
end
end
end
end
RUBY
end

it 'works when the base class is `Cop` instead of `Base`' do
expect_offense(<<~RUBY)
module RuboCop
module Cop
module Test
class Foo < Cop
def configured?
cop_config['Defined']
cop_config['Missing']
^^^^^^^^^ `Missing` is not defined in the configuration for `Test/Foo` in `config/default.yml`.
end
end
end
end
end
RUBY
end

it 'ignores `cop_config` in non-cop classes' do
expect_no_offenses(<<~RUBY)
class Test
def configured?
cop_config['Missing']
end
end
RUBY
end

it 'does not register an offense if using `cop_config` outside of a cop class' do
expect_no_offenses(<<~RUBY)
def configured?
cop_config['Missing']
end
RUBY
end

it 'can handle an empty file' do
expect_no_offenses('')
end
end

0 comments on commit bb5d832

Please sign in to comment.