w_syntax_tree-erb 0.9.5 → 0.10.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/.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
|