forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 2
/
useless_ruby2_keywords.rb
119 lines (106 loc) · 3.46 KB
/
useless_ruby2_keywords.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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# Looks for `ruby2_keywords` calls for methods that do not need it.
#
# `ruby2_keywords` should only be called on methods that accept an argument splat
# (`\*args`) but do not explicit keyword arguments (`k:` or `k: true`) or
# a keyword splat (`**kwargs`).
#
# @example
# # good (splat argument without keyword arguments)
# ruby2_keywords def foo(*args); end
#
# # bad (no arguments)
# ruby2_keywords def foo; end
#
# # good
# def foo; end
#
# # bad (positional argument)
# ruby2_keywords def foo(arg); end
#
# # good
# def foo(arg); end
#
# # bad (double splatted argument)
# ruby2_keywords def foo(**args); end
#
# # good
# def foo(**args); end
#
# # bad (keyword arguments)
# ruby2_keywords def foo(i:, j:); end
#
# # good
# def foo(i:, j:); end
#
# # bad (splat argument with keyword arguments)
# ruby2_keywords def foo(*args, i:, j:); end
#
# # good
# def foo(*args, i:, j:); end
#
# # bad (splat argument with double splat)
# ruby2_keywords def foo(*args, **kwargs); end
#
# # good
# def foo(*args, **kwargs); end
#
# # bad (ruby2_keywords given a symbol)
# def foo; end
# ruby2_keywords :foo
#
# # good
# def foo; end
#
# # bad (ruby2_keywords with dynamic method)
# define_method(:foo) { |arg| }
# ruby2_keywords :foo
#
# # good
# define_method(:foo) { |arg| }
#
class UselessRuby2Keywords < Base
MSG = '`ruby2_keywords` is unnecessary for method `%<method_name>s`.'
RESTRICT_ON_SEND = %i[ruby2_keywords].freeze
# Looks for statically or dynamically defined methods with a given name
# @!method method_definition(node, method_name)
def_node_matcher :method_definition, <<~PATTERN
{
(def %1 ...)
({block numblock} (send _ :define_method (sym %1)) ...)
}
PATTERN
def on_send(node)
return unless (first_argument = node.first_argument)
if first_argument.def_type?
inspect_def(node, first_argument)
elsif node.first_argument.sym_type?
inspect_sym(node, first_argument)
end
end
private
def inspect_def(node, def_node)
return if allowed_arguments(def_node.arguments)
add_offense(node.loc.selector, message: format(MSG, method_name: def_node.method_name))
end
def inspect_sym(node, sym_node)
return unless node.parent
method_name = sym_node.value
definition = node.parent.each_child_node.detect { |n| method_definition(n, method_name) }
return unless definition
return if allowed_arguments(definition.arguments)
add_offense(node, message: format(MSG, method_name: method_name))
end
# `ruby2_keywords` is only allowed if there's a `restarg` and no keyword arguments
def allowed_arguments(arguments)
return false if arguments.empty?
arguments.each_child_node(:restarg).any? &&
arguments.each_child_node(:kwarg, :kwoptarg, :kwrestarg).none?
end
end
end
end
end