Skip to content

Commit

Permalink
Enable pending cops by the version they were added, per-department
Browse files Browse the repository at this point in the history
  • Loading branch information
zofrex committed Aug 24, 2021
1 parent 7b0358f commit 7b9f279
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#8565](https://github.com/rubocop/rubocop/pull/8565): Add option to enable all new cops up to a given version of the plugin they came from. ([@zofrex][])

## 1.19.1 (2021-08-19)

### Bug fixes
Expand Down Expand Up @@ -5813,3 +5817,4 @@
[@markburns]: https://github.com/markburns
[@gregfletch]: https://github.com/gregfletch
[@thearjunmdas]: https://github.com/thearjunmdas
[@zofrex]: https://github.com/zofrex
2 changes: 2 additions & 0 deletions config/default.yml
Expand Up @@ -106,6 +106,8 @@ AllCops:
# 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.
# You can also set this to a version number per-department, e.g. `NewCops: 1.1` will
# enable all cops that were added in version 1.1 and earlier of the relevant plugin.
NewCops: pending
# Enables the result cache if `true`. Can be overridden by the `--cache` command
# line option.
Expand Down
15 changes: 15 additions & 0 deletions docs/modules/ROOT/pages/versioning.adoc
Expand Up @@ -82,6 +82,21 @@ AllCops:

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

=== Enabling/Disabling Pending Cops by Version

It is possible to enable all pending cops up to a given version of the plugin
they come from. This makes it easy to enable cops in bulk, without risking CI
breaking when future updates bring in newer cops.

[source,yaml]
----
Lint:
NewCops: 1.2
----

This would enable all cops from the Lint department that were added in Rubocop
version 1.2 or earlier, but not cops added in later versions.

=== Enabling/Disabling Individual Pending Cops

Finally, you can enable/disable individual pending cops by setting their `Enabled` configuration to either `true` or `false` in your `.rubocop.yml` file:
Expand Down
14 changes: 14 additions & 0 deletions lib/rubocop/config.rb
Expand Up @@ -248,10 +248,24 @@ def pending_cops
cop_metadata = self[qualified_cop_name]
next unless cop_metadata['Enabled'] == 'pending'

next if pending_cop_enabled_by_version?(qualified_cop_name, cop_metadata)

pending_cops << CopConfig.new(qualified_cop_name, cop_metadata)
end
end

def version_le(version, compare_to)
!version.nil? && !compare_to.nil? \
&& Gem::Version.correct?(version) && Gem::Version.correct?(compare_to) \
&& Gem::Version.new(version) <= Gem::Version.new(compare_to)
end

def pending_cop_enabled_by_version?(cop_name, cop_cfg)
department = department_of(cop_name)
department && !department['NewCops'].nil? && version_le(cop_cfg['VersionAdded'],
department['NewCops'])
end

private

def target_rails_version_from_bundler_lock_file
Expand Down
11 changes: 7 additions & 4 deletions lib/rubocop/cop/registry.rb
Expand Up @@ -156,7 +156,8 @@ def enabled(config, only = [], only_safe: false)
def enabled?(cop, config, only_safe)
cfg = config.for_cop(cop)

cop_enabled = cfg.fetch('Enabled') == true || enabled_pending_cop?(cfg, config)
cop_enabled = cfg.fetch('Enabled') == true ||
enabled_pending_cop?(cfg, config, cop.cop_name)

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

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

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

@options[:enable_pending_cops] || config.enabled_new_cops? \
|| config.pending_cop_enabled_by_version?(cop_name, cop_cfg)
end

def names
Expand Down
36 changes: 34 additions & 2 deletions spec/rubocop/cli/options_spec.rb
Expand Up @@ -390,7 +390,8 @@ class SomeCop < Cop

context 'when Style department is enabled' do
let(:new_cops_option) { '' }
let(:version_added) { "VersionAdded: '0.80'" }
let(:version_added) { "VersionAdded: '1.80'" }
let(:department_option) { '' }

before do
create_file('.rubocop.yml', <<~YAML)
Expand All @@ -400,6 +401,10 @@ class SomeCop < Cop
DefaultFormatter: progress
#{new_cops_option}
Style:
Enabled: true
#{department_option}
Style/SomeCop:
Description: Something
Enabled: pending
Expand All @@ -415,12 +420,39 @@ class SomeCop < Cop
remaining_range = pending_cop_warning.length..-(inspected_output.length + 1)
pending_cops = output[remaining_range]

expect(pending_cops).to include("Style/SomeCop: # new in 0.80\n Enabled: true")
expect(pending_cops).to include("Style/SomeCop: # new in 1.80\n Enabled: true")

