forked from rubocop/rubocop
/
space_inside_hash_literal_braces.rb
200 lines (176 loc) · 5.99 KB
/
space_inside_hash_literal_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
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks that braces used for hash literals have or don't have
# surrounding space depending on configuration.
#
# @example EnforcedStyle: space (default)
# # The `space` style enforces that hash literals have
# # surrounding space.
#
# # bad
# h = {a: 1, b: 2}
#
# # good
# h = { a: 1, b: 2 }
#
# @example EnforcedStyle: no_space
# # The `no_space` style enforces that hash literals have
# # no surrounding space.
#
# # bad
# h = { a: 1, b: 2 }
#
# # good
# h = {a: 1, b: 2}
#
# @example EnforcedStyle: compact
# # The `compact` style normally requires a space inside
# # hash braces, with the exception that successive left
# # braces or right braces are collapsed together in nested hashes.
#
# # bad
# h = { a: { b: 2 } }
# foo = { { a: 1 } => { b: { c: 2 } } }
#
# # good
# h = { a: { b: 2 }}
# foo = {{ a: 1 } => { b: { c: 2 }}}
#
# @example EnforcedStyleForEmptyBraces: no_space (default)
# # The `no_space` EnforcedStyleForEmptyBraces style enforces that
# # empty hash braces do not contain spaces.
#
# # bad
# foo = { }
# bar = { }
#
# # good
# foo = {}
# bar = {}
#
# @example EnforcedStyleForEmptyBraces: space
# # The `space` EnforcedStyleForEmptyBraces style enforces that
# # empty hash braces contain space.
#
# # bad
# foo = {}
#
# # good
# foo = { }
# foo = { }
# foo = { }
#
class SpaceInsideHashLiteralBraces < Cop
include SurroundingSpace
include ConfigurableEnforcedStyle
include RangeHelp
MSG = 'Space inside %<problem>s.'
def on_hash(node)
tokens = processed_source.tokens
hash_literal_with_braces(node) do |begin_index, end_index|
check(tokens[begin_index], tokens[begin_index + 1])
return if begin_index == end_index - 1
check(tokens[end_index - 1], tokens[end_index])
end
end
def autocorrect(range)
lambda do |corrector|
case range.source
when /\s/ then corrector.remove(range)
when '{' then corrector.insert_after(range, ' ')
else corrector.insert_before(range, ' ')
end
end
end
private
def hash_literal_with_braces(node)
tokens = processed_source.tokens
begin_index = index_of_first_token(node)
return unless tokens[begin_index].left_brace?
end_index = index_of_last_token(node)
return unless tokens[end_index].right_curly_brace?
yield begin_index, end_index
end
def check(token1, token2)
# No offense if line break inside.
return if token1.line < token2.line
return if token2.comment? # Also indicates there's a line break.
is_empty_braces = token1.left_brace? && token2.right_curly_brace?
expect_space = expect_space?(token1, token2)
if offense?(token1, expect_space)
incorrect_style_detected(token1, token2,
expect_space, is_empty_braces)
else
correct_style_detected
end
end
def expect_space?(token1, token2)
is_same_braces = token1.type == token2.type
is_empty_braces = token1.left_brace? && token2.right_curly_brace?
if is_same_braces && style == :compact
false
elsif is_empty_braces
cop_config['EnforcedStyleForEmptyBraces'] != 'no_space'
else
style != :no_space
end
end
def incorrect_style_detected(token1, token2,
expect_space, is_empty_braces)
brace = (token1.text == '{' ? token1 : token2).pos
range = expect_space ? brace : space_range(brace)
add_offense(
range,
location: range,
message: message(brace, is_empty_braces, expect_space)
) do
style = expect_space ? :no_space : :space
ambiguous_or_unexpected_style_detected(style,
token1.text == token2.text)
end
end
def ambiguous_or_unexpected_style_detected(style, is_match)
if is_match
ambiguous_style_detected(style, :compact)
else
unexpected_style_detected(style)
end
end
def offense?(token1, expect_space)
has_space = token1.space_after?
expect_space ? !has_space : has_space
end
def message(brace, is_empty_braces, expect_space)
inside_what = if is_empty_braces
'empty hash literal braces'
else
brace.source
end
problem = expect_space ? 'missing' : 'detected'
format(MSG, problem: "#{inside_what} #{problem}")
end
def space_range(token_range)
if token_range.source == '{'
range_of_space_to_the_right(token_range)
else
range_of_space_to_the_left(token_range)
end
end
def range_of_space_to_the_right(range)
src = range.source_buffer.source
end_pos = range.end_pos
end_pos += 1 while src[end_pos] =~ /[ \t]/
range_between(range.begin_pos + 1, end_pos)
end
def range_of_space_to_the_left(range)
src = range.source_buffer.source
begin_pos = range.begin_pos
begin_pos -= 1 while src[begin_pos - 1] =~ /[ \t]/
range_between(begin_pos, range.end_pos - 1)
end
end
end
end
end