forked from rubocop/rubocop
/
line_continuation_spacing.rb
130 lines (108 loc) · 3.3 KB
/
line_continuation_spacing.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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks that the backslash of a line continuation is separated from
# preceding text by exactly one space (default) or zero spaces.
#
# @example EnforcedStyle: space (default)
# # bad
# 'a'\
# 'b' \
# 'c'
#
# # good
# 'a' \
# 'b' \
# 'c'
#
# @example EnforcedStyle: no_space
# # bad
# 'a' \
# 'b' \
# 'c'
#
# # good
# 'a'\
# 'b'\
# 'c'
class LineContinuationSpacing < Base
include RangeHelp
extend AutoCorrector
def on_new_investigation
last_line = last_line(processed_source)
@ignored_ranges = string_literal_ranges(processed_source.ast) +
comment_ranges(processed_source.comments)
processed_source.raw_source.lines.each_with_index do |line, index|
break if index >= last_line
line_number = index + 1
investigate(line, line_number)
end
end
private
def investigate(line, line_number)
offensive_spacing = find_offensive_spacing(line)
return unless offensive_spacing
range = source_range(
processed_source.buffer,
line_number,
line.length - offensive_spacing.length - 1,
offensive_spacing.length
)
return if ignore_range?(range)
add_offense(range) { |corrector| autocorrect(corrector, range) }
end
def find_offensive_spacing(line)
if no_space_style?
line[/\s+\\$/, 0]
elsif space_style?
line[/((?<!\s)|\s{2,})\\$/, 0]
end
end
def message(_range)
if no_space_style?
'Use zero spaces in front of backslash.'
elsif space_style?
'Use one space in front of backslash.'
end
end
def autocorrect(corrector, range)
correction = if no_space_style?
'\\'
elsif space_style?
' \\'
end
corrector.replace(range, correction)
end
def string_literal_ranges(ast)
# which lines start inside a string literal?
return [] if ast.nil?
ast.each_node(:str, :dstr).each_with_object(Set.new) do |str, ranges|
loc = str.location
if str.heredoc?
ranges << loc.heredoc_body
elsif loc.respond_to?(:begin) && loc.begin
ranges << loc.expression
end
end
end
def comment_ranges(comments)
comments.map(&:loc).map(&:expression)
end
def last_line(processed_source)
last_token = processed_source.tokens.last
last_token ? last_token.line : processed_source.lines.length
end
def ignore_range?(backtick_range)
@ignored_ranges.any? { |range| range.contains?(backtick_range) }
end
def no_space_style?
cop_config['EnforcedStyle'] == 'no_space'
end
def space_style?
cop_config['EnforcedStyle'] == 'space'
end
end
end
end
end