Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* [#7753](https://github.com/rubocop-hq/rubocop/issues/7753): Add new `Lint/ToEnumArguments` cop. ([@fatkodima][]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Lint | ||
# This cop ensures that `to_enum`/`enum_for`, called for the current method, | ||
# has correct arguments. | ||
# | ||
# @example | ||
# # bad | ||
# def method(x, y = 1) | ||
# return to_enum(__method__, x) # `y` is missing | ||
# end | ||
# | ||
# # good | ||
# def method(x, y = 1) | ||
# return to_enum(__method__, x, y) | ||
# end | ||
# | ||
# # bad | ||
# def method(required:) | ||
# return to_enum(:method, required: something) # `required` has incorrect value | ||
# end | ||
# | ||
# # good | ||
# def method(required:) | ||
# return to_enum(:method, required: required) | ||
# end | ||
# | ||
class ToEnumArguments < Base | ||
MSG = 'Ensure you correctly provided all the arguments.' | ||
|
||
RESTRICT_ON_SEND = %i[to_enum enum_for].freeze | ||
|
||
def_node_matcher :enum_conversion_call?, <<~PATTERN | ||
(send {nil? self} {:to_enum :enum_for} $_ $...) | ||
PATTERN | ||
|
||
def_node_matcher :method_name?, <<~PATTERN | ||
{(send nil? :__method__) (sym %1)} | ||
PATTERN | ||
|
||
def_node_matcher :passing_keyword_arg?, <<~PATTERN | ||
(pair (sym %1) (lvar %1)) | ||
PATTERN | ||
|
||
# TODO: add support for argument forwarding (`...`) when ruby 3.0 is released | ||
def on_send(node) | ||
def_node = node.each_ancestor(:def, :defs).first | ||
return unless def_node | ||
|
||
enum_conversion_call?(node) do |method_node, arguments| | ||
add_offense(node) unless method_name?(method_node, def_node.method_name) && | ||
arguments_match?(arguments, def_node) | ||
end | ||
end | ||
|
||
private | ||
|
||
def arguments_match?(arguments, def_node) | ||
index = 0 | ||
|
||
def_node.arguments.reject(&:blockarg_type?).all? do |def_arg| | ||
send_arg = arguments[index] | ||
case def_arg.type | ||
when :arg, :restarg, :optarg | ||
index += 1 | ||
end | ||
|
||
send_arg && argument_match?(send_arg, def_arg) | ||
end | ||
end | ||
|
||
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity | ||
def argument_match?(send_arg, def_arg) | ||
def_arg_name = def_arg.children[0] | ||
|
||
case def_arg.type | ||
when :arg, :restarg | ||
send_arg.source == def_arg.source | ||
when :optarg | ||
send_arg.source == def_arg_name.to_s | ||
when :kwoptarg, :kwarg | ||
send_arg.hash_type? && | ||
send_arg.pairs.any? { |pair| passing_keyword_arg?(pair, def_arg_name) } | ||
when :kwrestarg | ||
send_arg.each_child_node(:kwsplat).any? { |child| child.source == def_arg.source } | ||
end | ||
end | ||
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Lint::ToEnumArguments do | ||
subject(:cop) { described_class.new } | ||
|
||
it 'registers an offense when required arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x) | ||
return to_enum(:m) unless block_given? | ||
^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when optional arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1) | ||
return to_enum(:m, x) unless block_given? | ||
^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when splat arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1, *args) | ||
return to_enum(:m, x, y) unless block_given? | ||
^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when required keyword arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1, *args, required:) | ||
return to_enum(:m, x, y, *args) unless block_given? | ||
^^^^^^^^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when optional keyword arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1, *args, required:, optional: true) | ||
return to_enum(:m, x, y, *args, required: required) unless block_given? | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when splat keyword arg is missing' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1, *args, required:, optional: true, **kwargs) | ||
return to_enum(:m, x, y, *args, required: required, optional: optional) unless block_given? | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when arguments are swapped' do | ||
expect_offense(<<~RUBY) | ||
def m(x, y = 1) | ||
return to_enum(:m, y, x) unless block_given? | ||
^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when other values are passed for keyword arguments' do | ||
expect_offense(<<~RUBY) | ||
def m(required:, optional: true) | ||
return to_enum(:m, required: something_else, optional: optional) unless block_given? | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when not inside method definition' do | ||
expect_no_offenses(<<~RUBY) | ||
to_enum(:m) | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when method call has a receiver other than `self`' do | ||
expect_no_offenses(<<~RUBY) | ||
def m(x) | ||
return foo.to_enum(:m) unless block_given? | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when method is called on `self`' do | ||
expect_offense(<<~RUBY) | ||
def m(x) | ||
return self.to_enum(:m) unless block_given? | ||
^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores the block argument' do | ||
expect_no_offenses(<<~RUBY) | ||
def m(x, &block) | ||
return to_enum(:m, x) unless block_given? | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when enumerator is created for another method' do | ||
expect_offense(<<~RUBY) | ||
def m(x) | ||
return to_enum(:not_m) unless block_given? | ||
^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense when enumerator is created for `__method__`' do | ||
expect_offense(<<~RUBY) | ||
def m(x) | ||
return to_enum(__method__) unless block_given? | ||
^^^^^^^^^^^^^^^^^^^ Ensure you correctly provided all the arguments. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when enumerator is created with the correct arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
def m(x, y = 1, *args, required:, optional: true, **kwargs, &block) | ||
return to_enum(:m, x, y, *args, required: required, optional: optional, **kwargs) unless block_given? | ||
end | ||
RUBY | ||
end | ||
end |