Skip to content

Commit

Permalink
Update Metrics/BlockLength and Metrics/MethodLength to use `Ignor…
Browse files Browse the repository at this point in the history
…edMethods` instead of `ExcludedMethods` in configuration. The previous key is retained for backwards compatibility.
  • Loading branch information
dvandersluis authored and bbatsov committed Nov 26, 2020
1 parent f2c05aa commit 4cc9176
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 94 deletions.
1 change: 1 addition & 0 deletions changelog/change_update_metricsblocklength_and.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#9098](https://github.com/rubocop-hq/rubocop/pull/9098): Update `Metrics/BlockLength` and `Metrics/MethodLength` to use `IgnoredMethods` instead of `ExcludedMethods` in configuration. The previous key is retained for backwards compatibility. ([@dvandersluis][])
10 changes: 6 additions & 4 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2120,11 +2120,12 @@ Metrics/BlockLength:
Description: 'Avoid long blocks with many lines.'
Enabled: true
VersionAdded: '0.44'
VersionChanged: '0.87'
VersionChanged: <<next>>
CountComments: false # count full line comments?
Max: 25
CountAsOne: []
ExcludedMethods:
ExcludedMethods: [] # deprecated, retained for backwards compatibility
IgnoredMethods:
# By default, exclude the `#refine` method, as it tends to have larger
# associated blocks.
- refine
Expand Down Expand Up @@ -2165,11 +2166,12 @@ Metrics/MethodLength:
StyleGuide: '#short-methods'
Enabled: true
VersionAdded: '0.25'
VersionChanged: '0.87'
VersionChanged: <<next>>
CountComments: false # count full line comments?
Max: 10
CountAsOne: []
ExcludedMethods: []
ExcludedMethods: [] # deprecated, retained for backwards compatibility
IgnoredMethods: []

Metrics/ModuleLength:
Description: 'Avoid modules longer than 100 lines of code.'
Expand Down
16 changes: 16 additions & 0 deletions docs/modules/ROOT/pages/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,22 @@ Style/PerlBackrefs:
AutoCorrect: false
----

== Common configuration parameters
There are some configuration parameters that are shared by many cops, with the same behavior.

=== IgnoredMethods

Cops that evaluate methods can often be configured to ignore certain methods. Both strings and
regular expressions can be used. For example:

[source,yaml]
----
Metrics/BlockLength:
IgnoredMethods:
- refine
- !ruby/regexp /\b(class|instance)_methods\b/
----

== Setting the target Ruby version

Some checks are dependent on the version of the Ruby interpreter which the
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
require_relative 'rubocop/ext/regexp_parser'

require_relative 'rubocop/core_ext/string'
require_relative 'rubocop/core_ext/hash'
require_relative 'rubocop/ext/processed_source'

require_relative 'rubocop/path_util'
Expand Down
6 changes: 6 additions & 0 deletions lib/rubocop/config_obsoletion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ class ConfigObsoletion
parameters: 'NameWhitelist',
alternative: '`NameWhitelist` has been renamed to ' \
'`AllowedMethods`.'
},
{
cops: %w[Metrics/BlockLength Metrics/MethodLength],
parameters: 'ExcludedMethods',
alternative: '`ExcludedMethods` has been renamed to `IgnoredMethods`.',
severity: :warning
}
].freeze

Expand Down
13 changes: 8 additions & 5 deletions lib/rubocop/cop/metrics/block_length.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ module Metrics
# Available are: 'array', 'hash', and 'heredoc'. Each literal
# will be counted as one line regardless of its actual size.
#
#
# NOTE: The `ExcludedMethods` configuration is deprecated and only kept
# for backwards compatibility. Please use `IgnoredMethods` instead.
#
# @example CountAsOne: ['array', 'heredoc']
#
# something do
Expand All @@ -33,6 +37,9 @@ module Metrics
# NOTE: This cop does not apply for `Struct` definitions.
class BlockLength < Base
include CodeLength
include IgnoredMethods

ignored_methods deprecated_key: 'ExcludedMethods'

LABEL = 'Block'

Expand All @@ -49,7 +56,7 @@ def excluded_method?(node)
node_receiver = node.receiver&.source&.gsub(/\s+/, '')
node_method = String(node.method_name)

excluded_methods.any? do |config|
ignored_methods.any? do |config|
receiver, method = config.split('.')

unless method
Expand All @@ -61,10 +68,6 @@ def excluded_method?(node)
end
end

def excluded_methods
cop_config['ExcludedMethods'] || []
end

def cop_label
LABEL
end
Expand Down
9 changes: 7 additions & 2 deletions lib/rubocop/cop/metrics/method_length.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ module Metrics
# Available are: 'array', 'hash', and 'heredoc'. Each literal
# will be counted as one line regardless of its actual size.
#
# NOTE: The `ExcludedMethods` configuration is deprecated and only kept
# for backwards compatibility. Please use `IgnoredMethods` instead.
#
# @example CountAsOne: ['array', 'heredoc']
#
# def m
Expand All @@ -31,12 +34,14 @@ module Metrics
#
class MethodLength < Base
include CodeLength
include IgnoredMethods

ignored_methods deprecated_key: 'ExcludedMethods'

LABEL = 'Method'

def on_def(node)
excluded_methods = cop_config['ExcludedMethods']
return if excluded_methods.any? { |m| m.match? String(node.method_name) }
return if ignored_methods.any? { |m| m.match? String(node.method_name) }

