forked from rubocop/rubocop
/
space_inside_block_braces.rb
261 lines (231 loc) · 8.74 KB
/
space_inside_block_braces.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks that block braces have or don't have surrounding space inside
# them on configuration. For blocks taking parameters, it checks that the
# left brace has or doesn't have trailing space depending on
# configuration.
#
# @example EnforcedStyle: space (default)
# # The `space` style enforces that block braces have
# # surrounding space.
#
# # bad
# some_array.each {puts e}
#
# # good
# some_array.each { puts e }
#
# @example EnforcedStyle: no_space
# # The `no_space` style enforces that block braces don't
# # have surrounding space.
#
# # bad
# some_array.each { puts e }
#
# # good
# some_array.each {puts e}
#
#
# @example EnforcedStyleForEmptyBraces: no_space (default)
# # The `no_space` EnforcedStyleForEmptyBraces style enforces that
# # block braces don't have a space in between when empty.
#
# # bad
# some_array.each { }
# some_array.each { }
# some_array.each { }
#
# # good
# some_array.each {}
#
# @example EnforcedStyleForEmptyBraces: space
# # The `space` EnforcedStyleForEmptyBraces style enforces that
# # block braces have at least a space in between when empty.
#
# # bad
# some_array.each {}
#
# # good
# some_array.each { }
# some_array.each { }
# some_array.each { }
#
#
# @example SpaceBeforeBlockParameters: true (default)
# # The SpaceBeforeBlockParameters style set to `true` enforces that
# # there is a space between `{` and `|`. Overrides `EnforcedStyle`
# # if there is a conflict.
#
# # bad
# [1, 2, 3].each {|n| n * 2 }
#
# # good
# [1, 2, 3].each { |n| n * 2 }
#
# @example SpaceBeforeBlockParameters: false
# # The SpaceBeforeBlockParameters style set to `false` enforces that
# # there is no space between `{` and `|`. Overrides `EnforcedStyle`
# # if there is a conflict.
#
# # bad
# [1, 2, 3].each { |n| n * 2 }
#
# # good
# [1, 2, 3].each {|n| n * 2 }
#
class SpaceInsideBlockBraces < Base
include ConfigurableEnforcedStyle
include SurroundingSpace
include RangeHelp
extend AutoCorrector
def on_block(node)
return if node.keywords?
# Do not register an offense for multi-line empty braces. That means
# preventing autocorrection to single-line empty braces. It will
# conflict with autocorrection by `Layout/SpaceInsideBlockBraces` cop
# if autocorrected to a single-line empty braces.
# See: https://github.com/rubocop/rubocop/issues/7363
return if node.body.nil? && node.multiline?
left_brace = node.loc.begin
right_brace = node.loc.end
check_inside(node, left_brace, right_brace)
end
alias on_numblock on_block
private
def check_inside(node, left_brace, right_brace)
if left_brace.end_pos == right_brace.begin_pos
adjacent_braces(left_brace, right_brace)
else
range = range_between(left_brace.end_pos, right_brace.begin_pos)
inner = range.source
if /\S/.match?(inner)
braces_with_contents_inside(node, inner)
elsif style_for_empty_braces == :no_space
offense(range.begin_pos, range.end_pos,
'Space inside empty braces detected.',
'EnforcedStyleForEmptyBraces')
end
end
end
def adjacent_braces(left_brace, right_brace)
return if style_for_empty_braces != :space
offense(left_brace.begin_pos, right_brace.end_pos,
'Space missing inside empty braces.',
'EnforcedStyleForEmptyBraces')
end
def braces_with_contents_inside(node, inner)
args_delimiter = node.arguments.loc.begin if node.block_type? # Can be ( | or nil.
check_left_brace(inner, node.loc.begin, args_delimiter)
check_right_brace(node, inner, node.loc.begin, node.loc.end, node.single_line?)
end
def check_left_brace(inner, left_brace, args_delimiter)
if /\A\S/.match?(inner)
no_space_inside_left_brace(left_brace, args_delimiter)
else
space_inside_left_brace(left_brace, args_delimiter)
end
end
def check_right_brace(node, inner, left_brace, right_brace, single_line)
if single_line && /\S$/.match?(inner)
no_space(right_brace.begin_pos, right_brace.end_pos, 'Space missing inside }.')
else
column = node.loc.expression.column
return if multiline_block?(left_brace, right_brace) &&
aligned_braces?(inner, right_brace, column)
space_inside_right_brace(inner, right_brace, column)
end
end
def multiline_block?(left_brace, right_brace)
left_brace.first_line != right_brace.first_line
end
def aligned_braces?(inner, right_brace, column)
column == right_brace.column || column == inner_last_space_count(inner)
end
def inner_last_space_count(inner)
inner.split("\n").last.count(' ')
end
def no_space_inside_left_brace(left_brace, args_delimiter)
if pipe?(args_delimiter)
if left_brace.end_pos == args_delimiter.begin_pos &&
cop_config['SpaceBeforeBlockParameters']
offense(left_brace.begin_pos, args_delimiter.end_pos,
'Space between { and | missing.')
else
correct_style_detected
end
else
# We indicate the position after the left brace. Otherwise it's
# difficult to distinguish between space missing to the left and to
# the right of the brace in autocorrect.
no_space(left_brace.end_pos, left_brace.end_pos + 1, 'Space missing inside {.')
end
end
def space_inside_left_brace(left_brace, args_delimiter)
if pipe?(args_delimiter)
if cop_config['SpaceBeforeBlockParameters']
correct_style_detected
else
offense(left_brace.end_pos, args_delimiter.begin_pos,
'Space between { and | detected.')
end
else
brace_with_space = range_with_surrounding_space(left_brace, side: :right)
space(brace_with_space.begin_pos + 1, brace_with_space.end_pos,
'Space inside { detected.')
end
end
def pipe?(args_delimiter)
args_delimiter&.is?('|')
end
def space_inside_right_brace(inner, right_brace, column)
brace_with_space = range_with_surrounding_space(right_brace, side: :left)
begin_pos = brace_with_space.begin_pos
end_pos = brace_with_space.end_pos - 1
if brace_with_space.source.match?(/\R/)
begin_pos = end_pos - (right_brace.column - column)
end
if inner.end_with?(']')
end_pos -= 1
begin_pos = end_pos - (inner_last_space_count(inner) - column)
end
space(begin_pos, end_pos, 'Space inside } detected.')
end
def no_space(begin_pos, end_pos, msg)
if style == :space
offense(begin_pos, end_pos, msg)
else
correct_style_detected
end
end
def space(begin_pos, end_pos, msg)
if style == :no_space
offense(begin_pos, end_pos, msg)
else
correct_style_detected
end
end
def offense(begin_pos, end_pos, msg, style_param = 'EnforcedStyle')
range = range_between(begin_pos, end_pos)
add_offense(range, message: msg) do |corrector|
case range.source
when /\s/ then corrector.remove(range)
when '{}' then corrector.replace(range, '{ }')
when '{|' then corrector.replace(range, '{ |')
else corrector.insert_before(range, ' ')
end
opposite_style_detected if style_param == 'EnforcedStyle'
end
end
def style_for_empty_braces
case cop_config['EnforcedStyleForEmptyBraces']
when 'space' then :space
when 'no_space' then :no_space
else raise 'Unknown EnforcedStyleForEmptyBraces selected!'
end
end
end
end
end
end