-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
comment_indentation.rb
141 lines (120 loc) · 4.12 KB
/
comment_indentation.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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# This cop checks the indentation of comments.
#
# @example
# # bad
# # comment here
# def method_name
# end
#
# # comment here
# a = 'hello'
#
# # yet another comment
# if true
# true
# end
#
# # good
# # comment here
# def method_name
# end
#
# # comment here
# a = 'hello'
#
# # yet another comment
# if true
# true
# end
#
class CommentIndentation < Cop
include Alignment
MSG = 'Incorrect indentation detected (column %<column>d ' \
'instead of %<correct_comment_indentation>d).'
def investigate(processed_source)
processed_source.comments.each { |comment| check(comment) }
end
def autocorrect(comment)
corrections = autocorrect_preceding_comments(comment) <<
autocorrect_one(comment)
->(corrector) { corrections.each { |corr| corr.call(corrector) } }
end
private
# Corrects all comment lines that occur immediately before the given
# comment and have the same indentation. This is to avoid a long chain
# of correcting, saving the file, parsing and inspecting again, and
# then correcting one more line, and so on.
def autocorrect_preceding_comments(comment)
comments = processed_source.comments
index = comments.index(comment)
comments[0..index]
.reverse_each
.each_cons(2)
.take_while { |below, above| should_correct?(above, below) }
.map { |_, above| autocorrect_one(above) }
end
def should_correct?(preceding_comment, reference_comment)
loc = preceding_comment.loc
ref_loc = reference_comment.loc
loc.line == ref_loc.line - 1 && loc.column == ref_loc.column
end
def autocorrect_one(comment)
AlignmentCorrector.correct(processed_source, comment, @column_delta)
end
def check(comment)
return unless own_line_comment?(comment)
next_line = line_after_comment(comment)
correct_comment_indentation = correct_indentation(next_line)
column = comment.loc.column
@column_delta = correct_comment_indentation - column
return if @column_delta.zero?
if two_alternatives?(next_line)
# Try the other
correct_comment_indentation += configured_indentation_width
# We keep @column_delta unchanged so that autocorrect changes to
# the preferred style of aligning the comment with the keyword.
return if column == correct_comment_indentation
end
add_offense(
comment,
message: message(column, correct_comment_indentation)
)
end
def message(column, correct_comment_indentation)
format(
MSG,
column: column,
correct_comment_indentation: correct_comment_indentation
)
end
def own_line_comment?(comment)
own_line = processed_source.lines[comment.loc.line - 1]
/\A\s*#/.match?(own_line)
end
def line_after_comment(comment)
lines = processed_source.lines
lines[comment.loc.line..-1].find { |line| !line.blank? }
end
def correct_indentation(next_line)
return 0 unless next_line
indentation_of_next_line = next_line =~ /\S/
indentation_of_next_line + if less_indented?(next_line)
configured_indentation_width
else
0
end
end
def less_indented?(line)
/^\s*(end\b|[)}\]])/.match?(line)
end
def two_alternatives?(line)
/^\s*(else|elsif|when|rescue|ensure)\b/.match?(line)
end
end
end
end
end