check_code_length(node)
end
Expand Down
28 changes: 26 additions & 2 deletions lib/rubocop/cop/mixin/ignored_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,38 @@ module Cop
# This module encapsulates the ability to ignore certain methods when
# parsing.
module IgnoredMethods
private
# Configuration for IgnoredMethods. It is added to classes that include
# the module so that configuration can be set using the `ignored_methods`
# class macro.
module Config
attr_accessor :deprecated_key

def ignored_methods(**config)
self.deprecated_key = config[:deprecated_key]
end
end

def self.included(base)
base.extend(Config)
end

def ignored_method?(name)
ignored_methods.include?(name.to_s)
end

def ignored_methods
cop_config.fetch('IgnoredMethods', [])
keys = %w[IgnoredMethods]
keys << deprecated_key if deprecated_key

cop_config.slice(*keys).values.reduce(&:concat)
end

private

def deprecated_key
return unless self.class.respond_to?(:deprecated_key)

self.class.deprecated_key&.to_s
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions lib/rubocop/core_ext/hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# Extensions to the core Hash class
class Hash
unless method_defined?(:slice)
# Adds `Hash#slice` for Ruby 2.4.
# Returns a hash containing a subset of keys. If a given key is not
# in the hash, it will not be returned.
#
# @return [Hash] hash containing only the keys given.
#
# @example
# { one: 1, two: 2 }.slice(:two, :three) #=> { two: 2 }
def slice(*keys)
h = {}
keys.each { |k| h[k] = self[k] if key?(k) }
h
end
end
end
1 change: 1 addition & 0 deletions spec/rubocop/config_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ class Loop < Cop
'CountComments' => false,
'Max' => 5,
'CountAsOne' => [],
'IgnoredMethods' => [],
'ExcludedMethods' => []
}
)
Expand Down
33 changes: 32 additions & 1 deletion spec/rubocop/config_obsoletion_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
OUTPUT
end

it 'prints a warning message' do
it 'prints a error message' do
begin
config_obsoletion.reject_obsolete_cops_and_parameters
raise 'Expected a RuboCop::ValidationError'
Expand All @@ -298,5 +298,36 @@
end
end
end

context 'when the configuration includes any deprecated parameters' do
let(:hash) do
{
'Metrics/BlockLength' => {
'ExcludedMethods' => %w[foo bar]
},
'Metrics/MethodLength' => {
'ExcludedMethods' => %w[foo bar]
}
}
end

let(:warning_message) { config_obsoletion.warnings.join("\n") }

let(:expected_message) do
<<~OUTPUT.chomp
obsolete parameter ExcludedMethods (for Metrics/BlockLength) found in example/.rubocop.yml
`ExcludedMethods` has been renamed to `IgnoredMethods`.
obsolete parameter ExcludedMethods (for Metrics/MethodLength) found in example/.rubocop.yml
`ExcludedMethods` has been renamed to `IgnoredMethods`.
OUTPUT
end

it 'prints a warning message' do
expect { config_obsoletion.reject_obsolete_cops_and_parameters }
.not_to raise_error(RuboCop::ValidationError)

expect(warning_message).to eq(expected_message)
end
end
end
end
62 changes: 33 additions & 29 deletions spec/rubocop/cop/metrics/block_length_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
RSpec.describe RuboCop::Cop::Metrics::BlockLength, :config do
let(:cop_config) { { 'Max' => 2, 'CountComments' => false } }

shared_examples 'ignoring an offense on an excluded method' do |excluded|
before { cop_config['ExcludedMethods'] = [excluded] }
shared_examples 'ignoring an offense on an ignored method' do |ignored, config_key|
before { cop_config[config_key] = [ignored] }

it 'still rejects other methods with long blocks' do
expect_offense(<<~RUBY)
Expand All @@ -19,7 +19,7 @@

it 'accepts the foo method with a long block' do
expect_no_offenses(<<~RUBY)
#{excluded} do
#{ignored} do
a = 1
a = 2
a = 3
Expand Down Expand Up @@ -211,38 +211,42 @@ def foo
end
end

context 'when ExcludedMethods is enabled' do
it_behaves_like('ignoring an offense on an excluded method', 'foo')
context 'when methods to ignore are defined' do
%w[IgnoredMethods ExcludedMethods].each do |key|
context 'when IgnoredMethods is enabled' do
it_behaves_like('ignoring an offense on an ignored method', 'foo', key)

it_behaves_like('ignoring an offense on an excluded method',
'Gem::Specification.new')
it_behaves_like('ignoring an offense on an ignored method',
'Gem::Specification.new', key)

context 'when receiver contains whitespaces' do
before { cop_config['ExcludedMethods'] = ['Foo::Bar.baz'] }
context 'when receiver contains whitespaces' do
before { cop_config[key] = ['Foo::Bar.baz'] }

it 'ignores whitespaces' do
expect_no_offenses(<<~RUBY)
Foo::
Bar.baz do
a = 1
a = 2
a = 3
it 'ignores whitespaces' do
expect_no_offenses(<<~RUBY)
Foo::
Bar.baz do
a = 1
a = 2
a = 3
end
RUBY
end
RUBY
end
end

context 'when a method is ignored, but receiver is a module' do
before { cop_config['ExcludedMethods'] = ['baz'] }
end

it 'does not report an offense' do
expect_no_offenses(<<~RUBY)
Foo::Bar.baz do
a = 1
a = 2
a = 3
context 'when a method is ignored, but receiver is a module' do
before { cop_config[key] = ['baz'] }

it 'does not report an offense' do
expect_no_offenses(<<~RUBY)
Foo::Bar.baz do
a = 1
a = 2
a = 3
end
RUBY
end
RUBY
end
end
end
end
Expand Down

0 comments on commit 4cc9176

Please sign in to comment.