manual_url = output[remaining_range].split("\n").last

expect(manual_url).to eq(versioning_manual_url)
end

context "when the department's NewCops option is a version > VersionAdded" do
let(:department_option) { "NewCops: '1.81'" }

it 'does not display a pending cop warning for that cop' do
expect(output).to start_with(pending_cop_warning)
expect(output).not_to include("Style/SomeCop: # new in 1.80\n Enabled: true")
end
end

context "when the department's NewCops option is a version < VersionAdded" do
let(:department_option) { "NewCops: '1.79'" }

it 'does display a pending cop warning for that cop' do
expect(output).to start_with(pending_cop_warning)
expect(output).to include("Style/SomeCop: # new in 1.80\n Enabled: true")
end
end

context "when the department's NewCops option is a version == VersionAdded" do
let(:department_option) { "NewCops: '1.80'" }

it 'does not display a pending cop warning for that cop' do
expect(output).to start_with(pending_cop_warning)
expect(output).not_to include("Style/SomeCop: # new in 1.80\n Enabled: true")
end
end
end

context 'when `VersionAdded` is not specified' do
Expand Down
66 changes: 66 additions & 0 deletions spec/rubocop/config_spec.rb
Expand Up @@ -771,6 +771,72 @@ def cop_enabled(cop_class)
end
end

context 'whether the cop is pending' do
context 'when the cop is pending' do
let(:hash) do
{
'Layout/TrailingWhitespace' => { 'Enabled' => 'pending' }
}
end

it 'is in the list of pending cops' do
expect(configuration.pending_cops).to \
include(have_attributes(name: 'Layout/TrailingWhitespace'))
end

context 'NewCops for the department is a version number' do
let(:new_cops_value) { '' }

let(:hash) do
{
'Layout' => { 'NewCops' => new_cops_value },
'Layout/TrailingWhitespace' => { 'Enabled' => 'pending', 'VersionAdded' => '0.80' }
}
end

context 'NewCops > VersionAdded' do
let(:new_cops_value) { '0.81' }

it 'is not in the list of pending cops' do
expect(configuration.pending_cops).not_to \
include(have_attributes(name: 'Layout/TrailingWhitespace'))
end
end

context 'NewCops == VersionAdded' do
let(:new_cops_value) { '0.80' }

it 'is not in the list of pending cops' do
expect(configuration.pending_cops).not_to \
include(have_attributes(name: 'Layout/TrailingWhitespace'))
end
end

context 'NewCops < VersionAdded' do
let(:new_cops_value) { '0.79' }

it 'is in the list of pending cops' do
expect(configuration.pending_cops).to \
include(have_attributes(name: 'Layout/TrailingWhitespace'))
end
end
end
end

context 'when the cop is not pending' do
let(:hash) do
{
'Layout/TrailingWhitespace' => { 'Enabled' => true }
}
end

it 'is not in the list of pending cops' do
expect(configuration.pending_cops).not_to \
include(have_attributes(name: 'Layout/TrailingWhitespace'))
end
end
end

describe '#for_department', :restore_registry do
let(:hash) do
{
Expand Down
34 changes: 34 additions & 0 deletions spec/rubocop/cop/registry_spec.rb
Expand Up @@ -304,6 +304,40 @@
expect(result).to include(RuboCop::Cop::Lint::BooleanSymbol)
end
end

context 'when specifying a version for NewCops in .rubocop.yml' do
let(:cops) do
[
RuboCop::Cop::Lint::EmptyBlock,
RuboCop::Cop::Lint::NoReturnInBeginEndBlocks,
RuboCop::Cop::Lint::EmptyBlock
]
end

let(:config) do
RuboCop::Config.new(
'Lint' => { 'NewCops' => '1.2' },
'Lint/EmptyBlock' => { 'Enabled' => 'pending', 'VersionAdded' => '1.1' },
'Lint/NoReturnInBeginEndBlocks' => { 'Enabled' => 'pending', 'VersionAdded' => '1.2' },
'Lint/EmptyClass' => { 'Enabled' => 'pending', 'VersionAdded' => '1.3' }
)
end

it 'includes cops added before the specified version' do
result = registry.enabled(config, [])
expect(result).to include(RuboCop::Cop::Lint::EmptyBlock)
end

it 'includes cops added in the specified version' do
result = registry.enabled(config, [])
expect(result).to include(RuboCop::Cop::Lint::NoReturnInBeginEndBlocks)
end

it 'does not include cops added after the specified version' do
result = registry.enabled(config, [])
expect(result).not_to include(RuboCop::Cop::Lint::EmptyClass)
end
end
end
end

Expand Down

0 comments on commit 7b9f279

Please sign in to comment.