-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
void.rb
168 lines (143 loc) · 5.13 KB
/
void.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
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# This cop checks for operators, variables, literals, and nonmutating
# methods used in void context.
#
# @example CheckForMethodsWithNoSideEffects: false (default)
# # bad
# def some_method
# some_num * 10
# do_something
# end
#
# def some_method(some_var)
# some_var
# do_something
# end
#
# @example CheckForMethodsWithNoSideEffects: true
# # bad
# def some_method(some_array)
# some_array.sort
# do_something(some_array)
# end
#
# def some_method(some_array)
# some_array.each(&:some_other_method)
# end
#
# # good
# def some_method
# do_something
# some_num * 10
# end
#
# def some_method(some_var)
# do_something
# some_var
# end
#
# def some_method(some_array)
# some_array.sort!
# do_something(some_array)
# end
#
# def some_method(some_array)
# some_array = some_array.map
# some_array.map(&:some_other_method)
# end
class Void < Cop
OP_MSG = 'Operator `%<op>s` used in void context.'
VAR_MSG = 'Variable `%<var>s` used in void context.'
LIT_MSG = 'Literal `%<lit>s` used in void context.'
SELF_MSG = '`self` used in void context.'
DEFINED_MSG = '`%<defined>s` used in void context.'
NONMUTATING_MSG = 'Method `#%<method>s` used in void context. ' \
'Did you mean `#%<method>s!`?'
BINARY_OPERATORS = %i[* / % + - == === != < > <= >= <=>].freeze
UNARY_OPERATORS = %i[+@ -@ ~ !].freeze
OPERATORS = (BINARY_OPERATORS + UNARY_OPERATORS).freeze
VOID_CONTEXT_TYPES = %i[def for block].freeze
NONMUTATING_METHODS = %i[capitalize chomp chop collect compact
delete_prefix delete_suffix downcase
encode flatten gsub lstrip map merge next
reject reverse rotate rstrip scrub select
shuffle slice sort sort_by squeeze strip sub
succ swapcase tr tr_s transform_values
unicode_normalize uniq upcase].freeze
VALUE_RETURNING_METHODS = %i[collect collect! delete_if drop_while
filter! find_index index keep_if map map!
reject reject! rindex select select! sort
sort! take_while uniq uniq!].freeze
def on_block(node)
return unless node.body && !node.body.begin_type?
return unless in_void_context?(node.body)
check_expression(node.body)
end
def on_begin(node)
check_begin(node)
end
alias on_kwbegin on_begin
private
def check_begin(node)
expressions = *node
expressions.pop unless in_void_context?(node)
expressions.each do |expr|
check_expression(expr)
end
end
def check_expression(expr)
check_void_op(expr)
check_literal(expr)
check_var(expr)
check_self(expr)
check_defined(expr)
return unless cop_config['CheckForMethodsWithNoSideEffects']
check_nonmutating(expr)
end
def check_void_op(node)
return unless node.send_type? && OPERATORS.include?(node.method_name)
add_offense(node,
location: :selector,
message: format(OP_MSG, op: node.method_name))
end
def check_var(node)
return unless node.variable? || node.const_type?
add_offense(node,
location: :name,
message: format(VAR_MSG, var: node.loc.name.source))
end
def check_literal(node)
return if !node.literal? || node.xstr_type?
add_offense(node, message: format(LIT_MSG, lit: node.source))
end
def check_self(node)
return unless node.self_type?
add_offense(node, message: SELF_MSG)
end
def check_defined(node)
return unless node.defined_type?
add_offense(node, message: format(DEFINED_MSG, defined: node.source))
end
def check_nonmutating(node)
unless node.send_type? &&
NONMUTATING_METHODS.include?(node.method_name)
return
end
add_offense(node, message: format(NONMUTATING_MSG,
method: node.method_name))
end
def in_void_context?(node)
parent = node.parent
return false unless parent && parent.children.last == node
return false if VALUE_RETURNING_METHODS.any? do |method|
node.enumerating?(method)
end
VOID_CONTEXT_TYPES.include?(parent.type) && parent.void_context?
end
end
end
end
end