From 623a75c2bb07def38c2d7632dc9479eb9d5aba44 Mon Sep 17 00:00:00 2001 From: Daniel Vandersluis Date: Wed, 22 Sep 2021 12:59:16 -0400 Subject: [PATCH] Add new `Style/NumberedParametersLimit` cop. --- ...dd_new_styleexcessivenumberedparameters.md | 1 + config/default.yml | 6 ++ lib/rubocop.rb | 1 + .../cop/style/numbered_parameters_limit.rb | 50 +++++++++++++ .../style/numbered_parameters_limit_spec.rb | 74 +++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 changelog/new_add_new_styleexcessivenumberedparameters.md create mode 100644 lib/rubocop/cop/style/numbered_parameters_limit.rb create mode 100644 spec/rubocop/cop/style/numbered_parameters_limit_spec.rb diff --git a/changelog/new_add_new_styleexcessivenumberedparameters.md b/changelog/new_add_new_styleexcessivenumberedparameters.md new file mode 100644 index 00000000000..9ac8d6a76db --- /dev/null +++ b/changelog/new_add_new_styleexcessivenumberedparameters.md @@ -0,0 +1 @@ +* [#10111](https://github.com/rubocop/rubocop/pull/10111): Add new `Style/NumberedParametersLimit` cop. ([@dvandersluis][]) diff --git a/config/default.yml b/config/default.yml index 61823bc2713..d491dfdb64a 100644 --- a/config/default.yml +++ b/config/default.yml @@ -4135,6 +4135,12 @@ Style/Not: VersionAdded: '0.9' VersionChanged: '0.20' +Style/NumberedParametersLimit: + Description: 'Avoid excessive numbered params in a single block.' + Enabled: pending + VersionAdded: '<>' + Max: 1 + Style/NumericLiteralPrefix: Description: 'Use smallcase prefixes for numeric literals.' StyleGuide: '#numeric-literal-prefixes' diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 9feeae3bec2..1298bc2bd87 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -549,6 +549,7 @@ require_relative 'rubocop/cop/style/nil_lambda' require_relative 'rubocop/cop/style/non_nil_check' require_relative 'rubocop/cop/style/not' +require_relative 'rubocop/cop/style/numbered_parameters_limit' require_relative 'rubocop/cop/style/numeric_literals' require_relative 'rubocop/cop/style/numeric_literal_prefix' require_relative 'rubocop/cop/style/numeric_predicate' diff --git a/lib/rubocop/cop/style/numbered_parameters_limit.rb b/lib/rubocop/cop/style/numbered_parameters_limit.rb new file mode 100644 index 00000000000..36c84ddc908 --- /dev/null +++ b/lib/rubocop/cop/style/numbered_parameters_limit.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # This cop detects use of an excessive amount of numbered parameters in a + # single block. Having too many numbered parameters can make code too + # cryptic and hard to read. + # + # The cop defaults to registering an offense if there is more than 1 numbered + # parameter but this maximum can be configured by setting `Max`. + # + # @example Max: 1 (default) + # # bad + # foo { _1.call(_2, _3, _4) } + # + # # good + # foo { do_something(_1) } + class NumberedParametersLimit < Base + extend TargetRubyVersion + extend ExcludeLimit + + DEFAULT_MAX_VALUE = 1 + + minimum_target_ruby_version 2.7 + exclude_limit 'Max' + + MSG = 'Avoid using more than %i numbered %s; %i detected.' + + def on_numblock(node) + _send_node, param_count, * = *node + return if param_count <= max_count + + parameter = max_count > 1 ? 'parameters' : 'parameter' + message = format(MSG, max: max_count, parameter: parameter, count: param_count) + add_offense(node, message: message) { self.max = param_count } + end + + private + + def max_count + max = cop_config.fetch('Max', DEFAULT_MAX_VALUE) + + # Ruby does not allow more than 9 numbered parameters + [max, 9].min + end + end + end + end +end diff --git a/spec/rubocop/cop/style/numbered_parameters_limit_spec.rb b/spec/rubocop/cop/style/numbered_parameters_limit_spec.rb new file mode 100644 index 00000000000..13ba9bb9279 --- /dev/null +++ b/spec/rubocop/cop/style/numbered_parameters_limit_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Style::NumberedParametersLimit, :config do + let(:cop_config) { { 'Max' => max } } + let(:max) { 2 } + + context 'with Ruby >= 2.7', :ruby27 do + it 'does not register an offense for a normal block with too many parameters' do + expect_no_offenses(<<~RUBY) + foo { |a, b, c, d, e, f, g| do_something(a,b,c,d,e,f,g) } + RUBY + end + + it 'does not register an offense for a numblock with fewer than `Max` parameters' do + expect_no_offenses(<<~RUBY) + foo { do_something(_1) } + RUBY + end + + it 'does not register an offense for a numblock with exactly `Max` parameters' do + expect_no_offenses(<<~RUBY) + foo { do_something(_1, _2) } + RUBY + end + + context 'when there are more than `Max` numbered parameters' do + it 'registers an offense for a single line `numblock`' do + expect_offense(<<~RUBY) + foo { do_something(_1, _2, _3, _4, _5) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using more than 2 numbered parameters; 5 detected. + RUBY + end + + it 'registers an offense for a multiline `numblock`' do + expect_offense(<<~RUBY) + foo do + ^^^^^^ Avoid using more than 2 numbered parameters; 5 detected. + do_something(_1, _2, _3, _4, _5) + end + RUBY + end + end + + context 'when configuring Max' do + let(:max) { 5 } + + it 'does not register an offense when there are not too many numbered params' do + expect_no_offenses(<<~RUBY) + foo { do_something(_1, _2, _3, _4, _5) } + RUBY + end + end + + context 'when Max is 1' do + let(:max) { 1 } + + it 'uses the right offense message' do + expect_offense(<<~RUBY) + foo { do_something(_1, _2, _3, _4, _5) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using more than 1 numbered parameter; 5 detected. + RUBY + end + end + + it 'sets Max properly for auto-gen-config' do + expect_offense(<<~RUBY) + foo { do_something(_1, _2, _3, _4, _5) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid using more than 2 numbered parameters; 5 detected. + RUBY + + expect(cop.config_to_allow_offenses).to eq(exclude_limit: { 'Max' => 5 }) + end + end +end