forked from rubocop/rubocop
/
trailing_underscore_variable.rb
156 lines (125 loc) · 4.54 KB
/
trailing_underscore_variable.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for extra underscores in variable assignment.
#
# @example
# # bad
# a, b, _ = foo()
# a, b, _, = foo()
# a, _, _ = foo()
# a, _, _, = foo()
#
# # good
# a, b, = foo()
# a, = foo()
# *a, b, _ = foo()
# # => We need to know to not include 2 variables in a
# a, *b, _ = foo()
# # => The correction `a, *b, = foo()` is a syntax error
#
# @example AllowNamedUnderscoreVariables: true (default)
# # good
# a, b, _something = foo()
#
# @example AllowNamedUnderscoreVariables: false
# # bad
# a, b, _something = foo()
#
class TrailingUnderscoreVariable < Base
include SurroundingSpace
include RangeHelp
extend AutoCorrector
MSG = 'Do not use trailing `_`s in parallel assignment. ' \
'Prefer `%<code>s`.'
UNDERSCORE = '_'
def on_masgn(node)
ranges = unneeded_ranges(node)
ranges.each do |range|
good_code = node.source
offset = range.begin_pos - node.source_range.begin_pos
good_code[offset, range.size] = ''
add_offense(range, message: format(MSG, code: good_code)) do |corrector|
corrector.remove(range)
end
end
end
private
def find_first_offense(variables)
first_offense = find_first_possible_offense(variables.reverse)
return unless first_offense
return if splat_variable_before?(first_offense, variables)
first_offense
end
def find_first_possible_offense(variables)
variables.reduce(nil) do |offense, variable|
break offense unless %i[lvasgn splat].include?(variable.type)
var, = *variable
var, = *var
break offense if (allow_named_underscore_variables && var != :_) ||
!var.to_s.start_with?(UNDERSCORE)
variable
end
end
def splat_variable_before?(first_offense, variables)
# Account for cases like `_, *rest, _`, where we would otherwise get
# the index of the first underscore.
first_offense_index = reverse_index(variables, first_offense)
variables[0...first_offense_index].any?(&:splat_type?)
end
def reverse_index(collection, item)
collection.size - 1 - collection.reverse.index(item)
end
def allow_named_underscore_variables
@allow_named_underscore_variables ||=
cop_config['AllowNamedUnderscoreVariables']
end
def unneeded_ranges(node)
node.masgn_type? ? (mlhs_node, = *node) : mlhs_node = node
variables = *mlhs_node
main_offense = main_node_offense(node)
if main_offense.nil?
children_offenses(variables)
else
children_offenses(variables) << main_offense
end
end
def main_node_offense(node)
node.masgn_type? ? (mlhs_node, right = *node) : mlhs_node = node
variables = *mlhs_node
first_offense = find_first_offense(variables)
return unless first_offense
if unused_variables_only?(first_offense, variables)
return unused_range(node.type, mlhs_node, right)
end
return range_for_parentheses(first_offense, mlhs_node) if Util.parentheses?(mlhs_node)
range_between(first_offense.source_range.begin_pos,
node.loc.operator.begin_pos)
end
def children_offenses(variables)
variables.select(&:mlhs_type?).flat_map { |v| unneeded_ranges(v) }
end
def unused_variables_only?(offense, variables)
offense.source_range == variables.first.source_range
end
def unused_range(node_type, mlhs_node, right)
start_range = mlhs_node.source_range.begin_pos
end_range = case node_type
when :masgn
right.source_range.begin_pos
when :mlhs
mlhs_node.source_range.end_pos
end
range_between(start_range, end_range)
end
def range_for_parentheses(offense, left)
range_between(
offense.source_range.begin_pos - 1,
left.loc.expression.end_pos - 1
)
end
end
end
end
end