rubocop-erb 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -1
- data/Gemfile.lock +15 -14
- data/config/default.yml +55 -8
- data/lib/rubocop/erb/corrector.rb +114 -0
- data/lib/rubocop/erb/erb_source.rb +150 -0
- data/lib/rubocop/erb/ruby_extractor.rb +36 -80
- data/lib/rubocop/erb/version.rb +1 -1
- data/lib/rubocop/erb.rb +5 -4
- data/rubocop-erb.gemspec +3 -3
- metadata +15 -17
- data/lib/rubocop/erb/keyword_remover.rb +0 -160
- data/lib/rubocop/erb/processed_source_builder.rb +0 -44
- data/lib/rubocop/erb/ruby_clip.rb +0 -11
- data/lib/rubocop/erb/when_decomposer.rb +0 -71
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 749a40c8adb5dd91a43e0db5eb6e045e2772afcad6ed832090ab77dece2b8e3d
|
|
4
|
+
data.tar.gz: 04301b69ced5ca9e5a8e8e2f26f31972d3112ea63b17ba3b5c936c935c79e223
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb18dff7fa5c47639da5301013d004703b7a68cb51d1a61caeae7be6a62091f7b94f84d97cc3ac4164ac6614d5f8679d1325b0949a277c07d7b00957bd8500b9
|
|
7
|
+
data.tar.gz: 8e07c760cfb7ac5bd4c41d3b8f368cb3f9171a64e815a9c19b41af585649ed344d46a257bd68b16cafbc99ac4d9486bda4f19894de48f50525699dc40b4b70db
|
data/.rubocop.yml
CHANGED
|
@@ -14,7 +14,7 @@ AllCops:
|
|
|
14
14
|
Exclude:
|
|
15
15
|
- spec/**/*.erb
|
|
16
16
|
NewCops: enable
|
|
17
|
-
TargetRubyVersion: 3.
|
|
17
|
+
TargetRubyVersion: 3.3
|
|
18
18
|
|
|
19
19
|
Gemspec/RequireMFA:
|
|
20
20
|
Enabled: false
|
|
@@ -28,6 +28,9 @@ RSpec/ExampleLength:
|
|
|
28
28
|
RSpec/MultipleExpectations:
|
|
29
29
|
Enabled: false
|
|
30
30
|
|
|
31
|
+
RSpec/MultipleMemoizedHelpers:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
31
34
|
RSpec/NamedSubject:
|
|
32
35
|
Enabled: false
|
|
33
36
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rubocop-erb (0.
|
|
5
|
-
herb (~> 0.
|
|
4
|
+
rubocop-erb (0.8.1)
|
|
5
|
+
herb (~> 0.9, >= 0.9.3)
|
|
6
6
|
lint_roller (~> 1.1)
|
|
7
|
-
rubocop (~> 1.
|
|
7
|
+
rubocop (~> 1.84)
|
|
8
8
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
@@ -30,21 +30,21 @@ GEM
|
|
|
30
30
|
connection_pool (2.5.0)
|
|
31
31
|
diff-lcs (1.6.0)
|
|
32
32
|
drb (2.2.1)
|
|
33
|
-
herb (0.
|
|
34
|
-
herb (0.
|
|
33
|
+
herb (0.10.1)
|
|
34
|
+
herb (0.10.1-x86_64-linux-gnu)
|
|
35
35
|
i18n (1.14.7)
|
|
36
36
|
concurrent-ruby (~> 1.0)
|
|
37
37
|
json (2.10.1)
|
|
38
38
|
language_server-protocol (3.17.0.4)
|
|
39
39
|
lint_roller (1.1.0)
|
|
40
40
|
logger (1.6.6)
|
|
41
|
-
minitest (5.
|
|
41
|
+
minitest (5.27.0)
|
|
42
42
|
mutex_m (0.3.0)
|
|
43
43
|
parallel (1.26.3)
|
|
44
|
-
parser (3.3.
|
|
44
|
+
parser (3.3.11.1)
|
|
45
45
|
ast (~> 2.4.1)
|
|
46
46
|
racc
|
|
47
|
-
prism (1.
|
|
47
|
+
prism (1.9.0)
|
|
48
48
|
racc (1.8.1)
|
|
49
49
|
rainbow (3.1.1)
|
|
50
50
|
rake (13.2.1)
|
|
@@ -62,19 +62,20 @@ GEM
|
|
|
62
62
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
63
63
|
rspec-support (~> 3.13.0)
|
|
64
64
|
rspec-support (3.13.2)
|
|
65
|
-
rubocop (1.
|
|
65
|
+
rubocop (1.87.0)
|
|
66
66
|
json (~> 2.3)
|
|
67
67
|
language_server-protocol (~> 3.17.0.2)
|
|
68
68
|
lint_roller (~> 1.1.0)
|
|
69
|
-
parallel (
|
|
69
|
+
parallel (>= 1.10)
|
|
70
70
|
parser (>= 3.3.0.2)
|
|
71
71
|
rainbow (>= 2.2.2, < 4.0)
|
|
72
72
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
73
|
-
rubocop-ast (>= 1.
|
|
73
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
74
74
|
ruby-progressbar (~> 1.7)
|
|
75
75
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
76
|
-
rubocop-ast (1.
|
|
77
|
-
parser (>= 3.3.
|
|
76
|
+
rubocop-ast (1.49.1)
|
|
77
|
+
parser (>= 3.3.7.2)
|
|
78
|
+
prism (~> 1.7)
|
|
78
79
|
rubocop-rake (0.7.1)
|
|
79
80
|
lint_roller (~> 1.1)
|
|
80
81
|
rubocop (>= 1.72.1)
|
|
@@ -90,7 +91,7 @@ GEM
|
|
|
90
91
|
concurrent-ruby (~> 1.0)
|
|
91
92
|
unicode-display_width (3.1.4)
|
|
92
93
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
93
|
-
unicode-emoji (4.0
|
|
94
|
+
unicode-emoji (4.3.0)
|
|
94
95
|
|
|
95
96
|
PLATFORMS
|
|
96
97
|
ruby
|
data/config/default.yml
CHANGED
|
@@ -27,10 +27,19 @@ Layout/CommentIndentation:
|
|
|
27
27
|
Exclude:
|
|
28
28
|
- '**/*.erb'
|
|
29
29
|
|
|
30
|
+
# Indentation follows the containing ERB tag, not the file as a whole.
|
|
30
31
|
Layout/InitialIndentation:
|
|
31
32
|
Exclude:
|
|
32
33
|
- '**/*.erb'
|
|
33
34
|
|
|
35
|
+
Layout/ElseAlignment:
|
|
36
|
+
Exclude:
|
|
37
|
+
- '**/*.erb'
|
|
38
|
+
|
|
39
|
+
Layout/EmptyLinesAroundBlockBody:
|
|
40
|
+
Exclude:
|
|
41
|
+
- '**/*.erb'
|
|
42
|
+
|
|
34
43
|
Layout/EmptyLineAfterGuardClause:
|
|
35
44
|
Exclude:
|
|
36
45
|
- '**/*.erb'
|
|
@@ -39,6 +48,10 @@ Layout/EndAlignment:
|
|
|
39
48
|
Exclude:
|
|
40
49
|
- '**/*.erb'
|
|
41
50
|
|
|
51
|
+
Layout/ExtraSpacing:
|
|
52
|
+
Exclude:
|
|
53
|
+
- '**/*.erb'
|
|
54
|
+
|
|
42
55
|
Layout/FirstArgumentIndentation:
|
|
43
56
|
Exclude:
|
|
44
57
|
- '**/*.erb'
|
|
@@ -79,6 +92,7 @@ Layout/IndentationWidth:
|
|
|
79
92
|
Exclude:
|
|
80
93
|
- '**/*.erb'
|
|
81
94
|
|
|
95
|
+
# Non-Ruby content gives false positives for this cop.
|
|
82
96
|
Layout/LeadingEmptyLines:
|
|
83
97
|
Exclude:
|
|
84
98
|
- '**/*.erb'
|
|
@@ -123,6 +137,11 @@ Layout/ParameterAlignment:
|
|
|
123
137
|
Exclude:
|
|
124
138
|
- '**/*.erb'
|
|
125
139
|
|
|
140
|
+
Layout/SpaceBeforeSemicolon:
|
|
141
|
+
Exclude:
|
|
142
|
+
- '**/*.erb'
|
|
143
|
+
|
|
144
|
+
# Non-Ruby content gives false positives for this cop.
|
|
126
145
|
Layout/TrailingEmptyLines:
|
|
127
146
|
Exclude:
|
|
128
147
|
- '**/*.erb'
|
|
@@ -131,38 +150,42 @@ Layout/TrailingWhitespace:
|
|
|
131
150
|
Exclude:
|
|
132
151
|
- '**/*.erb'
|
|
133
152
|
|
|
134
|
-
Lint/
|
|
153
|
+
Lint/DuplicateBranch:
|
|
135
154
|
Exclude:
|
|
136
155
|
- '**/*.erb'
|
|
137
156
|
|
|
138
|
-
Lint/
|
|
157
|
+
Lint/EmptyBlock:
|
|
139
158
|
Exclude:
|
|
140
159
|
- '**/*.erb'
|
|
141
160
|
|
|
142
|
-
Lint/
|
|
161
|
+
Lint/EmptyConditionalBody:
|
|
143
162
|
Exclude:
|
|
144
163
|
- '**/*.erb'
|
|
145
164
|
|
|
146
|
-
|
|
165
|
+
# Non-Ruby content gives false positives for this cop.
|
|
166
|
+
Lint/EmptyFile:
|
|
147
167
|
Exclude:
|
|
148
168
|
- '**/*.erb'
|
|
149
169
|
|
|
150
|
-
Lint/
|
|
170
|
+
Lint/EmptyWhen:
|
|
151
171
|
Exclude:
|
|
152
172
|
- '**/*.erb'
|
|
153
173
|
|
|
154
|
-
|
|
174
|
+
Lint/Void:
|
|
155
175
|
Exclude:
|
|
156
176
|
- '**/*.erb'
|
|
157
177
|
|
|
158
|
-
|
|
178
|
+
# The HTML between ERB tags is dropped, so an `if` around an output tag
|
|
179
|
+
# looks like `if a.present?; a; end`.
|
|
180
|
+
Rails/Presence:
|
|
159
181
|
Exclude:
|
|
160
182
|
- '**/*.erb'
|
|
161
183
|
|
|
162
|
-
|
|
184
|
+
Style/EmptyElse:
|
|
163
185
|
Exclude:
|
|
164
186
|
- '**/*.erb'
|
|
165
187
|
|
|
188
|
+
# ERB doesn't support magic comments.
|
|
166
189
|
Style/FrozenStringLiteralComment:
|
|
167
190
|
Exclude:
|
|
168
191
|
- '**/*.erb'
|
|
@@ -171,10 +194,18 @@ Style/IdenticalConditionalBranches:
|
|
|
171
194
|
Exclude:
|
|
172
195
|
- '**/*.erb'
|
|
173
196
|
|
|
197
|
+
Style/IfInsideElse:
|
|
198
|
+
Exclude:
|
|
199
|
+
- '**/*.erb'
|
|
200
|
+
|
|
174
201
|
Style/IfUnlessModifier:
|
|
175
202
|
Exclude:
|
|
176
203
|
- '**/*.erb'
|
|
177
204
|
|
|
205
|
+
Style/IfWithSemicolon:
|
|
206
|
+
Exclude:
|
|
207
|
+
- '**/*.erb'
|
|
208
|
+
|
|
178
209
|
Style/MultilineTernaryOperator:
|
|
179
210
|
Exclude:
|
|
180
211
|
- '**/*.erb'
|
|
@@ -191,14 +222,30 @@ Style/ParallelAssignment:
|
|
|
191
222
|
Exclude:
|
|
192
223
|
- '**/*.erb'
|
|
193
224
|
|
|
225
|
+
Style/RedundantCondition:
|
|
226
|
+
Exclude:
|
|
227
|
+
- '**/*.erb'
|
|
228
|
+
|
|
194
229
|
Style/RescueModifier:
|
|
195
230
|
Exclude:
|
|
196
231
|
- '**/*.erb'
|
|
197
232
|
|
|
233
|
+
Style/SafeNavigation:
|
|
234
|
+
Exclude:
|
|
235
|
+
- '**/*.erb'
|
|
236
|
+
|
|
198
237
|
Style/Semicolon:
|
|
199
238
|
Exclude:
|
|
200
239
|
- '**/*.erb'
|
|
201
240
|
|
|
241
|
+
Style/SoleNestedConditional:
|
|
242
|
+
Exclude:
|
|
243
|
+
- '**/*.erb'
|
|
244
|
+
|
|
245
|
+
Style/SymbolProc:
|
|
246
|
+
Exclude:
|
|
247
|
+
- '**/*.erb'
|
|
248
|
+
|
|
202
249
|
Style/TrailingCommaInArguments:
|
|
203
250
|
Exclude:
|
|
204
251
|
- '**/*.erb'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Erb
|
|
5
|
+
module Corrector
|
|
6
|
+
def initialize(source)
|
|
7
|
+
@processed_source = source.processed_source if source.respond_to?(:processed_source)
|
|
8
|
+
@discard_non_ruby_corrections = @processed_source.is_a?(ERBSource)
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Every rewrite operation funnels through `combine`, which is the only place
|
|
13
|
+
# both the target range and the replacement text are available. We use it to
|
|
14
|
+
# recognize a benign re-indentation -- only adding or removing whitespace in
|
|
15
|
+
# the leading whitespace of a line that begins with an ERB tag -- so it is
|
|
16
|
+
# kept rather than discarded as a non-Ruby correction. When the correction
|
|
17
|
+
# reaches past the indentation into the opening tag itself (as
|
|
18
|
+
# `Layout/CaseIndentation` does), it is shrunk to cover only the leading
|
|
19
|
+
# whitespace so the tag is preserved.
|
|
20
|
+
def combine(
|
|
21
|
+
range,
|
|
22
|
+
attributes
|
|
23
|
+
)
|
|
24
|
+
if (adjustment = whitespace_indent_adjustment(range, attributes))
|
|
25
|
+
range, attributes = adjustment
|
|
26
|
+
@whitespace_only_indent = true
|
|
27
|
+
end
|
|
28
|
+
super
|
|
29
|
+
ensure
|
|
30
|
+
@whitespace_only_indent = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A correction that touches non-Ruby code can't be applied to the extracted
|
|
34
|
+
# Ruby without breaking the surrounding ERB. Rather than apply a broken fix,
|
|
35
|
+
# we discard the whole correction (every operation from the offense) by
|
|
36
|
+
# reporting the corrector as empty, leaving the offense flagged but not
|
|
37
|
+
# auto-correctable.
|
|
38
|
+
def empty?
|
|
39
|
+
return true if @intersects_non_ruby
|
|
40
|
+
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_range(node_or_range)
|
|
45
|
+
node_or_range = @processed_source.to_range(node_or_range) if @processed_source.is_a?(ERBSource)
|
|
46
|
+
range = super
|
|
47
|
+
flag_non_ruby_intersection(range)
|
|
48
|
+
range
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def flag_non_ruby_intersection(range)
|
|
54
|
+
return unless @discard_non_ruby_corrections
|
|
55
|
+
return unless @processed_source.is_a?(ERBSource)
|
|
56
|
+
return unless range.is_a?(::Parser::Source::Range)
|
|
57
|
+
return if @whitespace_only_indent
|
|
58
|
+
return if @processed_source.range_within_ruby_content?(range.begin_pos, range.end_pos)
|
|
59
|
+
|
|
60
|
+
@intersects_non_ruby = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Array(Parser::Source::Range, Hash), nil] the (possibly rewritten)
|
|
64
|
+
# range and attributes to apply when the operation only adjusts whitespace
|
|
65
|
+
# in the leading indentation of an ERB-tag line, or nil when inapplicable
|
|
66
|
+
def whitespace_indent_adjustment(
|
|
67
|
+
range,
|
|
68
|
+
attributes
|
|
69
|
+
)
|
|
70
|
+
return unless @discard_non_ruby_corrections && @processed_source.is_a?(ERBSource)
|
|
71
|
+
return unless range.is_a?(::Parser::Source::Range)
|
|
72
|
+
return unless attributes.is_a?(Hash)
|
|
73
|
+
|
|
74
|
+
replacement = attributes[:replacement]
|
|
75
|
+
contents = attributes.values_at(:replacement, :insert_before, :insert_after).compact
|
|
76
|
+
# `attributes` comes from Parser's TreeRewriter internals. If none of the
|
|
77
|
+
# content keys we know about are present, the shape has changed from under
|
|
78
|
+
# us -- fail safe by treating this as a normal correction (discarded if it
|
|
79
|
+
# touches non-Ruby) rather than assuming it is whitespace-only.
|
|
80
|
+
return if contents.empty?
|
|
81
|
+
return unless contents.all? { |content| content.match?(/\A\s*\z/) }
|
|
82
|
+
|
|
83
|
+
source = @processed_source.template_source
|
|
84
|
+
line = @processed_source.buffer.line_range(@processed_source.buffer.line_for_position(range.begin_pos))
|
|
85
|
+
offset = source[line.begin_pos...line.end_pos].to_s.index(/\S/)
|
|
86
|
+
return unless offset
|
|
87
|
+
|
|
88
|
+
tag_start = line.begin_pos + offset
|
|
89
|
+
# Everything from the line start up to the range, and the first code on the
|
|
90
|
+
# line, must be whitespace then an ERB opening tag.
|
|
91
|
+
return unless source[tag_start, 2] == '<%'
|
|
92
|
+
return unless source[line.begin_pos...range.begin_pos].to_s.match?(/\A\s*\z/)
|
|
93
|
+
return unless range.begin_pos <= tag_start
|
|
94
|
+
|
|
95
|
+
# Wholly within the indentation: keep the operation untouched.
|
|
96
|
+
return [range, attributes] if range.end_pos <= tag_start
|
|
97
|
+
|
|
98
|
+
# The range crosses into the tag; only the opening delimiter and trailing
|
|
99
|
+
# whitespace may be swallowed, and only a replacement can be reshaped.
|
|
100
|
+
return unless replacement
|
|
101
|
+
|
|
102
|
+
preserved = source[tag_start...range.end_pos].to_s
|
|
103
|
+
return unless preserved.match?(/\A<%[-=]*\s*\z/)
|
|
104
|
+
|
|
105
|
+
new_length = replacement.length - preserved.length
|
|
106
|
+
return if new_length.negative?
|
|
107
|
+
|
|
108
|
+
new_range = ::Parser::Source::Range.new(range.source_buffer, range.begin_pos, tag_start)
|
|
109
|
+
[new_range, attributes.merge(replacement: ' ' * new_length)]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
Cop::Corrector.prepend(Corrector)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Erb
|
|
5
|
+
class ERBSource < ProcessedSource
|
|
6
|
+
attr_reader :herb_parse_result, :template_source
|
|
7
|
+
|
|
8
|
+
def initialize(
|
|
9
|
+
template_source,
|
|
10
|
+
...
|
|
11
|
+
) # rubocop:disable Layout/SpaceInsideParens
|
|
12
|
+
@template_source = template_source
|
|
13
|
+
@herb_parse_result = Herb.parse(template_source)
|
|
14
|
+
|
|
15
|
+
super(...)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param [Integer] byte_offset offset into the (original) source in bytes
|
|
19
|
+
# @return [Integer] the equivalent character offset
|
|
20
|
+
def byte_to_char_offset(byte_offset)
|
|
21
|
+
template_source.byteslice(0, byte_offset).length
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param [Integer] position full position in the buffer
|
|
25
|
+
# @return [Herb::AST::Node, nil] the ERB node whose tag contains the position
|
|
26
|
+
def erb_node_for_pos(position)
|
|
27
|
+
erb_node_offsets.bsearch do |(range, _node)|
|
|
28
|
+
next -1 if position < range.begin
|
|
29
|
+
next 1 if position >= range.end
|
|
30
|
+
|
|
31
|
+
0
|
|
32
|
+
end&.last
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Buffer ranges of every ERB tag (`<% … %>`), paired with their node.
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<(Range, Herb::AST::Node)>]
|
|
38
|
+
def erb_node_offsets
|
|
39
|
+
@erb_node_offsets ||= [].tap { |offsets| ERBNodeOffsetVisitor.new(self, offsets).visit(erb_root) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Herb::AST::DocumentNode]
|
|
43
|
+
def erb_root
|
|
44
|
+
herb_parse_result.value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Prefer {#herb_range_to_parser_range}: Herb ranges are always byte offsets,
|
|
48
|
+
# while the unit of Herb position columns differs between herb versions
|
|
49
|
+
# (e.g. bytes in 0.9.x and characters in 0.11.x for multi-byte Ruby code).
|
|
50
|
+
#
|
|
51
|
+
# @param [Herb::Location] location
|
|
52
|
+
# @return [Parser::Source::Range]
|
|
53
|
+
def herb_location_to_parser_range(location)
|
|
54
|
+
Parser::Source::Range.new(buffer,
|
|
55
|
+
herb_position_to_buffer_pos(location.start),
|
|
56
|
+
herb_position_to_buffer_pos(location.end))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param [Herb::Position] position
|
|
60
|
+
# @return [Integer]
|
|
61
|
+
def herb_position_to_buffer_pos(position)
|
|
62
|
+
line_range = buffer.line_range(position.line)
|
|
63
|
+
# Herb reports columns as byte offsets, but the parser buffer uses
|
|
64
|
+
# character offsets; convert so multi-byte characters line up.
|
|
65
|
+
char_column = line_range.source.byteslice(0, position.column).length
|
|
66
|
+
line_range.begin.begin_pos + char_column
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param [Herb::Position] position
|
|
70
|
+
# @return [Parser::Source::Range] zero-width range at the position
|
|
71
|
+
def herb_position_to_parser_range(position)
|
|
72
|
+
pos = herb_position_to_buffer_pos(position)
|
|
73
|
+
Parser::Source::Range.new(buffer, pos, pos)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @param [Herb::Range] range
|
|
77
|
+
# @return [Parser::Source::Range]
|
|
78
|
+
def herb_range_to_parser_range(range)
|
|
79
|
+
# Herb ranges are byte offsets into the source; the parser buffer uses
|
|
80
|
+
# character offsets, so convert to keep multi-byte characters aligned.
|
|
81
|
+
Parser::Source::Range.new(buffer, byte_to_char_offset(range.from), byte_to_char_offset(range.to))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Boolean] whether the buffer range lies entirely within the Ruby
|
|
85
|
+
# content (between the delimiters) of a single ERB tag
|
|
86
|
+
def range_within_ruby_content?(
|
|
87
|
+
begin_pos,
|
|
88
|
+
end_pos
|
|
89
|
+
)
|
|
90
|
+
return false unless (node = erb_node_for_pos(begin_pos)) && (content = node.content)
|
|
91
|
+
|
|
92
|
+
content_range = herb_range_to_parser_range(content.range)
|
|
93
|
+
begin_pos >= content_range.begin_pos && end_pos <= content_range.end_pos
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Convert various Herb node or range types to a Parser::Source::Range
|
|
97
|
+
# Unrecognized values are passed through
|
|
98
|
+
#
|
|
99
|
+
# @param [#range, #location, Herb::Location, Herb::Range, Object] node_or_range
|
|
100
|
+
# @return [Parser::Source::Range, Object, nil]
|
|
101
|
+
def to_range(node_or_range)
|
|
102
|
+
node_or_range = node_or_range.range if node_or_range.respond_to?(:range)
|
|
103
|
+
node_or_range = herb_node_range(node_or_range) || node_or_range.location if node_or_range.is_a?(Herb::AST::Node)
|
|
104
|
+
node_or_range = node_or_range.location if node_or_range.is_a?(Herb::Token)
|
|
105
|
+
node_or_range = herb_location_to_parser_range(node_or_range) if node_or_range.is_a?(Herb::Location)
|
|
106
|
+
node_or_range = herb_range_to_parser_range(node_or_range) if node_or_range.is_a?(Herb::Range)
|
|
107
|
+
node_or_range = herb_position_to_parser_range(node_or_range) if node_or_range.is_a?(Herb::Position)
|
|
108
|
+
|
|
109
|
+
node_or_range
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Herb AST nodes have no byte range of their own, so derive one from the
|
|
115
|
+
# tokens of an ERB node when possible.
|
|
116
|
+
#
|
|
117
|
+
# @param [Herb::AST::Node] node
|
|
118
|
+
# @return [Herb::Range, nil]
|
|
119
|
+
def herb_node_range(node)
|
|
120
|
+
return unless node.respond_to?(:tag_opening) && node.respond_to?(:tag_closing)
|
|
121
|
+
return unless node.tag_opening && node.tag_closing
|
|
122
|
+
|
|
123
|
+
Herb::Range.new(node.tag_opening.range.from, node.tag_closing.range.to)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Collects the buffer range of every ERB tag, paired with its node.
|
|
127
|
+
class ERBNodeOffsetVisitor < Herb::Visitor
|
|
128
|
+
def initialize(
|
|
129
|
+
source,
|
|
130
|
+
offsets
|
|
131
|
+
)
|
|
132
|
+
@source = source
|
|
133
|
+
@offsets = offsets
|
|
134
|
+
super()
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def visit_erb_node(node)
|
|
138
|
+
@offsets << [
|
|
139
|
+
Range.new(@source.byte_to_char_offset(node.tag_opening.range.from),
|
|
140
|
+
@source.byte_to_char_offset(node.tag_closing.range.to),
|
|
141
|
+
true),
|
|
142
|
+
node
|
|
143
|
+
]
|
|
144
|
+
super
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
private_constant :ERBNodeOffsetVisitor
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -5,7 +5,7 @@ require 'rubocop'
|
|
|
5
5
|
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Erb
|
|
8
|
-
# Extract Ruby codes from
|
|
8
|
+
# Extract Ruby codes from ERB template.
|
|
9
9
|
class RubyExtractor
|
|
10
10
|
class << self
|
|
11
11
|
# @param [RuboCop::ProcessedSource] processed_source
|
|
@@ -24,54 +24,50 @@ module RuboCop
|
|
|
24
24
|
def call
|
|
25
25
|
return unless supported_file_path_pattern?
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
new_processed_source = ERBSource.new(template_source,
|
|
28
|
+
extracted_ruby,
|
|
29
|
+
@processed_source.ruby_version,
|
|
30
|
+
@processed_source.path,
|
|
31
|
+
parser_engine: @processed_source.parser_engine)
|
|
32
|
+
new_processed_source.config = @processed_source.config
|
|
33
|
+
new_processed_source.registry = @processed_source.registry
|
|
34
|
+
[{
|
|
35
|
+
offset: 0,
|
|
36
|
+
processed_source: new_processed_source
|
|
37
|
+
}]
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
private
|
|
39
41
|
|
|
42
|
+
# The extracted Ruby, character-aligned with the template.
|
|
43
|
+
#
|
|
44
|
+
# `Herb.extract_ruby` blanks non-Ruby bytes to spaces, which expands every
|
|
45
|
+
# multi-byte character (e.g. an emoji in the HTML) into several spaces and
|
|
46
|
+
# shifts the position of all following characters. Since corrections map
|
|
47
|
+
# extracted positions back to the template 1:1, re-collapse those runs so a
|
|
48
|
+
# multi-byte source character stays a single character.
|
|
49
|
+
#
|
|
50
|
+
# @return [String]
|
|
51
|
+
def extracted_ruby
|
|
52
|
+
extracted = Herb.extract_ruby(template_source)
|
|
53
|
+
return extracted if extracted.length == template_source.length
|
|
54
|
+
|
|
55
|
+
realigned = +''
|
|
56
|
+
byte_index = 0
|
|
57
|
+
template_source.each_char do |char|
|
|
58
|
+
size = char.bytesize
|
|
59
|
+
chunk = extracted.byteslice(byte_index, size)
|
|
60
|
+
realigned << (size == 1 || chunk == char ? chunk : ' ')
|
|
61
|
+
byte_index += size
|
|
62
|
+
end
|
|
63
|
+
realigned
|
|
64
|
+
end
|
|
65
|
+
|
|
40
66
|
# @return [String, nil]
|
|
41
67
|
def file_path
|
|
42
68
|
@processed_source.path
|
|
43
69
|
end
|
|
44
70
|
|
|
45
|
-
# @return [Array<Herb::AST::Node>]
|
|
46
|
-
def nodes
|
|
47
|
-
visitor = ErbNodeVisitor.new
|
|
48
|
-
visitor.visit(root)
|
|
49
|
-
visitor.erb_nodes
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# @return [Herb::AST::DocumentNode]
|
|
53
|
-
def root
|
|
54
|
-
::Herb.parse(template_source).value
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# @return [Array<RuboCop::Erb::RubyClip>]
|
|
58
|
-
def ruby_clips
|
|
59
|
-
nodes.map do |node|
|
|
60
|
-
erb_start_location = node.content.location.start
|
|
61
|
-
line_range = @processed_source.buffer.line_range(erb_start_location.line)
|
|
62
|
-
RubyClip.new(
|
|
63
|
-
code: node.content.value,
|
|
64
|
-
offset: line_range.begin.begin_pos + erb_start_location.column
|
|
65
|
-
)
|
|
66
|
-
end.flat_map do |ruby_clip|
|
|
67
|
-
WhenDecomposer.call(@processed_source, ruby_clip)
|
|
68
|
-
end.map do |ruby_clip|
|
|
69
|
-
KeywordRemover.call(ruby_clip)
|
|
70
|
-
end.reject do |ruby_clip|
|
|
71
|
-
ruby_clip.code.match?(/\A\s*\z/)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
71
|
# @return [Boolean]
|
|
76
72
|
def supported_file_path_pattern?
|
|
77
73
|
file_path&.end_with?('.erb')
|
|
@@ -81,46 +77,6 @@ module RuboCop
|
|
|
81
77
|
def template_source
|
|
82
78
|
@processed_source.raw_source
|
|
83
79
|
end
|
|
84
|
-
|
|
85
|
-
class ErbNodeVisitor < Herb::Visitor
|
|
86
|
-
# @return [Array<Symbol>]
|
|
87
|
-
def self.erb_visitor_methods
|
|
88
|
-
instance_methods.select { |method_name| method_name.to_s.start_with?('visit_erb_') }
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
attr_reader :erb_nodes
|
|
92
|
-
|
|
93
|
-
def initialize
|
|
94
|
-
@erb_nodes = []
|
|
95
|
-
super
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# @return [Boolean]
|
|
99
|
-
def comment?(node)
|
|
100
|
-
node.tag_opening&.value == '<%#'
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# @return [Boolean]
|
|
104
|
-
def escape?(node)
|
|
105
|
-
node.tag_opening&.value == '<%%'
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
erb_visitor_methods.each do |method_name|
|
|
109
|
-
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
110
|
-
def #{method_name}(node) # def visit_erb_content_node(node)
|
|
111
|
-
record_node(node) # record_node(node)
|
|
112
|
-
super # super
|
|
113
|
-
end # end
|
|
114
|
-
RUBY
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def record_node(node)
|
|
118
|
-
return unless node.respond_to?(:content) && node.content
|
|
119
|
-
return if comment?(node) || escape?(node)
|
|
120
|
-
|
|
121
|
-
@erb_nodes << node
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
80
|
end
|
|
125
81
|
end
|
|
126
82
|
end
|
data/lib/rubocop/erb/version.rb
CHANGED
data/lib/rubocop/erb.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'herb'
|
|
4
|
+
require 'rubocop'
|
|
5
|
+
|
|
3
6
|
module RuboCop
|
|
4
7
|
module Erb
|
|
5
|
-
autoload :
|
|
6
|
-
autoload :ProcessedSourceBuilder, 'rubocop/erb/processed_source_builder'
|
|
7
|
-
autoload :RubyClip, 'rubocop/erb/ruby_clip'
|
|
8
|
+
autoload :ERBSource, 'rubocop/erb/erb_source'
|
|
8
9
|
autoload :RubyExtractor, 'rubocop/erb/ruby_extractor'
|
|
9
|
-
autoload :WhenDecomposer, 'rubocop/erb/when_decomposer'
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
require_relative 'erb/plugin'
|
|
14
14
|
require_relative 'erb/version'
|
|
15
|
+
require_relative 'erb/corrector'
|
data/rubocop-erb.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = 'RuboCop plugin for ERB template.'
|
|
12
12
|
spec.homepage = 'https://github.com/r7kamura/rubocop-erb'
|
|
13
13
|
spec.license = 'MIT'
|
|
14
|
-
spec.required_ruby_version = '>= 3.
|
|
14
|
+
spec.required_ruby_version = '>= 3.3'
|
|
15
15
|
|
|
16
16
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
17
|
spec.metadata['source_code_uri'] = spec.homepage
|
|
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
30
|
spec.require_paths = ['lib']
|
|
31
31
|
|
|
32
|
-
spec.add_dependency 'herb', '~> 0.
|
|
32
|
+
spec.add_dependency 'herb', '~> 0.9', '>= 0.9.3'
|
|
33
33
|
spec.add_dependency 'lint_roller', '~> 1.1'
|
|
34
|
-
spec.add_dependency 'rubocop', '~> 1.
|
|
34
|
+
spec.add_dependency 'rubocop', '~> 1.84'
|
|
35
35
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-erb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryo Nakamura
|
|
@@ -15,14 +15,20 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.9'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.9.3
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - "~>"
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
28
|
+
version: '0.9'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.9.3
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: lint_roller
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -43,20 +49,14 @@ dependencies:
|
|
|
43
49
|
requirements:
|
|
44
50
|
- - "~>"
|
|
45
51
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '1.
|
|
47
|
-
- - ">="
|
|
48
|
-
- !ruby/object:Gem::Version
|
|
49
|
-
version: 1.72.1
|
|
52
|
+
version: '1.84'
|
|
50
53
|
type: :runtime
|
|
51
54
|
prerelease: false
|
|
52
55
|
version_requirements: !ruby/object:Gem::Requirement
|
|
53
56
|
requirements:
|
|
54
57
|
- - "~>"
|
|
55
58
|
- !ruby/object:Gem::Version
|
|
56
|
-
version: '1.
|
|
57
|
-
- - ">="
|
|
58
|
-
- !ruby/object:Gem::Version
|
|
59
|
-
version: 1.72.1
|
|
59
|
+
version: '1.84'
|
|
60
60
|
email:
|
|
61
61
|
- r7kamura@gmail.com
|
|
62
62
|
executables: []
|
|
@@ -73,13 +73,11 @@ files:
|
|
|
73
73
|
- Rakefile
|
|
74
74
|
- config/default.yml
|
|
75
75
|
- lib/rubocop/erb.rb
|
|
76
|
-
- lib/rubocop/erb/
|
|
76
|
+
- lib/rubocop/erb/corrector.rb
|
|
77
|
+
- lib/rubocop/erb/erb_source.rb
|
|
77
78
|
- lib/rubocop/erb/plugin.rb
|
|
78
|
-
- lib/rubocop/erb/processed_source_builder.rb
|
|
79
|
-
- lib/rubocop/erb/ruby_clip.rb
|
|
80
79
|
- lib/rubocop/erb/ruby_extractor.rb
|
|
81
80
|
- lib/rubocop/erb/version.rb
|
|
82
|
-
- lib/rubocop/erb/when_decomposer.rb
|
|
83
81
|
- rubocop-erb.gemspec
|
|
84
82
|
homepage: https://github.com/r7kamura/rubocop-erb
|
|
85
83
|
licenses:
|
|
@@ -96,14 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
96
94
|
requirements:
|
|
97
95
|
- - ">="
|
|
98
96
|
- !ruby/object:Gem::Version
|
|
99
|
-
version: '3.
|
|
97
|
+
version: '3.3'
|
|
100
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
99
|
requirements:
|
|
102
100
|
- - ">="
|
|
103
101
|
- !ruby/object:Gem::Version
|
|
104
102
|
version: '0'
|
|
105
103
|
requirements: []
|
|
106
|
-
rubygems_version:
|
|
104
|
+
rubygems_version: 4.0.20
|
|
107
105
|
specification_version: 4
|
|
108
106
|
summary: RuboCop plugin for ERB template.
|
|
109
107
|
test_files: []
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Erb
|
|
5
|
-
# Remove unnecessary keyword part (e.g. `if`, `unless`, `do`, ...) from Ruby-ish code.
|
|
6
|
-
class KeywordRemover
|
|
7
|
-
class << self
|
|
8
|
-
# @param [RuboCop::Erb::RubyClip] ruby_clip
|
|
9
|
-
# @return [RuboCop::Erb::RubyClip]
|
|
10
|
-
def call(ruby_clip)
|
|
11
|
-
new(ruby_clip).call
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# @param [RuboCop::Erb::RubyClip] ruby_clip
|
|
16
|
-
def initialize(ruby_clip)
|
|
17
|
-
@ruby_clip = ruby_clip
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# @return [RuboCop::Erb::RubyClip]
|
|
21
|
-
def call
|
|
22
|
-
[
|
|
23
|
-
PrecedingKeywordRemover,
|
|
24
|
-
PrecedingBraceRemover,
|
|
25
|
-
TrailingBraceRemover,
|
|
26
|
-
TrailingThenRemover,
|
|
27
|
-
TrailingDoRemover
|
|
28
|
-
].reduce(@ruby_clip) do |previous, callable|
|
|
29
|
-
result = callable.call(previous.code)
|
|
30
|
-
RubyClip.new(
|
|
31
|
-
code: result.code,
|
|
32
|
-
offset: previous.offset + result.offset
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
class PrecedingSourceRemover
|
|
38
|
-
class << self
|
|
39
|
-
# @param [String] code
|
|
40
|
-
# @return [RubyClip]
|
|
41
|
-
def call(code)
|
|
42
|
-
new(code).call
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# @param [String] code
|
|
47
|
-
def initialize(code)
|
|
48
|
-
@code = code
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# @return [Hash]
|
|
52
|
-
def call
|
|
53
|
-
data = @code.match(self.class::REGEXP)
|
|
54
|
-
if data
|
|
55
|
-
offset = data[0].length
|
|
56
|
-
RubyClip.new(
|
|
57
|
-
code: @code[offset..],
|
|
58
|
-
offset: offset
|
|
59
|
-
)
|
|
60
|
-
else
|
|
61
|
-
RubyClip.new(
|
|
62
|
-
code: @code,
|
|
63
|
-
offset: 0
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Remove preceding keyword.
|
|
70
|
-
class PrecedingKeywordRemover < PrecedingSourceRemover
|
|
71
|
-
REGEXP = /
|
|
72
|
-
\A
|
|
73
|
-
\s*
|
|
74
|
-
(?:
|
|
75
|
-
begin
|
|
76
|
-
| case
|
|
77
|
-
| else
|
|
78
|
-
| elsif
|
|
79
|
-
| end
|
|
80
|
-
| ensure
|
|
81
|
-
| if
|
|
82
|
-
| rescue
|
|
83
|
-
| unless
|
|
84
|
-
| until
|
|
85
|
-
| when
|
|
86
|
-
| while
|
|
87
|
-
| for[ \t]+\w+[ \t]+in
|
|
88
|
-
)
|
|
89
|
-
\b[ \t]*
|
|
90
|
-
/x
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Remove preceding `}`.
|
|
94
|
-
class PrecedingBraceRemover < PrecedingSourceRemover
|
|
95
|
-
REGEXP = /
|
|
96
|
-
\A
|
|
97
|
-
\s*
|
|
98
|
-
}
|
|
99
|
-
/x
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
class TrailingSourceRemover
|
|
103
|
-
class << self
|
|
104
|
-
# @param [String] code
|
|
105
|
-
# @return [RubyClip]
|
|
106
|
-
def call(code)
|
|
107
|
-
new(code).call
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# @param [String] code
|
|
112
|
-
def initialize(code)
|
|
113
|
-
@code = code
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# @return [Hash]
|
|
117
|
-
def call
|
|
118
|
-
RubyClip.new(
|
|
119
|
-
code: @code.sub(self.class::REGEXP, ''),
|
|
120
|
-
offset: 0
|
|
121
|
-
)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Remove trailing `{`.
|
|
126
|
-
class TrailingBraceRemover < TrailingSourceRemover
|
|
127
|
-
REGEXP = /
|
|
128
|
-
{
|
|
129
|
-
[ \t]*
|
|
130
|
-
(?:\|[^|]*\|)?
|
|
131
|
-
\s*
|
|
132
|
-
\z
|
|
133
|
-
/x
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Remove trailing `then`.
|
|
137
|
-
class TrailingThenRemover < TrailingSourceRemover
|
|
138
|
-
REGEXP = /
|
|
139
|
-
[ \t]*\b
|
|
140
|
-
then
|
|
141
|
-
\s*
|
|
142
|
-
\z
|
|
143
|
-
/x
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Remove trailing `do`.
|
|
147
|
-
class TrailingDoRemover < TrailingSourceRemover
|
|
148
|
-
REGEXP = /
|
|
149
|
-
(?:\b[ \t]*|[ \t])
|
|
150
|
-
do
|
|
151
|
-
[ \t]*
|
|
152
|
-
(?:\|[^|]*\|)?
|
|
153
|
-
\s*
|
|
154
|
-
(\#.*)?
|
|
155
|
-
\z
|
|
156
|
-
/x
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Erb
|
|
5
|
-
class ProcessedSourceBuilder
|
|
6
|
-
class << self
|
|
7
|
-
# Creates a new ProcessedSource, inheriting state from a donor.
|
|
8
|
-
#
|
|
9
|
-
# @param [String] code
|
|
10
|
-
# @param [RuboCop::ProcessedSource] processed_source
|
|
11
|
-
# @return [RuboCop::ProcessedSource]
|
|
12
|
-
def call(
|
|
13
|
-
code:,
|
|
14
|
-
processed_source:
|
|
15
|
-
)
|
|
16
|
-
new(
|
|
17
|
-
code: code,
|
|
18
|
-
processed_source: processed_source
|
|
19
|
-
).call
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def initialize(
|
|
24
|
-
code:,
|
|
25
|
-
processed_source:
|
|
26
|
-
)
|
|
27
|
-
@code = code
|
|
28
|
-
@processed_source = processed_source
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def call
|
|
32
|
-
new_processed_source = ::RuboCop::ProcessedSource.new(
|
|
33
|
-
@code,
|
|
34
|
-
@processed_source.ruby_version,
|
|
35
|
-
@processed_source.path,
|
|
36
|
-
parser_engine: @processed_source.parser_engine
|
|
37
|
-
)
|
|
38
|
-
new_processed_source.config = @processed_source.config
|
|
39
|
-
new_processed_source.registry = @processed_source.registry
|
|
40
|
-
new_processed_source
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Erb
|
|
5
|
-
class WhenDecomposer
|
|
6
|
-
REGEXP = /
|
|
7
|
-
\A
|
|
8
|
-
\s*
|
|
9
|
-
when[ \t]
|
|
10
|
-
/x
|
|
11
|
-
|
|
12
|
-
class << self
|
|
13
|
-
# @param [RuboCop::ProcessedSource] processed_source
|
|
14
|
-
# @param [RuboCop::Erb::RubyClip] ruby_clip
|
|
15
|
-
# @return [Array<RuboCop::Erb::RubyClip>]
|
|
16
|
-
def call(
|
|
17
|
-
processed_source,
|
|
18
|
-
ruby_clip
|
|
19
|
-
)
|
|
20
|
-
new(processed_source, ruby_clip).call
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# @param [RuboCop::ProcessedSource] processed_source
|
|
25
|
-
# @param [RuboCop::Erb::RubyClip] ruby_clip
|
|
26
|
-
def initialize(
|
|
27
|
-
processed_source,
|
|
28
|
-
ruby_clip
|
|
29
|
-
)
|
|
30
|
-
@processed_source = processed_source
|
|
31
|
-
@ruby_clip = ruby_clip
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# @return [Array<RuboCop::Erb::RubyClip>]
|
|
35
|
-
def call
|
|
36
|
-
match_data = @ruby_clip.code.match(REGEXP)
|
|
37
|
-
if match_data
|
|
38
|
-
offset = match_data[0].length
|
|
39
|
-
condition = @ruby_clip.code[offset..].sub(/[ \t]then(?:[ \t].*)?/, '')
|
|
40
|
-
nodes = parse(
|
|
41
|
-
<<~RUBY
|
|
42
|
-
[
|
|
43
|
-
#{condition}
|
|
44
|
-
]
|
|
45
|
-
RUBY
|
|
46
|
-
)&.children || []
|
|
47
|
-
|
|
48
|
-
nodes.map do |child|
|
|
49
|
-
RubyClip.new(
|
|
50
|
-
code: child.location.expression.source,
|
|
51
|
-
offset: @ruby_clip.offset + offset + child.location.expression.begin_pos - 4
|
|
52
|
-
)
|
|
53
|
-
end
|
|
54
|
-
else
|
|
55
|
-
[@ruby_clip]
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
# @param [String] source
|
|
62
|
-
# @return [RuboCop::AST::Node]
|
|
63
|
-
def parse(source)
|
|
64
|
-
ProcessedSourceBuilder.call(
|
|
65
|
-
code: source,
|
|
66
|
-
processed_source: @processed_source
|
|
67
|
-
).ast
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|