forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
method_call_with_args_parentheses.rb
306 lines (268 loc) · 8.49 KB
/
method_call_with_args_parentheses.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop enforces the presence (default) or absence of parentheses in
# method calls containing parameters.
#
# In the default style (require_parentheses), macro methods are ignored.
# Additional methods can be added to the `IgnoredMethods` list. This
# option is valid only in the default style.
#
# In the alternative style (omit_parentheses), there are two additional
# options.
#
# 1. `AllowParenthesesInChaining` is `false` by default. Setting it to
# `true` allows the presence of parentheses in the last call during
# method chaining.
#
# 2. `AllowParenthesesInMultilineCall` is `false` by default. Setting it
# to `true` allows the presence of parentheses in multi-line method
# calls.
#
# @example EnforcedStyle: require_parentheses
#
#
# # bad
# array.delete e
#
# # good
# array.delete(e)
#
# # good
# # Operators don't need parens
# foo == bar
#
# # good
# # Setter methods don't need parens
# foo.bar = baz
#
# # okay with `puts` listed in `IgnoredMethods`
# puts 'test'
#
# # IgnoreMacros: true (default)
#
# # good
# class Foo
# bar :baz
# end
#
# # IgnoreMacros: false
#
# # bad
# class Foo
# bar :baz
# end
#
# @example EnforcedStyle: omit_parentheses
#
# # bad
# array.delete(e)
#
# # good
# array.delete e
#
# # bad
# foo.enforce(strict: true)
#
# # good
# foo.enforce strict: true
#
# # AllowParenthesesInMultilineCall: false (default)
#
# # bad
# foo.enforce(
# strict: true
# )
#
# # good
# foo.enforce \
# strict: true
#
# # AllowParenthesesInMultilineCall: true
#
# # good
# foo.enforce(
# strict: true
# )
#
# # good
# foo.enforce \
# strict: true
#
# # AllowParenthesesInChaining: false (default)
#
# # bad
# foo().bar(1)
#
# # good
# foo().bar 1
#
# # AllowParenthesesInChaining: true
#
# # good
# foo().bar(1)
#
# # good
# foo().bar 1
class MethodCallWithArgsParentheses < Cop
include ConfigurableEnforcedStyle
include IgnoredMethods
TRAILING_WHITESPACE_REGEX = /\s+\Z/.freeze
def on_send(node)
case style
when :require_parentheses
add_offense_for_require_parentheses(node)
when :omit_parentheses
add_offense_for_omit_parentheses(node)
end
end
alias on_super on_send
alias on_yield on_send
def autocorrect(node)
case style
when :require_parentheses
autocorrect_for_require_parentheses(node)
when :omit_parentheses
autocorrect_for_omit_parentheses(node)
end
end
def message(_node = nil)
case style
when :require_parentheses
'Use parentheses for method calls with arguments.'.freeze
when :omit_parentheses
'Omit parentheses for method calls with arguments.'.freeze
end
end
private
def add_offense_for_require_parentheses(node)
return if ignored_method?(node.method_name)
return if eligible_for_parentheses_omission?(node)
return unless node.arguments? && !node.parenthesized?
add_offense(node)
end
def add_offense_for_omit_parentheses(node)
return unless node.parenthesized?
return if node.implicit_call?
return if super_call_without_arguments?(node)
return if camel_case_method_call_without_arguments?(node)
return if eligible_for_parentheses_presence?(node)
add_offense(node, location: node.loc.begin.join(node.loc.end))
end
def autocorrect_for_require_parentheses(node)
lambda do |corrector|
corrector.replace(args_begin(node), '(')
unless args_parenthesized?(node)
corrector.insert_after(args_end(node), ')')
end
end
end
def autocorrect_for_omit_parentheses(node)
lambda do |corrector|
if parentheses_at_the_end_of_multiline_call?(node)
corrector.replace(args_begin(node), ' \\')
else
corrector.replace(args_begin(node), ' ')
end
corrector.remove(node.loc.end)
end
end
def eligible_for_parentheses_omission?(node)
node.operator_method? || node.setter_method? || ignore_macros?(node)
end
def ignore_macros?(node)
cop_config['IgnoreMacros'] && node.macro?
end
def args_begin(node)
loc = node.loc
selector =
node.super_type? || node.yield_type? ? loc.keyword : loc.selector
resize_by = args_parenthesized?(node) ? 2 : 1
selector.end.resize(resize_by)
end
def args_end(node)
node.loc.expression.end
end
def args_parenthesized?(node)
return false unless node.arguments.one?
first_node = node.arguments.first
first_node.begin_type? && first_node.parenthesized_call?
end
def parentheses_at_the_end_of_multiline_call?(node)
node.multiline? &&
node.loc.begin.source_line
.gsub(TRAILING_WHITESPACE_REGEX, '')
.end_with?('(')
end
def super_call_without_arguments?(node)
node.super_type? && node.arguments.none?
end
def camel_case_method_call_without_arguments?(node)
node.camel_case_method? && node.arguments.none?
end
def eligible_for_parentheses_presence?(node)
call_in_literals?(node) ||
call_with_ambiguous_arguments?(node) ||
call_in_logical_operators?(node) ||
allowed_multiline_call_with_parentheses?(node) ||
allowed_chained_call_with_parentheses?(node)
end
def call_in_literals?(node)
node.parent &&
(node.parent.pair_type? ||
node.parent.array_type? ||
splat?(node.parent) ||
ternary_if?(node.parent))
end
def call_in_logical_operators?(node)
node.parent &&
(logical_operator?(node.parent) ||
node.parent.descendants.any?(&method(:logical_operator?)))
end
def call_with_ambiguous_arguments?(node)
call_with_braced_block?(node) ||
call_as_argument_or_chain?(node) ||
hash_literal_in_arguments?(node) ||
node.descendants.any? do |n|
splat?(n) || ternary_if?(n) || logical_operator?(n)
end
end
def call_with_braced_block?(node)
node.block_node && node.block_node.braces?
end
def call_as_argument_or_chain?(node)
node.parent && (node.parent.send_type? || node.parent.csend_type?)
end
def hash_literal_in_arguments?(node)
node.arguments.any? do |n|
hash_literal?(n) ||
n.send_type? && node.descendants.any?(&method(:hash_literal?))
end
end
def allowed_multiline_call_with_parentheses?(node)
cop_config['AllowParenthesesInMultilineCall'] && node.multiline?
end
def allowed_chained_call_with_parentheses?(node)
return false unless cop_config['AllowParenthesesInChaining']
previous = node.descendants.first
return false unless previous && previous.send_type?
previous.parenthesized? ||
allowed_chained_call_with_parentheses?(previous)
end
def splat?(node)
node.splat_type? || node.kwsplat_type? || node.block_pass_type?
end
def ternary_if?(node)
node.if_type? && node.ternary?
end
def logical_operator?(node)
(node.and_type? || node.or_type?) && node.logical_operator?
end
def hash_literal?(node)
node.hash_type? && node.braces?
end
end
end
end
end