/
rescue_ensure_alignment.rb
187 lines (152 loc) · 5.17 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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# This cop checks whether the rescue and ensure keywords are aligned
# properly.
#
# @example
#
# # bad
# begin
# something
# rescue
# puts 'error'
# end
#
# # good
# begin
# something
# rescue
# puts 'error'
# end
class RescueEnsureAlignment < Cop
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.'.freeze
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
ASSIGNMENT_TYPES = %i[
lvasgn
ivasgn
cvasgn
gvasgn
casgn
masgn
op_asgn
and_asgn
or_asgn
].freeze
def on_resbody(node)
check(node) unless modifier?(node)
end
def on_ensure(node)
check(node)
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
def alignment_source(node, starting_loc)
ending_loc =
case node.type
when :block, :kwbegin
node.loc.begin
when :def, :defs, :class, :module
node.loc.name
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
# 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? ||
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_TYPES.include?(assignment_node.type)
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_node.respond_to?(:access_modifier?) &&
access_modifier_node.access_modifier?
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
end
end
end
end