rubocop-erb 0.7.2 → 0.8.0
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 +51 -10
- 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: d829909cc60a55799ce1976340a7e82e7cb40f82e379de1ef8fd1b8cc3395ee0
|
|
4
|
+
data.tar.gz: 7712047a8cf2e03e4da145dd477f197a0697dbf300adc9728ab5d5fab26756cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a653d659c624405db7226d3501bab436656eeca3ed31d84e6cad8838141d6c9dd384af376e6bfe30307da0b8365dee24ee70d376f47e2da1ae44a2e160dd7cb
|
|
7
|
+
data.tar.gz: deb517d509c2e611f04a1555db766d076793a4e01f42c42697b95a5ed5a222ff6e6824711c6b613fc02a53149aafb487819378e39841ffeb1b0385662c097899
|
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.0)
|
|
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,46 +137,49 @@ Layout/ParameterAlignment:
|
|
|
123
137
|
Exclude:
|
|
124
138
|
- '**/*.erb'
|
|
125
139
|
|
|
126
|
-
Layout/
|
|
140
|
+
Layout/SpaceBeforeSemicolon:
|
|
127
141
|
Exclude:
|
|
128
142
|
- '**/*.erb'
|
|
129
143
|
|
|
130
|
-
|
|
144
|
+
# Non-Ruby content gives false positives for this cop.
|
|
145
|
+
Layout/TrailingEmptyLines:
|
|
131
146
|
Exclude:
|
|
132
147
|
- '**/*.erb'
|
|
133
148
|
|
|
134
|
-
|
|
149
|
+
Layout/TrailingWhitespace:
|
|
135
150
|
Exclude:
|
|
136
151
|
- '**/*.erb'
|
|
137
152
|
|
|
138
|
-
Lint/
|
|
153
|
+
Lint/DuplicateBranch:
|
|
139
154
|
Exclude:
|
|
140
155
|
- '**/*.erb'
|
|
141
156
|
|
|
142
|
-
Lint/
|
|
157
|
+
Lint/EmptyBlock:
|
|
143
158
|
Exclude:
|
|
144
159
|
- '**/*.erb'
|
|
145
160
|
|
|
146
|
-
Lint/
|
|
161
|
+
Lint/EmptyConditionalBody:
|
|
147
162
|
Exclude:
|
|
148
163
|
- '**/*.erb'
|
|
149
164
|
|
|
150
|
-
|
|
165
|
+
# Non-Ruby content gives false positives for this cop.
|
|
166
|
+
Lint/EmptyFile:
|
|
151
167
|
Exclude:
|
|
152
168
|
- '**/*.erb'
|
|
153
169
|
|
|
154
|
-
|
|
170
|
+
Lint/EmptyWhen:
|
|
155
171
|
Exclude:
|
|
156
172
|
- '**/*.erb'
|
|
157
173
|
|
|
158
|
-
|
|
174
|
+
Lint/Void:
|
|
159
175
|
Exclude:
|
|
160
176
|
- '**/*.erb'
|
|
161
177
|
|
|
162
|
-
|
|
178
|
+
Style/EmptyElse:
|
|
163
179
|
Exclude:
|
|
164
180
|
- '**/*.erb'
|
|
165
181
|
|
|
182
|
+
# ERB doesn't support magic comments.
|
|
166
183
|
Style/FrozenStringLiteralComment:
|
|
167
184
|
Exclude:
|
|
168
185
|
- '**/*.erb'
|
|
@@ -171,10 +188,18 @@ Style/IdenticalConditionalBranches:
|
|
|
171
188
|
Exclude:
|
|
172
189
|
- '**/*.erb'
|
|
173
190
|
|
|
191
|
+
Style/IfInsideElse:
|
|
192
|
+
Exclude:
|
|
193
|
+
- '**/*.erb'
|
|
194
|
+
|
|
174
195
|
Style/IfUnlessModifier:
|
|
175
196
|
Exclude:
|
|
176
197
|
- '**/*.erb'
|
|
177
198
|
|
|
199
|
+
Style/IfWithSemicolon:
|
|
200
|
+
Exclude:
|
|
201
|
+
- '**/*.erb'
|
|
202
|
+
|
|
178
203
|
Style/MultilineTernaryOperator:
|
|
179
204
|
Exclude:
|
|
180
205
|
- '**/*.erb'
|
|
@@ -191,14 +216,30 @@ Style/ParallelAssignment:
|
|
|
191
216
|
Exclude:
|
|
192
217
|
- '**/*.erb'
|
|
193
218
|
|
|
219
|
+
Style/RedundantCondition:
|
|
220
|
+
Exclude:
|
|
221
|
+
- '**/*.erb'
|
|
222
|
+
|
|
194
223
|
Style/RescueModifier:
|
|
195
224
|
Exclude:
|
|
196
225
|
- '**/*.erb'
|
|
197
226
|
|
|
227
|
+
Style/SafeNavigation:
|
|
228
|
+
Exclude:
|
|
229
|
+
- '**/*.erb'
|
|
230
|
+
|
|
198
231
|
Style/Semicolon:
|
|
199
232
|
Exclude:
|
|
200
233
|
- '**/*.erb'
|
|
201
234
|
|
|
235
|
+
Style/SoleNestedConditional:
|
|
236
|
+
Exclude:
|
|
237
|
+
- '**/*.erb'
|
|
238
|
+
|
|
239
|
+
Style/SymbolProc:
|
|
240
|
+
Exclude:
|
|
241
|
+
- '**/*.erb'
|
|
242
|
+
|
|
202
243
|
Style/TrailingCommaInArguments:
|
|
203
244
|
Exclude:
|
|
204
245
|
- '**/*.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.0
|
|
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
|