w_syntax_tree-erb 0.9.5 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.husky/pre-commit +4 -0
- data/.vscode/launch.json +25 -0
- data/CHANGELOG.md +23 -1
- data/Gemfile.lock +2 -2
- data/README.md +16 -1
- data/lib/syntax_tree/erb/format.rb +152 -23
- data/lib/syntax_tree/erb/nodes.rb +189 -48
- data/lib/syntax_tree/erb/parser.rb +113 -24
- data/lib/syntax_tree/erb/pretty_print.rb +9 -1
- data/lib/syntax_tree/erb/version.rb +1 -1
- data/lib/syntax_tree/erb.rb +1 -0
- data/package.json +10 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a76706871104d4d2ea966b498103883d197378ec7e93fb339430b82d8e70d7ee
|
4
|
+
data.tar.gz: 2d78160b39db8ddf8a62aaa132c3d935870d5db8f2383250a0502d7475d1bd1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 111165a4191e3dd16baf66ef3ab1b1607a4ea1303113a63b5bf01a49d6b0ff0949ff313fb3ba6ada0e42a04de7809294738dc6b079537590915b3b764ac4b030
|
7
|
+
data.tar.gz: 8ff9ea2143768e7eb7ee067688b4ed1b33f05412f8bf381782376586edc7d958f3f53c4c28042320f583408f0775bee00ecedda7df8c145598b874cf8bcf2046
|
data/.husky/pre-commit
ADDED
data/.vscode/launch.json
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
3
|
+
// Hover to view descriptions of existing attributes.
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
5
|
+
"version": "0.2.0",
|
6
|
+
"configurations": [
|
7
|
+
{
|
8
|
+
"type": "ruby_lsp",
|
9
|
+
"name": "Debug file",
|
10
|
+
"request": "launch",
|
11
|
+
"program": "ruby ${file}"
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"type": "ruby_lsp",
|
15
|
+
"name": "Debug test",
|
16
|
+
"request": "launch",
|
17
|
+
"program": "ruby -Itest ${relativeFile}"
|
18
|
+
},
|
19
|
+
{
|
20
|
+
"type": "ruby_lsp",
|
21
|
+
"name": "Debug attach",
|
22
|
+
"request": "attach"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.10.1] - 2023-08-20
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Allow `DOCTYPE` to be after other tags, to work with e.g. ERB-tags on first line.
|
14
|
+
|
15
|
+
## [0.10.0] - 2023-08-20
|
16
|
+
|
17
|
+
- Changes how whitespace and newlines are handled.
|
18
|
+
- Supports syntax like:
|
19
|
+
|
20
|
+
```erb
|
21
|
+
<%= part %> / <%= total %> (<%= percentage %>%)
|
22
|
+
```
|
23
|
+
|
9
24
|
## [0.9.5] - 2023-07-02
|
10
25
|
|
11
26
|
- Fixes ruby comment in ERB-tag included VoidStatement
|
@@ -61,5 +76,12 @@ Output:
|
|
61
76
|
- Can format a lot of .html.erb-syntax and works as a plugin to syntax_tree.
|
62
77
|
- This is still early and there are a lot of different weird syntaxes out there.
|
63
78
|
|
64
|
-
[unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.
|
79
|
+
[unreleased]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.10.1...HEAD
|
80
|
+
[0.10.1]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.10.0...v0.10.1
|
81
|
+
[0.10.0]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.5...v0.10.0
|
82
|
+
[0.9.5]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.4...v0.9.5
|
83
|
+
[0.9.4]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.3...v0.9.4
|
84
|
+
[0.9.3]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.2...v0.9.3
|
85
|
+
[0.9.2]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.1...v0.9.2
|
86
|
+
[0.9.1]: https://github.com/davidwessman/syntax_tree-erb/compare/v0.9.0...v0.9.1
|
65
87
|
[0.9.0]: https://github.com/davidwessman/syntax_tree-erb/compare/419727a73af94057ca0980733e69ac8b4d52fdf4...v0.9.0
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
w_syntax_tree-erb (0.
|
4
|
+
w_syntax_tree-erb (0.10.1)
|
5
5
|
prettier_print (~> 1.2, >= 1.2.0)
|
6
6
|
syntax_tree (~> 6.1, >= 6.1.1)
|
7
7
|
|
@@ -9,7 +9,7 @@ GEM
|
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
11
|
docile (1.4.0)
|
12
|
-
minitest (5.
|
12
|
+
minitest (5.19.0)
|
13
13
|
prettier_print (1.2.1)
|
14
14
|
rake (13.0.6)
|
15
15
|
simplecov (0.22.0)
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ Currently handles
|
|
30
30
|
Add this line to your application's Gemfile:
|
31
31
|
|
32
32
|
```ruby
|
33
|
-
gem "w_syntax_tree-erb", "~> 0.
|
33
|
+
gem "w_syntax_tree-erb", "~> 0.10", require: false
|
34
34
|
```
|
35
35
|
|
36
36
|
> I added the `w_` prefix to avoid conflicts if there will ever be an official `syntax_tree-erb` gem.
|
@@ -77,6 +77,21 @@ Dir
|
|
77
77
|
puts failures
|
78
78
|
```
|
79
79
|
|
80
|
+
## Development
|
81
|
+
|
82
|
+
Setup linting:
|
83
|
+
|
84
|
+
```sh
|
85
|
+
npm run prepare
|
86
|
+
```
|
87
|
+
|
88
|
+
Install dependencies and run tests:
|
89
|
+
|
90
|
+
```sh
|
91
|
+
bundle
|
92
|
+
bundle exec rake
|
93
|
+
```
|
94
|
+
|
80
95
|
## Contributing
|
81
96
|
|
82
97
|
Bug reports and pull requests are welcome on GitHub at https://github.com/davidwessman/syntax_tree-erb.
|
@@ -22,9 +22,7 @@ module SyntaxTree
|
|
22
22
|
def visit_document(node)
|
23
23
|
child_nodes = node.child_nodes.sort_by(&:location)
|
24
24
|
|
25
|
-
|
26
|
-
visit(child_node)
|
27
|
-
end
|
25
|
+
handle_child_nodes(child_nodes)
|
28
26
|
|
29
27
|
q.breakable(force: true)
|
30
28
|
end
|
@@ -32,31 +30,55 @@ module SyntaxTree
|
|
32
30
|
def visit_block(node)
|
33
31
|
visit(node.opening)
|
34
32
|
|
33
|
+
breakable = breakable_inside(node)
|
35
34
|
if node.elements.any?
|
36
35
|
q.indent do
|
37
|
-
q.breakable
|
38
|
-
|
39
|
-
node.elements,
|
40
|
-
-> { q.breakable(force: true) }
|
41
|
-
) { |child_node| visit(child_node) }
|
36
|
+
q.breakable if breakable
|
37
|
+
handle_child_nodes(node.elements)
|
42
38
|
end
|
43
39
|
end
|
44
40
|
|
45
41
|
if node.closing
|
46
|
-
q.breakable("")
|
42
|
+
q.breakable("") if breakable
|
47
43
|
visit(node.closing)
|
48
44
|
end
|
49
45
|
end
|
50
46
|
|
51
|
-
def
|
52
|
-
# Make sure to group the tags together if there is no child nodes.
|
47
|
+
def visit_html_groupable(node, group)
|
53
48
|
if node.elements.size == 0
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
visit(node.opening)
|
50
|
+
visit(node.closing)
|
51
|
+
else
|
52
|
+
visit(node.opening)
|
53
|
+
|
54
|
+
with_break = breakable_inside(node)
|
55
|
+
q.indent do
|
56
|
+
if with_break
|
57
|
+
group ? q.breakable("") : q.breakable
|
58
|
+
end
|
59
|
+
handle_child_nodes(node.elements)
|
60
|
+
end
|
61
|
+
|
62
|
+
if with_break
|
63
|
+
group ? q.breakable("") : q.breakable
|
57
64
|
end
|
65
|
+
visit(node.closing)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit_html(node)
|
70
|
+
# Make sure to group the tags together if there is no child nodes.
|
71
|
+
if node.elements.size == 0 ||
|
72
|
+
node.elements.any? { |node|
|
73
|
+
node.is_a?(SyntaxTree::ERB::CharData)
|
74
|
+
} ||
|
75
|
+
(
|
76
|
+
node.elements.size == 1 &&
|
77
|
+
node.elements.first.is_a?(SyntaxTree::ERB::ErbNode)
|
78
|
+
)
|
79
|
+
q.group { visit_html_groupable(node, true) }
|
58
80
|
else
|
59
|
-
|
81
|
+
visit_html_groupable(node, false)
|
60
82
|
end
|
61
83
|
end
|
62
84
|
|
@@ -146,7 +168,7 @@ module SyntaxTree
|
|
146
168
|
formatter.flush
|
147
169
|
rows = formatter.output.join.split("\n")
|
148
170
|
|
149
|
-
output_rows(
|
171
|
+
output_rows(rows)
|
150
172
|
end
|
151
173
|
|
152
174
|
def output_rows(rows)
|
@@ -170,9 +192,14 @@ module SyntaxTree
|
|
170
192
|
visit(child_node)
|
171
193
|
end
|
172
194
|
end
|
195
|
+
|
196
|
+
# Only add breakable if we have attributes
|
197
|
+
q.breakable(node.closing.value == "/>" ? " " : "")
|
198
|
+
elsif node.closing.value == "/>"
|
199
|
+
# Need a space before end-tag for self-closing
|
200
|
+
q.text(" ")
|
173
201
|
end
|
174
202
|
|
175
|
-
q.breakable(node.closing.value == "/>" ? " " : "")
|
176
203
|
visit(node.closing)
|
177
204
|
end
|
178
205
|
end
|
@@ -208,15 +235,20 @@ module SyntaxTree
|
|
208
235
|
visit(node.token)
|
209
236
|
end
|
210
237
|
|
238
|
+
def visit_erb_comment(node)
|
239
|
+
visit(node.token)
|
240
|
+
end
|
241
|
+
|
211
242
|
# Visit a CharData node.
|
212
243
|
def visit_char_data(node)
|
213
|
-
|
244
|
+
return if node.value.value.strip.empty?
|
214
245
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
246
|
+
q.text(node.value.value)
|
247
|
+
end
|
248
|
+
|
249
|
+
def visit_new_line(node)
|
250
|
+
q.breakable(force: :skip_parent_break)
|
251
|
+
q.breakable(force: :skip_parent_break) if node.count > 1
|
220
252
|
end
|
221
253
|
|
222
254
|
# Visit a Doctype node.
|
@@ -230,6 +262,49 @@ module SyntaxTree
|
|
230
262
|
end
|
231
263
|
end
|
232
264
|
|
265
|
+
private
|
266
|
+
|
267
|
+
def breakable_inside(node)
|
268
|
+
if node.is_a?(SyntaxTree::ERB::HtmlNode)
|
269
|
+
node.elements.first.class != SyntaxTree::ERB::CharData ||
|
270
|
+
node_new_line_count(node.opening) > 0
|
271
|
+
elsif node.is_a?(SyntaxTree::ERB::Block)
|
272
|
+
true
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def breakable_between(node, next_node)
|
277
|
+
new_lines = node_new_line_count(node)
|
278
|
+
|
279
|
+
if new_lines == 1
|
280
|
+
q.breakable
|
281
|
+
elsif new_lines > 1
|
282
|
+
q.breakable
|
283
|
+
q.breakable(force: :skip_parent_break)
|
284
|
+
elsif next_node && !node.is_a?(SyntaxTree::ERB::CharData) &&
|
285
|
+
!next_node.is_a?(SyntaxTree::ERB::CharData)
|
286
|
+
q.breakable
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def breakable_between_group(node, next_node)
|
291
|
+
new_lines = node_new_line_count(node)
|
292
|
+
|
293
|
+
if new_lines == 1
|
294
|
+
q.breakable(force: true)
|
295
|
+
elsif new_lines > 1
|
296
|
+
q.breakable(force: true)
|
297
|
+
q.breakable(force: true)
|
298
|
+
elsif next_node && !node.is_a?(SyntaxTree::ERB::CharData) &&
|
299
|
+
!next_node.is_a?(SyntaxTree::ERB::CharData)
|
300
|
+
q.breakable("")
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def node_new_line_count(node)
|
305
|
+
node.respond_to?(:new_line) ? node.new_line&.count || 0 : 0
|
306
|
+
end
|
307
|
+
|
233
308
|
def erb_print_width(node)
|
234
309
|
# Set the width to maximum if we have an IfNode or IfOp,
|
235
310
|
# we cannot format them purely with SyntaxTree because the ERB-syntax will be unparseable.
|
@@ -247,6 +322,60 @@ module SyntaxTree
|
|
247
322
|
check_for_if_statement(child_node)
|
248
323
|
end
|
249
324
|
end
|
325
|
+
|
326
|
+
def handle_child_nodes(child_nodes)
|
327
|
+
group = []
|
328
|
+
|
329
|
+
if child_nodes.size == 1
|
330
|
+
visit(child_nodes.first)
|
331
|
+
return
|
332
|
+
end
|
333
|
+
|
334
|
+
child_nodes.each_with_index do |child_node, index|
|
335
|
+
is_last = index == child_nodes.size - 1
|
336
|
+
|
337
|
+
# Last element should not have new lines
|
338
|
+
node = is_last ? child_node.without_new_line : child_node
|
339
|
+
|
340
|
+
if node_should_group(node)
|
341
|
+
group << node
|
342
|
+
next
|
343
|
+
end
|
344
|
+
|
345
|
+
# Render all group elements before the current node
|
346
|
+
handle_group(group, break_after: true)
|
347
|
+
group = []
|
348
|
+
|
349
|
+
# Render the current node
|
350
|
+
visit(node)
|
351
|
+
next_node = child_nodes[index + 1]
|
352
|
+
|
353
|
+
breakable_between(node, next_node)
|
354
|
+
end
|
355
|
+
|
356
|
+
# Handle group if we have any nodes left
|
357
|
+
handle_group(group, break_after: false)
|
358
|
+
end
|
359
|
+
|
360
|
+
def handle_group(nodes, break_after:)
|
361
|
+
return unless nodes.any?
|
362
|
+
|
363
|
+
q.group do
|
364
|
+
nodes.each_with_index do |node, group_index|
|
365
|
+
visit(node)
|
366
|
+
next_node = nodes[group_index + 1]
|
367
|
+
next if next_node.nil?
|
368
|
+
breakable_between_group(node, next_node)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
breakable_between_group(nodes.last, nil) if break_after
|
373
|
+
end
|
374
|
+
|
375
|
+
def node_should_group(node)
|
376
|
+
node.is_a?(SyntaxTree::ERB::CharData) ||
|
377
|
+
node.is_a?(SyntaxTree::ERB::ErbNode)
|
378
|
+
end
|
250
379
|
end
|
251
380
|
end
|
252
381
|
end
|
@@ -53,6 +53,14 @@ module SyntaxTree
|
|
53
53
|
def pretty_print(q)
|
54
54
|
PrettyPrint.new(q).visit(self)
|
55
55
|
end
|
56
|
+
|
57
|
+
def without_new_line
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def skip?
|
62
|
+
false
|
63
|
+
end
|
56
64
|
end
|
57
65
|
|
58
66
|
# A Token is any kind of lexical token from the source. It has a type, a
|
@@ -110,6 +118,25 @@ module SyntaxTree
|
|
110
118
|
end
|
111
119
|
end
|
112
120
|
|
121
|
+
# This is a base class for a Node that can also hold an appended
|
122
|
+
# new line.
|
123
|
+
class Element < Node
|
124
|
+
attr_reader(:new_line, :location)
|
125
|
+
|
126
|
+
def initialize(new_line:, location:)
|
127
|
+
@new_line = new_line
|
128
|
+
@location = location
|
129
|
+
end
|
130
|
+
|
131
|
+
def without_new_line
|
132
|
+
self.class.new(**deconstruct_keys([]).merge(new_line: nil))
|
133
|
+
end
|
134
|
+
|
135
|
+
def deconstruct_keys(keys)
|
136
|
+
{ new_line: new_line, location: location }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
113
140
|
# This is a base class for a block that contains:
|
114
141
|
# - an opening
|
115
142
|
# - optional elements
|
@@ -131,12 +158,22 @@ module SyntaxTree
|
|
131
158
|
[opening, *elements, closing].compact
|
132
159
|
end
|
133
160
|
|
161
|
+
def new_line
|
162
|
+
closing.new_line if closing.respond_to?(:new_line)
|
163
|
+
end
|
164
|
+
|
165
|
+
def without_new_line
|
166
|
+
self.class.new(
|
167
|
+
**deconstruct_keys([]).merge(closing: closing&.without_new_line)
|
168
|
+
)
|
169
|
+
end
|
170
|
+
|
134
171
|
alias deconstruct child_nodes
|
135
172
|
|
136
173
|
def deconstruct_keys(keys)
|
137
174
|
{
|
138
175
|
opening: opening,
|
139
|
-
|
176
|
+
elements: elements,
|
140
177
|
closing: closing,
|
141
178
|
location: location
|
142
179
|
}
|
@@ -151,15 +188,22 @@ module SyntaxTree
|
|
151
188
|
# The opening tag of an element. It contains the opening character (<),
|
152
189
|
# the name of the element, any optional attributes, and the closing
|
153
190
|
# token (either > or />).
|
154
|
-
class OpeningTag <
|
155
|
-
attr_reader :opening, :name, :attributes, :closing
|
156
|
-
|
157
|
-
def initialize(
|
191
|
+
class OpeningTag < Element
|
192
|
+
attr_reader :opening, :name, :attributes, :closing
|
193
|
+
|
194
|
+
def initialize(
|
195
|
+
opening:,
|
196
|
+
name:,
|
197
|
+
attributes:,
|
198
|
+
closing:,
|
199
|
+
new_line:,
|
200
|
+
location:
|
201
|
+
)
|
202
|
+
super(new_line: new_line, location: location)
|
158
203
|
@opening = opening
|
159
204
|
@name = name
|
160
205
|
@attributes = attributes
|
161
206
|
@closing = closing
|
162
|
-
@location = location
|
163
207
|
end
|
164
208
|
|
165
209
|
def accept(visitor)
|
@@ -173,26 +217,25 @@ module SyntaxTree
|
|
173
217
|
alias deconstruct child_nodes
|
174
218
|
|
175
219
|
def deconstruct_keys(keys)
|
176
|
-
|
220
|
+
super.merge(
|
177
221
|
opening: opening,
|
178
222
|
name: name,
|
179
223
|
attributes: attributes,
|
180
|
-
closing: closing
|
181
|
-
|
182
|
-
}
|
224
|
+
closing: closing
|
225
|
+
)
|
183
226
|
end
|
184
227
|
end
|
185
228
|
|
186
229
|
# The closing tag of an element. It contains the opening character (<),
|
187
230
|
# the name of the element, and the closing character (>).
|
188
|
-
class ClosingTag <
|
189
|
-
attr_reader :opening, :name, :closing
|
231
|
+
class ClosingTag < Element
|
232
|
+
attr_reader :opening, :name, :closing
|
190
233
|
|
191
|
-
def initialize(opening:, name:, closing:, location:)
|
234
|
+
def initialize(opening:, name:, closing:, location:, new_line:)
|
235
|
+
super(new_line: new_line, location: location)
|
192
236
|
@opening = opening
|
193
237
|
@name = name
|
194
238
|
@closing = closing
|
195
|
-
@location = location
|
196
239
|
end
|
197
240
|
|
198
241
|
def accept(visitor)
|
@@ -206,19 +249,41 @@ module SyntaxTree
|
|
206
249
|
alias deconstruct child_nodes
|
207
250
|
|
208
251
|
def deconstruct_keys(keys)
|
209
|
-
|
252
|
+
super.merge(opening: opening, name: name, closing: closing)
|
210
253
|
end
|
211
254
|
end
|
212
255
|
|
256
|
+
def without_new_line
|
257
|
+
self.class.new(
|
258
|
+
**deconstruct_keys([]).merge(
|
259
|
+
opening: closing.nil? ? opening.without_new_line : opening,
|
260
|
+
closing: closing&.without_new_line
|
261
|
+
)
|
262
|
+
)
|
263
|
+
end
|
264
|
+
|
265
|
+
# The HTML-closing tag is responsible for new lines after the node.
|
266
|
+
def new_line
|
267
|
+
closing.nil? ? opening.new_line : closing&.new_line
|
268
|
+
end
|
269
|
+
|
213
270
|
def accept(visitor)
|
214
271
|
visitor.visit_html(self)
|
215
272
|
end
|
216
273
|
end
|
217
274
|
|
218
|
-
class ErbNode <
|
219
|
-
attr_reader :opening_tag, :keyword, :content, :closing_tag
|
220
|
-
|
221
|
-
def initialize(
|
275
|
+
class ErbNode < Element
|
276
|
+
attr_reader :opening_tag, :keyword, :content, :closing_tag
|
277
|
+
|
278
|
+
def initialize(
|
279
|
+
opening_tag:,
|
280
|
+
keyword:,
|
281
|
+
content:,
|
282
|
+
closing_tag:,
|
283
|
+
new_line:,
|
284
|
+
location:
|
285
|
+
)
|
286
|
+
super(new_line: new_line, location: location)
|
222
287
|
@opening_tag = opening_tag
|
223
288
|
# prune whitespace from keyword
|
224
289
|
@keyword =
|
@@ -229,12 +294,18 @@ module SyntaxTree
|
|
229
294
|
location: keyword.location
|
230
295
|
)
|
231
296
|
end
|
232
|
-
|
233
|
-
content
|
234
|
-
|
235
|
-
|
297
|
+
|
298
|
+
@content =
|
299
|
+
if content.is_a?(ErbContent)
|
300
|
+
content
|
301
|
+
else
|
302
|
+
# Set content to nil if it is empty
|
303
|
+
content ||= []
|
304
|
+
content = content.map(&:value).join if content.is_a?(Array)
|
305
|
+
ErbContent.new(value: content) unless content.strip.empty?
|
306
|
+
end
|
307
|
+
|
236
308
|
@closing_tag = closing_tag
|
237
|
-
@location = location
|
238
309
|
end
|
239
310
|
|
240
311
|
def accept(visitor)
|
@@ -245,30 +316,50 @@ module SyntaxTree
|
|
245
316
|
[opening_tag, keyword, content, closing_tag].compact
|
246
317
|
end
|
247
318
|
|
319
|
+
def new_line
|
320
|
+
closing_tag&.new_line
|
321
|
+
end
|
322
|
+
|
323
|
+
def without_new_line
|
324
|
+
self.class.new(
|
325
|
+
**deconstruct_keys([]).merge(
|
326
|
+
closing_tag: closing_tag.without_new_line
|
327
|
+
)
|
328
|
+
)
|
329
|
+
end
|
330
|
+
|
248
331
|
alias deconstruct child_nodes
|
249
332
|
|
250
333
|
def deconstruct_keys(keys)
|
251
|
-
|
334
|
+
super.merge(
|
252
335
|
opening_tag: opening_tag,
|
253
336
|
keyword: keyword,
|
254
337
|
content: content,
|
255
|
-
closing_tag: closing_tag
|
256
|
-
|
257
|
-
}
|
338
|
+
closing_tag: closing_tag
|
339
|
+
)
|
258
340
|
end
|
259
341
|
end
|
260
342
|
|
261
343
|
class ErbBlock < Block
|
344
|
+
def initialize(opening:, location:, elements: nil, closing: nil)
|
345
|
+
super(
|
346
|
+
opening: opening,
|
347
|
+
location: location,
|
348
|
+
elements: elements,
|
349
|
+
closing: closing
|
350
|
+
)
|
351
|
+
end
|
352
|
+
|
262
353
|
def accept(visitor)
|
263
354
|
visitor.visit_erb_block(self)
|
264
355
|
end
|
265
356
|
end
|
266
357
|
|
267
|
-
class ErbClose <
|
268
|
-
attr_reader :
|
358
|
+
class ErbClose < Element
|
359
|
+
attr_reader :closing
|
269
360
|
|
270
|
-
def initialize(
|
271
|
-
|
361
|
+
def initialize(closing:, new_line:, location:)
|
362
|
+
super(new_line: new_line, location: location)
|
272
363
|
@closing = closing
|
273
364
|
end
|
274
365
|
|
@@ -283,7 +374,7 @@ module SyntaxTree
|
|
283
374
|
alias deconstruct child_nodes
|
284
375
|
|
285
376
|
def deconstruct_keys(keys)
|
286
|
-
|
377
|
+
super.merge(closing: closing)
|
287
378
|
end
|
288
379
|
end
|
289
380
|
|
@@ -442,12 +533,12 @@ module SyntaxTree
|
|
442
533
|
end
|
443
534
|
end
|
444
535
|
|
445
|
-
class HtmlComment <
|
446
|
-
attr_reader :token
|
536
|
+
class HtmlComment < Element
|
537
|
+
attr_reader :token
|
447
538
|
|
448
|
-
def initialize(token:, location:)
|
539
|
+
def initialize(token:, new_line:, location:)
|
540
|
+
super(new_line: new_line, location: location)
|
449
541
|
@token = token
|
450
|
-
@location = location
|
451
542
|
end
|
452
543
|
|
453
544
|
def accept(visitor)
|
@@ -461,18 +552,41 @@ module SyntaxTree
|
|
461
552
|
alias deconstruct child_nodes
|
462
553
|
|
463
554
|
def deconstruct_keys(keys)
|
464
|
-
|
555
|
+
super.merge(token: token)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
class ErbComment < Element
|
560
|
+
attr_reader :token
|
561
|
+
|
562
|
+
def initialize(token:, new_line:, location:)
|
563
|
+
super(new_line: new_line, location: location)
|
564
|
+
@token = token
|
565
|
+
end
|
566
|
+
|
567
|
+
def accept(visitor)
|
568
|
+
visitor.visit_erb_comment(self)
|
569
|
+
end
|
570
|
+
|
571
|
+
def child_nodes
|
572
|
+
[]
|
573
|
+
end
|
574
|
+
|
575
|
+
alias deconstruct child_nodes
|
576
|
+
|
577
|
+
def deconstruct_keys(keys)
|
578
|
+
super.merge(token: token)
|
465
579
|
end
|
466
580
|
end
|
467
581
|
|
468
582
|
# A CharData contains either plain text or whitespace within an element.
|
469
583
|
# It wraps a single token value.
|
470
|
-
class CharData <
|
471
|
-
attr_reader :value
|
584
|
+
class CharData < Element
|
585
|
+
attr_reader :value
|
472
586
|
|
473
|
-
def initialize(value:, location:)
|
587
|
+
def initialize(value:, new_line:, location:)
|
588
|
+
super(new_line: new_line, location: location)
|
474
589
|
@value = value
|
475
|
-
@location = location
|
476
590
|
end
|
477
591
|
|
478
592
|
def accept(visitor)
|
@@ -486,7 +600,34 @@ module SyntaxTree
|
|
486
600
|
alias deconstruct child_nodes
|
487
601
|
|
488
602
|
def deconstruct_keys(keys)
|
489
|
-
|
603
|
+
super.merge(value: value)
|
604
|
+
end
|
605
|
+
|
606
|
+
def skip?
|
607
|
+
value.value.strip.empty?
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
class NewLine < Node
|
612
|
+
attr_reader :count, :location
|
613
|
+
|
614
|
+
def initialize(location:, count:)
|
615
|
+
@location = location
|
616
|
+
@count = count
|
617
|
+
end
|
618
|
+
|
619
|
+
def accept(visitor)
|
620
|
+
visitor.visit_new_line(self)
|
621
|
+
end
|
622
|
+
|
623
|
+
def child_nodes
|
624
|
+
[]
|
625
|
+
end
|
626
|
+
|
627
|
+
alias deconstruct child_nodes
|
628
|
+
|
629
|
+
def deconstruct_keys(keys)
|
630
|
+
{ location: location, count: count }
|
490
631
|
end
|
491
632
|
end
|
492
633
|
|
@@ -494,14 +635,14 @@ module SyntaxTree
|
|
494
635
|
# type of the document. It contains an opening declaration, the name of
|
495
636
|
# the document type, an optional external identifier, and a closing of the
|
496
637
|
# tag.
|
497
|
-
class Doctype <
|
498
|
-
attr_reader :opening, :name, :closing
|
638
|
+
class Doctype < Element
|
639
|
+
attr_reader :opening, :name, :closing
|
499
640
|
|
500
|
-
def initialize(opening:, name:, closing:, location:)
|
641
|
+
def initialize(opening:, name:, closing:, new_line:, location:)
|
642
|
+
super(new_line: new_line, location: location)
|
501
643
|
@opening = opening
|
502
644
|
@name = name
|
503
645
|
@closing = closing
|
504
|
-
@location = location
|
505
646
|
end
|
506
647
|
|
507
648
|
def accept(visitor)
|
@@ -515,7 +656,7 @@ module SyntaxTree
|
|
515
656
|
alias deconstruct child_nodes
|
516
657
|
|
517
658
|
def deconstruct_keys(keys)
|
518
|
-
|
659
|
+
super.merge(opening: opening, name: name, closing: closing)
|
519
660
|
end
|
520
661
|
end
|
521
662
|
end
|
@@ -20,16 +20,16 @@ module SyntaxTree
|
|
20
20
|
def initialize(source)
|
21
21
|
@source = source
|
22
22
|
@tokens = make_tokens
|
23
|
+
@found_doctype = false
|
23
24
|
end
|
24
25
|
|
25
26
|
def parse
|
26
|
-
doctype = maybe { parse_doctype }
|
27
27
|
elements = many { parse_any_tag }
|
28
28
|
|
29
29
|
location =
|
30
30
|
elements.first.location.to(elements.last.location) if elements.any?
|
31
31
|
|
32
|
-
Document.new(elements:
|
32
|
+
Document.new(elements: elements, location: location)
|
33
33
|
end
|
34
34
|
|
35
35
|
def debug_tokens
|
@@ -41,10 +41,24 @@ module SyntaxTree
|
|
41
41
|
private
|
42
42
|
|
43
43
|
def parse_any_tag
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
loop do
|
45
|
+
tag =
|
46
|
+
atleast do
|
47
|
+
maybe { parse_doctype } || maybe { parse_html_comment } ||
|
48
|
+
maybe { parse_erb_tag } || maybe { parse_erb_comment } ||
|
49
|
+
maybe { parse_html_element } || maybe { parse_new_line } ||
|
50
|
+
maybe { parse_chardata }
|
51
|
+
end
|
52
|
+
|
53
|
+
if tag.is_a?(Doctype)
|
54
|
+
if @found_doctype
|
55
|
+
raise(ParseError, "Only one doctype element is allowed")
|
56
|
+
else
|
57
|
+
@found_doctype = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# Allow skipping empty CharData
|
61
|
+
return tag unless tag.skip?
|
48
62
|
end
|
49
63
|
end
|
50
64
|
|
@@ -62,10 +76,10 @@ module SyntaxTree
|
|
62
76
|
# two or more newlines should be ONE blank line
|
63
77
|
enum.yield :blank_line, $&, index, line
|
64
78
|
line += $&.count("\n")
|
65
|
-
when /\A
|
66
|
-
#
|
67
|
-
|
68
|
-
line +=
|
79
|
+
when /\A\n/
|
80
|
+
# newlines
|
81
|
+
enum.yield :new_line, $&, index, line
|
82
|
+
line += 1
|
69
83
|
when /\A<!--(.|\r?\n)*?-->/m
|
70
84
|
# comments
|
71
85
|
# <!-- this is a comment -->
|
@@ -97,8 +111,11 @@ module SyntaxTree
|
|
97
111
|
# <
|
98
112
|
enum.yield :open, $&, index, line
|
99
113
|
state << :inside
|
100
|
-
when /\A
|
101
|
-
#
|
114
|
+
when /\A(?: |\t|\r)+/m
|
115
|
+
# whitespace
|
116
|
+
enum.yield :whitespace, $&, index, line
|
117
|
+
when /\A(?!\s+$)[^<\n]+/
|
118
|
+
# plain text content, but do not allow only white space
|
102
119
|
# abc
|
103
120
|
enum.yield :text, $&, index, line
|
104
121
|
else
|
@@ -178,7 +195,7 @@ module SyntaxTree
|
|
178
195
|
# the beginning of an ERB tag
|
179
196
|
# <%
|
180
197
|
enum.yield :erb_open, $&, index, line
|
181
|
-
state << :
|
198
|
+
state << :erb_start
|
182
199
|
when /\A[^<']+/
|
183
200
|
# plain text content
|
184
201
|
# abc
|
@@ -201,7 +218,7 @@ module SyntaxTree
|
|
201
218
|
# the beginning of an ERB tag
|
202
219
|
# <%
|
203
220
|
enum.yield :erb_open, $&, index, line
|
204
|
-
state << :
|
221
|
+
state << :erb_start
|
205
222
|
when /\A[^<"]+/
|
206
223
|
# plain text content
|
207
224
|
# abc
|
@@ -251,7 +268,7 @@ module SyntaxTree
|
|
251
268
|
# the beginning of an ERB tag
|
252
269
|
# <%
|
253
270
|
enum.yield :erb_open, $&, index, line
|
254
|
-
state << :
|
271
|
+
state << :erb_start
|
255
272
|
when /\A"/
|
256
273
|
# the beginning of a string
|
257
274
|
enum.yield :string_open_double_quote, $&, index, line
|
@@ -357,12 +374,18 @@ module SyntaxTree
|
|
357
374
|
maybe { consume(:close) } || maybe { consume(:slash_close) }
|
358
375
|
end
|
359
376
|
|
377
|
+
new_line = maybe { parse_new_line }
|
378
|
+
|
379
|
+
# Parse any whitespace after new lines
|
380
|
+
maybe { consume(:whitespace) }
|
381
|
+
|
360
382
|
HtmlNode::OpeningTag.new(
|
361
383
|
opening: opening,
|
362
384
|
name: name,
|
363
385
|
attributes: attributes,
|
364
386
|
closing: closing,
|
365
|
-
location: opening.location.to(closing.location)
|
387
|
+
location: opening.location.to(closing.location),
|
388
|
+
new_line: new_line
|
366
389
|
)
|
367
390
|
end
|
368
391
|
|
@@ -371,11 +394,14 @@ module SyntaxTree
|
|
371
394
|
name = consume(:name)
|
372
395
|
closing = consume(:close)
|
373
396
|
|
397
|
+
new_line = maybe { parse_new_line }
|
398
|
+
|
374
399
|
HtmlNode::ClosingTag.new(
|
375
400
|
opening: opening,
|
376
401
|
name: name,
|
377
402
|
closing: closing,
|
378
|
-
location: opening.location.to(closing.location)
|
403
|
+
location: opening.location.to(closing.location),
|
404
|
+
new_line: new_line
|
379
405
|
)
|
380
406
|
end
|
381
407
|
|
@@ -450,6 +476,9 @@ module SyntaxTree
|
|
450
476
|
end
|
451
477
|
|
452
478
|
def parse_erb_if(erb_node)
|
479
|
+
# Skip any leading whitespace
|
480
|
+
maybe { consume(:whitespace) }
|
481
|
+
|
453
482
|
elements =
|
454
483
|
maybe { parse_until_erb(classes: [ErbElsif, ErbElse, ErbEnd]) } || []
|
455
484
|
|
@@ -513,11 +542,14 @@ module SyntaxTree
|
|
513
542
|
end
|
514
543
|
|
515
544
|
def parse_erb_end(erb_node)
|
545
|
+
new_line = maybe { parse_new_line }
|
546
|
+
|
516
547
|
ErbEnd.new(
|
517
548
|
opening_tag: erb_node.opening_tag,
|
518
549
|
keyword: erb_node.keyword,
|
519
550
|
content: nil,
|
520
551
|
closing_tag: erb_node.closing_tag,
|
552
|
+
new_line: new_line,
|
521
553
|
location: erb_node.location
|
522
554
|
)
|
523
555
|
end
|
@@ -540,12 +572,15 @@ module SyntaxTree
|
|
540
572
|
)
|
541
573
|
end
|
542
574
|
|
575
|
+
new_line = maybe { parse_new_line }
|
576
|
+
|
543
577
|
erb_node =
|
544
578
|
ErbNode.new(
|
545
579
|
opening_tag: opening_tag,
|
546
580
|
keyword: keyword,
|
547
581
|
content: content,
|
548
582
|
closing_tag: closing_tag,
|
583
|
+
new_line: new_line,
|
549
584
|
location: opening_tag.location.to(closing_tag.location)
|
550
585
|
)
|
551
586
|
|
@@ -602,6 +637,7 @@ module SyntaxTree
|
|
602
637
|
maybe { parse_erb_do_close } || maybe { parse_erb_close } ||
|
603
638
|
maybe { consume(:erb_code) }
|
604
639
|
end
|
640
|
+
|
605
641
|
items << result
|
606
642
|
|
607
643
|
break if result.is_a?(ErbClose)
|
@@ -610,22 +646,46 @@ module SyntaxTree
|
|
610
646
|
items
|
611
647
|
end
|
612
648
|
|
613
|
-
|
614
|
-
|
649
|
+
# This method is called at the end of most tags, it fixes:
|
650
|
+
# 1. Parsing any new lines after the tag
|
651
|
+
# 2. Parsing any whitespace after the new lines
|
652
|
+
# The whitespace is just consumed
|
653
|
+
def parse_new_line
|
654
|
+
line_break =
|
655
|
+
atleast do
|
656
|
+
maybe { consume(:blank_line) } || maybe { consume(:new_line) }
|
657
|
+
end
|
658
|
+
|
659
|
+
maybe { consume(:whitespace) }
|
615
660
|
|
616
|
-
|
661
|
+
NewLine.new(
|
662
|
+
location: line_break.location,
|
663
|
+
count: line_break.value.count("\n")
|
664
|
+
)
|
617
665
|
end
|
618
666
|
|
619
667
|
def parse_erb_close
|
620
668
|
closing = consume(:erb_close)
|
621
669
|
|
622
|
-
|
670
|
+
new_line = maybe { parse_new_line }
|
671
|
+
|
672
|
+
ErbClose.new(
|
673
|
+
location: closing.location,
|
674
|
+
new_line: new_line,
|
675
|
+
closing: closing
|
676
|
+
)
|
623
677
|
end
|
624
678
|
|
625
679
|
def parse_erb_do_close
|
626
680
|
closing = consume(:erb_do_close)
|
627
681
|
|
628
|
-
|
682
|
+
new_line = maybe { parse_new_line }
|
683
|
+
|
684
|
+
ErbDoClose.new(
|
685
|
+
location: closing.location,
|
686
|
+
new_line: new_line,
|
687
|
+
closing: closing
|
688
|
+
)
|
629
689
|
end
|
630
690
|
|
631
691
|
def parse_html_string
|
@@ -715,7 +775,15 @@ module SyntaxTree
|
|
715
775
|
values.first
|
716
776
|
end
|
717
777
|
|
718
|
-
|
778
|
+
new_line = maybe { parse_new_line }
|
779
|
+
|
780
|
+
if token&.value
|
781
|
+
CharData.new(
|
782
|
+
value: token,
|
783
|
+
location: token.location,
|
784
|
+
new_line: new_line
|
785
|
+
)
|
786
|
+
end
|
719
787
|
end
|
720
788
|
|
721
789
|
def parse_doctype
|
@@ -723,10 +791,13 @@ module SyntaxTree
|
|
723
791
|
name = consume(:name)
|
724
792
|
closing = consume(:close)
|
725
793
|
|
794
|
+
new_line = maybe { parse_new_line }
|
795
|
+
|
726
796
|
Doctype.new(
|
727
797
|
opening: opening,
|
728
798
|
name: name,
|
729
799
|
closing: closing,
|
800
|
+
new_line: new_line,
|
730
801
|
location: opening.location.to(closing.location)
|
731
802
|
)
|
732
803
|
end
|
@@ -734,7 +805,25 @@ module SyntaxTree
|
|
734
805
|
def parse_html_comment
|
735
806
|
comment = consume(:html_comment)
|
736
807
|
|
737
|
-
|
808
|
+
new_line = maybe { parse_new_line }
|
809
|
+
|
810
|
+
HtmlComment.new(
|
811
|
+
token: comment,
|
812
|
+
new_line: new_line,
|
813
|
+
location: comment.location
|
814
|
+
)
|
815
|
+
end
|
816
|
+
|
817
|
+
def parse_erb_comment
|
818
|
+
comment = consume(:erb_comment)
|
819
|
+
|
820
|
+
new_line = maybe { parse_new_line }
|
821
|
+
|
822
|
+
ErbComment.new(
|
823
|
+
token: comment,
|
824
|
+
new_line: new_line,
|
825
|
+
location: comment.location
|
826
|
+
)
|
738
827
|
end
|
739
828
|
end
|
740
829
|
end
|
@@ -108,7 +108,7 @@ module SyntaxTree
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def visit_erb_end(node)
|
111
|
-
q.pp("erb_end")
|
111
|
+
q.pp("(erb_end)")
|
112
112
|
end
|
113
113
|
|
114
114
|
# Visit an ErbContent node.
|
@@ -131,6 +131,10 @@ module SyntaxTree
|
|
131
131
|
visit_node("char_data", node)
|
132
132
|
end
|
133
133
|
|
134
|
+
def visit_new_line(node)
|
135
|
+
node.count > 1 ? q.text("(new_line blank)") : q.text("(new_line)")
|
136
|
+
end
|
137
|
+
|
134
138
|
def visit_erb_close(node)
|
135
139
|
visit(node.closing)
|
136
140
|
end
|
@@ -148,6 +152,10 @@ module SyntaxTree
|
|
148
152
|
visit_node("html_comment", node)
|
149
153
|
end
|
150
154
|
|
155
|
+
def visit_erb_comment(node)
|
156
|
+
visit_node("erb_comment", node)
|
157
|
+
end
|
158
|
+
|
151
159
|
private
|
152
160
|
|
153
161
|
# A generic visit node function for how we pretty print nodes.
|
data/lib/syntax_tree/erb.rb
CHANGED
data/package.json
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: w_syntax_tree-erb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-08-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: prettier_print
|
@@ -121,6 +121,8 @@ files:
|
|
121
121
|
- ".github/workflows/auto-merge.yml"
|
122
122
|
- ".github/workflows/main.yml"
|
123
123
|
- ".gitignore"
|
124
|
+
- ".husky/pre-commit"
|
125
|
+
- ".vscode/launch.json"
|
124
126
|
- CHANGELOG.md
|
125
127
|
- Gemfile
|
126
128
|
- Gemfile.lock
|
@@ -135,6 +137,7 @@ files:
|
|
135
137
|
- lib/syntax_tree/erb/pretty_print.rb
|
136
138
|
- lib/syntax_tree/erb/version.rb
|
137
139
|
- lib/syntax_tree/erb/visitor.rb
|
140
|
+
- package.json
|
138
141
|
- syntax_tree-erb.gemspec
|
139
142
|
homepage: https://github.com/davidwessman/syntax_tree-erb
|
140
143
|
licenses:
|
@@ -156,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
159
|
- !ruby/object:Gem::Version
|
157
160
|
version: '0'
|
158
161
|
requirements: []
|
159
|
-
rubygems_version: 3.4.
|
162
|
+
rubygems_version: 3.4.19
|
160
163
|
signing_key:
|
161
164
|
specification_version: 4
|
162
165
|
summary: Syntax Tree support for ERB
|