Skip to content

Commit

Permalink
[Fix rubocop#7850] Make it possible to enable/disable pending cops
Browse files Browse the repository at this point in the history
Resolves rubocop#7850.

This PR will add `--enable-pending-cops` and `--disable-pending-cops`
command-line options and `NewCops` will be provided in .rubocop.yml.

When `NewCops` is `enable`, pending cops are enabled in bulk.

```yaml
AllCops:
  NewCops: enable
```

Can be overridden by the `--enable-pending-cops` command-line option.

When `NewCops` is `disable`, pending cops are disabled in bulk.

```yaml
AllCops:
  NewCops: disable
```

Can be overridden by the `--disable-pending-cops` command-line option.

The default value of `NewCops` is `pending`.
  • Loading branch information
koic committed Apr 7, 2020
1 parent 28f18ac commit 544edbf
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Expand Up @@ -13,7 +13,7 @@ InternalAffairs/NodeDestructuring:
# Offense count: 50
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 183
Max: 186

# Offense count: 209
# Configuration parameters: CountComments, ExcludedMethods.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#7850](https://github.com/rubocop-hq/rubocop/issues/7850): Make it possible to enable/disable pending cops. ([@koic][])

### Bug fixes

* [#7842](https://github.com/rubocop-hq/rubocop/issues/7842): Fix a false positive for `Lint/RaiseException` when raising Exception with explicit namespace. ([@koic][])
Expand Down
8 changes: 8 additions & 0 deletions config/default.yml
Expand Up @@ -97,6 +97,14 @@ AllCops:
# to true in the same configuration.
EnabledByDefault: false
DisabledByDefault: false
# New cops introduced between major versions are set to a special pending status
# and are not enabled by default with warning message.
# Change this behavior by overriding either `NewCops: enable` or `NewCops: disable`.
# When `NewCops` is `enable`, pending cops are enabled in bulk. Can be overridden by
# the `--enable-pending-cops` command-line option.
# When `NewCops` is `disable`, pending cops are disabled in bulk. Can be overridden by
# the `--disable-pending-cops` command-line option.
NewCops: pending
# Enables the result cache if `true`. Can be overridden by the `--cache` command
# line option.
UseCache: true
Expand Down
14 changes: 10 additions & 4 deletions lib/rubocop/cli.rb
Expand Up @@ -83,10 +83,7 @@ def validate_options_vs_config
end

def act_on_options
ConfigLoader.debug = @options[:debug]
ConfigLoader.auto_gen_config = @options[:auto_gen_config]
ConfigLoader.ignore_parent_exclusion = @options[:ignore_parent_exclusion]
ConfigLoader.options_config = @options[:config]
set_options_to_config_loader

@config_store.options_config = @options[:config] if @options[:config]
@config_store.force_default_config! if @options[:force_default_config]
Expand All @@ -102,6 +99,15 @@ def act_on_options
end
end

def set_options_to_config_loader
ConfigLoader.debug = @options[:debug]
ConfigLoader.auto_gen_config = @options[:auto_gen_config]
ConfigLoader.disable_pending_cops = @options[:disable_pending_cops]
ConfigLoader.enable_pending_cops = @options[:enable_pending_cops]
ConfigLoader.ignore_parent_exclusion = @options[:ignore_parent_exclusion]
ConfigLoader.options_config = @options[:config]
end

def handle_exiting_options
return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }

Expand Down
8 changes: 8 additions & 0 deletions lib/rubocop/config.rb
Expand Up @@ -116,6 +116,14 @@ def for_all_cops
@for_all_cops ||= self['AllCops'] || {}
end

def disabled_new_cops?
for_all_cops['NewCops'] == 'disable'
end

def enabled_new_cops?
for_all_cops['NewCops'] == 'enable'
end

def file_to_include?(file)
relative_file_path = path_relative_to_config(file)

Expand Down
20 changes: 13 additions & 7 deletions lib/rubocop/config_loader.rb
Expand Up @@ -24,7 +24,7 @@ class << self
include FileFinder

attr_accessor :debug, :auto_gen_config, :ignore_parent_exclusion,
:options_config
:options_config, :disable_pending_cops, :enable_pending_cops
attr_writer :default_configuration

alias debug? debug
Expand Down Expand Up @@ -91,15 +91,22 @@ def configuration_from_file(config_file)
else
add_excludes_from_files(config, config_file)
end

merge_with_default(config, config_file).tap do |merged_config|
warn_on_pending_cops(merged_config.pending_cops)
unless possible_new_cops?(config)
warn_on_pending_cops(merged_config.pending_cops)
end
end
end

def possible_new_cops?(config)
disable_pending_cops || enable_pending_cops ||
config.disabled_new_cops? || config.enabled_new_cops?
end

def add_excludes_from_files(config, config_file)
found_files =
find_files_upwards(DOTFILE, config_file) +
[find_user_dotfile, find_user_xdg_config].compact
found_files = find_files_upwards(DOTFILE, config_file) +
[find_user_dotfile, find_user_xdg_config].compact

return if found_files.empty?
return if PathUtil.relative_path(found_files.last) ==
Expand Down Expand Up @@ -250,8 +257,7 @@ def read_file(absolute_path)

def yaml_safe_load(yaml_code, filename)
if defined?(SafeYAML) && SafeYAML.respond_to?(:load)
SafeYAML.load(yaml_code, filename,
whitelisted_tags: %w[!ruby/regexp])
SafeYAML.load(yaml_code, filename, whitelisted_tags: %w[!ruby/regexp])
# Ruby 2.6+
elsif Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0')
YAML.safe_load(
Expand Down
16 changes: 16 additions & 0 deletions lib/rubocop/config_validator.rb
Expand Up @@ -13,6 +13,7 @@ class ConfigValidator
INTERNAL_PARAMS = %w[Description StyleGuide
VersionAdded VersionChanged VersionRemoved
Reference Safe SafeAutoCorrect].freeze
NEW_COPS_VALUES = %w[pending disable enable].freeze

def_delegators :@config, :smart_loaded_path, :for_all_cops

Expand All @@ -22,6 +23,7 @@ def initialize(config)
@target_ruby = TargetRuby.new(config)
end

# rubocop:disable Metrics/AbcSize
def validate
check_cop_config_value(@config)
reject_conflicting_safe_settings
Expand All @@ -37,11 +39,13 @@ def validate

alert_about_unrecognized_cops(invalid_cop_names)
check_target_ruby
validate_new_cops_parameter
validate_parameter_names(valid_cop_names)
validate_enforced_styles(valid_cop_names)
validate_syntax_cop
reject_mutually_exclusive_defaults
end
# rubocop:enable Metrics/AbcSize

def target_ruby_version
target_ruby.version
Expand Down Expand Up @@ -107,6 +111,18 @@ def validate_syntax_cop
'It\'s not possible to disable this cop.'
end

def validate_new_cops_parameter
new_cop_parameter = @config.for_all_cops['NewCops']
return if new_cop_parameter.nil? ||
NEW_COPS_VALUES.include?(new_cop_parameter)

message = "invalid #{new_cop_parameter} for `NewCops` found in" \
"#{smart_loaded_path}\n" \
"Valid choices are: #{NEW_COPS_VALUES.join(', ')}"

raise ValidationError, message
end

def validate_parameter_names(valid_cop_names)
valid_cop_names.each do |name|
validate_section_presence(name)
Expand Down
15 changes: 11 additions & 4 deletions lib/rubocop/cop/registry.rb
Expand Up @@ -22,12 +22,13 @@ def initialize(name, origin, badges)

# Registry that tracks all cops by their badge and department.
class Registry
def initialize(cops = [])
def initialize(cops = [], options = {})
@registry = {}
@departments = {}
@cops_by_cop_name = Hash.new { |hash, key| hash[key] = [] }

cops.each { |cop| enlist(cop) }
@options = options
end

def enlist(cop)
Expand Down Expand Up @@ -147,9 +148,8 @@ def enabled(config, only, only_safe = false)
def enabled?(cop, config, only_safe)
cfg = config.for_cop(cop)

# cfg['Enabled'] might be a string `pending`, which is considered
# disabled
cop_enabled = cfg.fetch('Enabled') == true
cop_enabled = cfg.fetch('Enabled') == true ||
enabled_pending_cop?(cfg, config)

if only_safe
cop_enabled && cfg.fetch('Safe', true)
Expand All @@ -158,6 +158,13 @@ def enabled?(cop, config, only_safe)
end
end

def enabled_pending_cop?(cop_cfg, config)
return false if @options[:disable_pending_cops]

cop_cfg.fetch('Enabled') == 'pending' &&
(@options[:enable_pending_cops] || config.enabled_new_cops?)
end

def names
cops.map(&:cop_name)
end
Expand Down
6 changes: 6 additions & 0 deletions lib/rubocop/options.rb
Expand Up @@ -154,6 +154,7 @@ def add_flags_with_optional_args(opts)
end
end

# rubocop:disable Metrics/MethodLength
def add_boolean_flags(opts)
option(opts, '-F', '--fail-fast')
option(opts, '-C', '--cache FLAG')
Expand All @@ -162,6 +163,8 @@ def add_boolean_flags(opts)
option(opts, '-E', '--extra-details')
option(opts, '-S', '--display-style-guide')
option(opts, '-a', '--auto-correct')
option(opts, '--disable-pending-cops')
option(opts, '--enable-pending-cops')
option(opts, '--ignore-disable-comments')

option(opts, '--safe')
Expand All @@ -172,6 +175,7 @@ def add_boolean_flags(opts)
option(opts, '-V', '--verbose-version')
option(opts, '-P', '--parallel')
end
# rubocop:enable Metrics/MethodLength

def add_aliases(opts)
option(opts, '-l', '--lint') do
Expand Down Expand Up @@ -438,7 +442,9 @@ module OptionsHelp
debug: 'Display debug info.',
display_cop_names: ['Display cop names in offense messages.',
'Default is true.'],
disable_pending_cops: 'Run without pending cops.',
display_style_guide: 'Display style guide URLs in offense messages.',
enable_pending_cops: 'Run with pending cops.',
extra_details: 'Display extra details in offense messages.',
lint: 'Run only lint cops.',
safe: 'Run only safe cops.',
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/runner.rb
Expand Up @@ -314,7 +314,7 @@ def mobilized_cop_classes(config)

cop_classes.reject! { |c| c.match?(@options[:except]) }

Cop::Registry.new(cop_classes)
Cop::Registry.new(cop_classes, @options)
end
end

Expand Down
2 changes: 2 additions & 0 deletions manual/basic_usage.md
Expand Up @@ -116,9 +116,11 @@ Command flag | Description
`-c/--config` | Run with specified config file.
`-C/--cache` | Store and reuse results for faster operation.
`-d/--debug` | Displays some extra debug output.
--disable-pending-cops | Run without pending cops.
` --disable-uncorrectable` | Used with --auto-correct to annotate any offenses that do not support autocorrect with `rubocop:todo` comments.
`-D/--[no-]display-cop-names` | Displays cop names in offense messages. Default is true.
` --display-only-fail-level-offenses` | Only output offense messages at the specified `--fail-level` or above
--enable-pending-cops | Run with pending cops.
` --except` | Run all cops enabled by configuration except the specified cop(s) and/or departments.
` --exclude-limit` | Limit how many individual files `--auto-gen-config` can list in `Exclude` parameters, default is 15.
`-E/--extra-details` | Displays extra details in offense messages.
Expand Down
20 changes: 19 additions & 1 deletion manual/versioning.md
Expand Up @@ -54,7 +54,25 @@ For more information: https://docs.rubocop.org/en/latest/versioning/
You can see that 3 new cops were added in RuboCop 0.80 and it's up to you
to decide if you want to enable or disable them.

To suppress this message set `Enabled` to either `true` or `false` in your `.rubocop.yml` file.
To suppress this message set `NewCops` to either `enable` or `disable` in your `.rubocop.yml` file.

The following setting or using `rubocop --enable-pending-cops` command-line option, pending cops are enabled in bulk.

```yaml
AllCops:
NewCops: enable
```

The following setting or using `rubocop --disable-pending-cops` command-line option, pending cops are disabled in bulk.

```yaml
AllCops:
NewCops: disable
```

The command-line options takes precedence over `.rubocop.yml` file.

Or set `Enabled` to either `true` or `false` in your `.rubocop.yml` file.

`Style/ANewCop` is an example of a newly added pending cop:

Expand Down
70 changes: 68 additions & 2 deletions spec/rubocop/cli/cli_options_spec.rb
Expand Up @@ -5,6 +5,8 @@

subject(:cli) { described_class.new }

let(:rubocop) { "#{RuboCop::ConfigLoader::RUBOCOP_HOME}/exe/rubocop" }

before do
RuboCop::ConfigLoader.default_configuration = nil
end
Expand Down Expand Up @@ -221,8 +223,6 @@ class SomeCop < Cop
end

context 'when specifying a pending cop' do
let(:rubocop) { "#{RuboCop::ConfigLoader::RUBOCOP_HOME}/exe/rubocop" }

# Since we define a new cop class, we have to do this in a separate
# process. Otherwise, the extra cop will affect other specs.
let(:output) do
Expand Down Expand Up @@ -260,10 +260,16 @@ class SomeCop < Cop
end

context 'when Style department is enabled' do
let(:new_cops_option) { '' }

before do
create_file('.rubocop.yml', <<~YAML)
require: rubocop_ext
AllCops:
DefaultFormatter: progress
#{new_cops_option}
Style/SomeCop:
Description: Something
Enabled: pending
Expand All @@ -287,6 +293,66 @@ class SomeCop < Cop

expect(manual_url).to eq(versioning_manual_url)
end

context 'when using `--disable-pending-cops` command-line option' do
let(:option) { '--disable-pending-cops' }

let(:output) do
`ruby -I . "#{rubocop}" --require redirect.rb #{option}`
end

it 'does not display a pending cop warning' do
expect(output).not_to start_with(pending_cop_warning)
end
end

context 'when using `--enable-pending-cops` command-line option' do
let(:option) { '--enable-pending-cops' }

let(:output) do
`ruby -I . "#{rubocop}" --require redirect.rb #{option}`
end

it 'does not display a pending cop warning' do
expect(output).not_to start_with(pending_cop_warning)
end
end

context 'when specifying `NewCops: pending` in .rubocop.yml' do
let(:new_cops_option) { 'NewCops: pending' }

let(:output) do
`ruby -I . "#{rubocop}" --require redirect.rb`
end

it 'displays a pending cop warning' do
expect(output).to start_with(pending_cop_warning)
end
end

context 'when specifying `NewCops: disable` in .rubocop.yml' do
let(:new_cops_option) { 'NewCops: disable' }

let(:output) do
`ruby -I . "#{rubocop}" --require redirect.rb`
end

it 'does not display a pending cop warning' do
expect(output).not_to start_with(pending_cop_warning)
end
end

context 'when specifying `NewCops: enable` in .rubocop.yml' do
let(:new_cops_option) { 'NewCops: enable' }

let(:output) do
`ruby -I . "#{rubocop}" --require redirect.rb`
end

it 'does not display a pending cop warning' do
expect(output).not_to start_with(pending_cop_warning)
end
end
end

context 'when Style department is disabled' do
Expand Down

0 comments on commit 544edbf

Please sign in to comment.