-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
to_enum_arguments.rb
95 lines (82 loc) · 2.78 KB
/
to_enum_arguments.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 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
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 }
when :forward_arg
send_arg.forwarded_args_type?
end
end
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
end
end
end
end