forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rescue_ensure_alignment.rb
221 lines (182 loc) · 6.48 KB
/
rescue_ensure_alignment.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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# This cop checks whether the rescue and ensure keywords are aligned
# properly.
#
# Two modes are supported through the `EnforcedStyleAlignWith`
# configuration parameter:
#
# If it's set to `keyword` (which is the default), the `rescue`
# shall be aligned with the start of the begin keyword.
#
# If it's set to `variable` the `rescue` shall be aligned with the
# left-hand-side of the variable assignment, if there is one.
#
# @example EnforcedStyleAlignWith: keyword (default)
#
# # bad
# result = begin
# something
# rescue
# puts 'error'
# end
#
# # good
# result = begin
# something
# rescue
# puts 'error'
# end
#
# @example EnforcedStyleAlignWith: variable
#
# # bad
# result = begin
# something
# rescue
# puts 'error'
# end
#
# # good
# result = begin
# something
# rescue
# puts 'error'
# end
class RescueEnsureAlignment < Cop
include ConfigurableEnforcedStyle
include RangeHelp
MSG = '`%<kw_loc>s` at %<kw_loc_line>d, %<kw_loc_column>d is not ' \
'aligned with `%<beginning>s` at ' \
'%<begin_loc_line>d, %<begin_loc_column>d.'
ANCESTOR_TYPES = %i[kwbegin def defs class module].freeze
RUBY_2_5_ANCESTOR_TYPES = (ANCESTOR_TYPES + %i[block]).freeze
ANCESTOR_TYPES_WITH_ACCESS_MODIFIERS = %i[def defs].freeze
ALTERNATIVE_ACCESS_MODIFIERS = %i[public_class_method
private_class_method].freeze
def on_resbody(node)
check(node) unless modifier?(node)
end
def on_ensure(node)
check(node)
end
def style_parameter_name
'EnforcedStyleAlignWith'
end
def autocorrect(node)
whitespace = whitespace_range(node)
# Some inline node is sitting before current node.
return nil unless whitespace.source.strip.empty?
alignment_node = alignment_node(node)
return false if alignment_node.nil?
new_column = alignment_node.loc.column
->(corrector) { corrector.replace(whitespace, ' ' * new_column) }
end
def investigate(processed_source)
@modifier_locations =
processed_source.tokens.each_with_object([]) do |token, locations|
next unless token.rescue_modifier?
locations << token.pos
end
end
private
# Check alignment of node with rescue or ensure modifiers.
def check(node)
alignment_node = alignment_node(node)
return if alignment_node.nil?
alignment_loc = alignment_node.loc.expression
kw_loc = node.loc.keyword
return if
alignment_loc.column == kw_loc.column ||
alignment_loc.line == kw_loc.line
add_offense(
node,
location: kw_loc,
message: format_message(alignment_node, alignment_loc, kw_loc)
)
end
def format_message(alignment_node, alignment_loc, kw_loc)
format(
MSG,
kw_loc: kw_loc.source,
kw_loc_line: kw_loc.line,
kw_loc_column: kw_loc.column,
beginning: alignment_source(alignment_node, alignment_loc),
begin_loc_line: alignment_loc.line,
begin_loc_column: alignment_loc.column
)
end
# rubocop:disable Metrics/AbcSize
def alignment_source(node, starting_loc)
ending_loc =
case node.type
when :block, :kwbegin
node.loc.begin
when :def, :defs, :class, :module,
:lvasgn, :ivasgn, :cvasgn, :gvasgn, :casgn
node.loc.name
when :masgn
mlhs_node, = *node
mlhs_node.loc.expression
else
# It is a wrapper with access modifier.
node.child_nodes.first.loc.name
end
range_between(starting_loc.begin_pos, ending_loc.end_pos).source
end
# rubocop:enable Metrics/AbcSize
# We will use ancestor or wrapper with access modifier.
def alignment_node(node)
ancestor_node = ancestor_node(node)
return ancestor_node if ancestor_node.nil?
return ancestor_node if style == :keyword && ancestor_node.kwbegin_type?
assignment_node = assignment_node(ancestor_node)
return assignment_node if same_line?(ancestor_node, assignment_node)
access_modifier_node = access_modifier_node(ancestor_node)
return access_modifier_node unless access_modifier_node.nil?
ancestor_node
end
def ancestor_node(node)
ancestor_types =
if target_ruby_version >= 2.5
RUBY_2_5_ANCESTOR_TYPES
else
ANCESTOR_TYPES
end
node.each_ancestor(*ancestor_types).first
end
def assignment_node(node)
assignment_node = node.ancestors.first
return nil unless
assignment_node&.assignment?
assignment_node
end
def access_modifier_node(node)
return nil unless
ANCESTOR_TYPES_WITH_ACCESS_MODIFIERS.include?(node.type)
access_modifier_node = node.ancestors.first
return nil unless access_modifier?(access_modifier_node)
access_modifier_node
end
def modifier?(node)
return false unless @modifier_locations.respond_to?(:include?)
@modifier_locations.include?(node.loc.keyword)
end
def whitespace_range(node)
begin_pos = node.loc.keyword.begin_pos
current_column = node.loc.keyword.column
range_between(begin_pos - current_column, begin_pos)
end
def access_modifier?(node)
return true if node.respond_to?(:access_modifier?) &&
node.access_modifier?
return true if node.respond_to?(:method_name) &&
ALTERNATIVE_ACCESS_MODIFIERS.include?(node.method_name)
false
end
end
end
end
end