forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 2
/
else_alignment.rb
156 lines (129 loc) · 4.29 KB
/
else_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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks the alignment of else keywords. Normally they should
# be aligned with an if/unless/while/until/begin/def/rescue keyword, but there
# are special cases when they should follow the same rules as the
# alignment of end.
#
# @example
# # bad
# if something
# code
# else
# code
# end
#
# # bad
# if something
# code
# elsif something
# code
# end
#
# # good
# if something
# code
# else
# code
# end
class ElseAlignment < Base
include EndKeywordAlignment
include Alignment
include CheckAssignment
extend AutoCorrector
MSG = 'Align `%<else_range>s` with `%<base_range>s`.'
def on_if(node, base = nil)
return if ignored_node?(node)
return unless node.else? && begins_its_line?(node.loc.else)
check_alignment(base_range_of_if(node, base), node.loc.else)
return unless node.elsif_conditional?
check_nested(node.else_branch, base)
end
def on_rescue(node)
return unless node.loc.respond_to?(:else) && node.loc.else
check_alignment(base_range_of_rescue(node), node.loc.else)
end
def on_case(node)
return unless node.else?
check_alignment(node.when_branches.last.loc.keyword, node.loc.else)
end
def on_case_match(node)
return unless node.else?
check_alignment(node.in_pattern_branches.last.loc.keyword, node.loc.else)
end
private
def autocorrect(corrector, node)
AlignmentCorrector.correct(corrector, processed_source, node, column_delta)
end
def check_nested(node, base)
on_if(node, base)
ignore_node(node)
end
def base_range_of_if(node, base)
if base
base.source_range
else
lineage = [node, *node.each_ancestor(:if)]
lineage.find { |parent| parent.if? || parent.unless? }.loc.keyword
end
end
def base_range_of_rescue(node)
parent = node.parent
parent = parent.parent if parent.ensure_type?
case parent.type
when :def, :defs then base_for_method_definition(parent)
when :kwbegin then parent.loc.begin
when :block
assignment_node = assignment_node(parent)
if same_line?(parent, assignment_node)
assignment_node.source_range
else
parent.send_node.source_range
end
else node.loc.keyword
end
end
def base_for_method_definition(node)
parent = node.parent
if parent&.send_type?
parent.loc.selector # For example "private def ..."
else
node.loc.keyword
end
end
def check_assignment(node, rhs)
# If there are method calls chained to the right hand side of the
# assignment, we let rhs be the receiver of those method calls before
# we check its indentation.
rhs = first_part_of_call_chain(rhs)
return unless rhs
end_config = config.for_cop('Layout/EndAlignment')
style = end_config['EnforcedStyleAlignWith'] || 'keyword'
base = variable_alignment?(node.loc, rhs, style.to_sym) ? node : rhs
return unless rhs.if_type?
check_nested(rhs, base)
end
def check_alignment(base_range, else_range)
return unless begins_its_line?(else_range)
@column_delta = column_offset_between(base_range, else_range)
return if @column_delta.zero?
message = format(
MSG,
else_range: else_range.source,
base_range: base_range.source[/^\S*/]
)
add_offense(else_range, message: message) do |corrector|
autocorrect(corrector, else_range)
end
end
def assignment_node(node)
assignment_node = node.ancestors.first
return unless assignment_node&.assignment?
assignment_node
end
end
end
end
end