w_syntax_tree-erb 0.9.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.
@@ -0,0 +1,511 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module ERB
5
+ # A Location represents a position for a node in the source file.
6
+ class Location
7
+ attr_reader :start_char, :end_char, :start_line, :end_line
8
+
9
+ def initialize(start_char:, end_char:, start_line:, end_line:)
10
+ @start_char = start_char
11
+ @end_char = end_char
12
+ @start_line = start_line
13
+ @end_line = end_line
14
+ end
15
+
16
+ def deconstruct_keys(keys)
17
+ {
18
+ start_char: start_char,
19
+ end_char: end_char,
20
+ start_line: start_line,
21
+ end_line: end_line
22
+ }
23
+ end
24
+
25
+ def to(other)
26
+ Location.new(
27
+ start_char: start_char,
28
+ start_line: start_line,
29
+ end_char: other.end_char,
30
+ end_line: other.end_line
31
+ )
32
+ end
33
+
34
+ def <=>(other)
35
+ start_char <=> other.start_char
36
+ end
37
+
38
+ def to_s
39
+ if start_line == end_line
40
+ "line #{start_line}, char #{start_char}..#{end_char}"
41
+ else
42
+ "line #{start_line},char #{start_char} to line #{end_line}, char #{end_char}"
43
+ end
44
+ end
45
+ end
46
+
47
+ # A parent node that contains a bit of shared functionality.
48
+ class Node
49
+ def format(q)
50
+ Format.new(q).visit(self)
51
+ end
52
+
53
+ def pretty_print(q)
54
+ PrettyPrint.new(q).visit(self)
55
+ end
56
+ end
57
+
58
+ # A Token is any kind of lexical token from the source. It has a type, a
59
+ # value which is a subset of the source, and an index where it starts in
60
+ # the source.
61
+ class Token < Node
62
+ attr_reader :type, :value, :location
63
+
64
+ def initialize(type:, value:, location:)
65
+ @type = type
66
+ @value = value
67
+ @location = location
68
+ end
69
+
70
+ def accept(visitor)
71
+ visitor.visit_token(self)
72
+ end
73
+
74
+ def child_nodes
75
+ []
76
+ end
77
+
78
+ alias deconstruct child_nodes
79
+
80
+ def deconstruct_keys(keys)
81
+ { type: type, value: value, location: location }
82
+ end
83
+ end
84
+
85
+ # The Document node is the top of the syntax tree.
86
+ # It contains any number of:
87
+ # - Text
88
+ # - HtmlNode
89
+ # - ErbNodes
90
+ class Document < Node
91
+ attr_reader :elements, :location
92
+
93
+ def initialize(elements:, location:)
94
+ @elements = elements
95
+ @location = location
96
+ end
97
+
98
+ def accept(visitor)
99
+ visitor.visit_document(self)
100
+ end
101
+
102
+ def child_nodes
103
+ [*elements].compact
104
+ end
105
+
106
+ alias deconstruct child_nodes
107
+
108
+ def deconstruct_keys(keys)
109
+ { elements: elements, location: location }
110
+ end
111
+ end
112
+
113
+ # This is a base class for a block that contains:
114
+ # - an opening
115
+ # - optional elements
116
+ # - optional closing
117
+ class Block < Node
118
+ attr_reader(:opening, :elements, :closing, :location)
119
+ def initialize(opening:, location:, elements: nil, closing: nil)
120
+ @opening = opening
121
+ @elements = elements || []
122
+ @closing = closing
123
+ @location = location
124
+ end
125
+
126
+ def accept(visitor)
127
+ visitor.visit_block(self)
128
+ end
129
+
130
+ def child_nodes
131
+ [opening, *elements, closing].compact
132
+ end
133
+
134
+ alias deconstruct child_nodes
135
+
136
+ def deconstruct_keys(keys)
137
+ {
138
+ opening: opening,
139
+ content: content,
140
+ closing: closing,
141
+ location: location
142
+ }
143
+ end
144
+ end
145
+
146
+ # An element is a child of the document. It contains an opening tag, any
147
+ # optional content within the tag, and a closing tag. It can also
148
+ # potentially contain an opening tag that self-closes, in which case the
149
+ # content and closing tag will be nil.
150
+ class HtmlNode < Block
151
+ # The opening tag of an element. It contains the opening character (<),
152
+ # the name of the element, any optional attributes, and the closing
153
+ # token (either > or />).
154
+ class OpeningTag < Node
155
+ attr_reader :opening, :name, :attributes, :closing, :location
156
+
157
+ def initialize(opening:, name:, attributes:, closing:, location:)
158
+ @opening = opening
159
+ @name = name
160
+ @attributes = attributes
161
+ @closing = closing
162
+ @location = location
163
+ end
164
+
165
+ def accept(visitor)
166
+ visitor.visit_opening_tag(self)
167
+ end
168
+
169
+ def child_nodes
170
+ [opening, name, *attributes, closing]
171
+ end
172
+
173
+ alias deconstruct child_nodes
174
+
175
+ def deconstruct_keys(keys)
176
+ {
177
+ opening: opening,
178
+ name: name,
179
+ attributes: attributes,
180
+ closing: closing,
181
+ location: location
182
+ }
183
+ end
184
+ end
185
+
186
+ # The closing tag of an element. It contains the opening character (<),
187
+ # the name of the element, and the closing character (>).
188
+ class ClosingTag < Node
189
+ attr_reader :opening, :name, :closing, :location
190
+
191
+ def initialize(opening:, name:, closing:, location:)
192
+ @opening = opening
193
+ @name = name
194
+ @closing = closing
195
+ @location = location
196
+ end
197
+
198
+ def accept(visitor)
199
+ visitor.visit_closing_tag(self)
200
+ end
201
+
202
+ def child_nodes
203
+ [opening, name, closing]
204
+ end
205
+
206
+ alias deconstruct child_nodes
207
+
208
+ def deconstruct_keys(keys)
209
+ { opening: opening, name: name, closing: closing, location: location }
210
+ end
211
+ end
212
+
213
+ def accept(visitor)
214
+ visitor.visit_html(self)
215
+ end
216
+ end
217
+
218
+ class ErbNode < Node
219
+ attr_reader :opening_tag, :keyword, :content, :closing_tag, :location
220
+
221
+ def initialize(opening_tag:, keyword:, content:, closing_tag:, location:)
222
+ @opening_tag = opening_tag
223
+ @keyword = keyword
224
+ @content = ErbContent.new(value: content.map(&:value).join) if content
225
+ @closing_tag = closing_tag
226
+ @location = location
227
+ end
228
+
229
+ def accept(visitor)
230
+ visitor.visit_erb(self)
231
+ end
232
+
233
+ def child_nodes
234
+ [opening_tag, keyword, content, closing_tag].compact
235
+ end
236
+
237
+ alias deconstruct child_nodes
238
+
239
+ def deconstruct_keys(keys)
240
+ {
241
+ opening_tag: opening_tag,
242
+ keyword: keyword,
243
+ content: content,
244
+ closing_tag: closing_tag,
245
+ location: location
246
+ }
247
+ end
248
+ end
249
+
250
+ class ErbBlock < Block
251
+ def accept(visitor)
252
+ visitor.visit_erb_block(self)
253
+ end
254
+ end
255
+
256
+ class ErbClose < Node
257
+ attr_reader :location, :closing
258
+
259
+ def initialize(location:, closing:)
260
+ @location = location
261
+ @closing = closing
262
+ end
263
+
264
+ def accept(visitor)
265
+ visitor.visit_erb_close(self)
266
+ end
267
+
268
+ def child_nodes
269
+ []
270
+ end
271
+
272
+ alias deconstruct child_nodes
273
+
274
+ def deconstruct_keys(keys)
275
+ { location: location, closing: closing }
276
+ end
277
+ end
278
+
279
+ class ErbDoClose < ErbClose
280
+ def accept(visitor)
281
+ visitor.visit_erb_do_close(self)
282
+ end
283
+ end
284
+
285
+ class ErbControl < Block
286
+ end
287
+
288
+ class ErbIf < ErbControl
289
+ # opening: ErbNode
290
+ # elements: [[HtmlNode | ErbNode | CharDataNode]]
291
+ # closing: [nil | ErbElsif | ErbElse]
292
+ def accept(visitor)
293
+ visitor.visit_erb_if(self)
294
+ end
295
+ end
296
+
297
+ class ErbUnless < ErbIf
298
+ # opening: ErbNode
299
+ # elements: [[HtmlNode | ErbNode | CharDataNode]]
300
+ # closing: [nil | ErbElsif | ErbElse]
301
+ def accept(visitor)
302
+ visitor.visit_erb_if(self)
303
+ end
304
+ end
305
+
306
+ class ErbElsif < ErbIf
307
+ def accept(visitor)
308
+ visitor.visit_erb_if(self)
309
+ end
310
+ end
311
+
312
+ class ErbElse < ErbIf
313
+ def accept(visitor)
314
+ visitor.visit_erb_if(self)
315
+ end
316
+ end
317
+
318
+ class ErbEnd < ErbNode
319
+ def accept(visitor)
320
+ visitor.visit_erb_end(self)
321
+ end
322
+
323
+ def child_nodes
324
+ []
325
+ end
326
+
327
+ alias deconstruct child_nodes
328
+ end
329
+
330
+ class ErbCase < ErbControl
331
+ # opening: ErbNode
332
+ # elements: [[HtmlNode | ErbNode | CharDataNode]]
333
+ # closing: [nil | ErbCaseWhen | ErbElse | ErbEnd]
334
+ def accept(visitor)
335
+ visitor.visit_erb_case(self)
336
+ end
337
+ end
338
+
339
+ class ErbCaseWhen < ErbControl
340
+ # opening: ErbNode
341
+ # elements: [[HtmlNode | ErbNode | CharDataNode]]
342
+ # closing: [nil | ErbCaseWhen | ErbElse | ErbEnd]
343
+ def accept(visitor)
344
+ visitor.visit_erb_case_when(self)
345
+ end
346
+ end
347
+
348
+ class ErbContent < Node
349
+ attr_reader(:value, :unparsed_value)
350
+
351
+ def initialize(value:)
352
+ @unparsed_value = value
353
+ begin
354
+ @value = SyntaxTree.parse(value.strip)
355
+ rescue SyntaxTree::Parser::ParseError
356
+ # Removes leading and trailing whitespace
357
+ @value = value&.lstrip&.rstrip
358
+ end
359
+ end
360
+
361
+ def accept(visitor)
362
+ visitor.visit_erb_content(self)
363
+ end
364
+
365
+ def child_nodes
366
+ []
367
+ end
368
+
369
+ alias deconstruct child_nodes
370
+
371
+ def deconstruct_keys(keys)
372
+ { value: value }
373
+ end
374
+ end
375
+
376
+ # An HtmlAttribute is a key-value pair within a tag. It contains the key, the
377
+ # equals sign, and the value.
378
+ class HtmlAttribute < Node
379
+ attr_reader :key, :equals, :value, :location
380
+
381
+ def initialize(key:, equals:, value:, location:)
382
+ @key = key
383
+ @equals = equals
384
+ @value = value
385
+ @location = location
386
+ end
387
+
388
+ def accept(visitor)
389
+ visitor.visit_attribute(self)
390
+ end
391
+
392
+ def child_nodes
393
+ [key, equals, value]
394
+ end
395
+
396
+ alias deconstruct child_nodes
397
+
398
+ def deconstruct_keys(keys)
399
+ { key: key, equals: equals, value: value, location: location }
400
+ end
401
+ end
402
+
403
+ # A HtmlString can include ERB-tags
404
+ class HtmlString < Node
405
+ attr_reader :opening, :contents, :closing, :location
406
+
407
+ def initialize(opening:, contents:, closing:, location:)
408
+ @opening = opening
409
+ @contents = contents
410
+ @closing = closing
411
+ @location = location
412
+ end
413
+
414
+ def accept(visitor)
415
+ visitor.visit_html_string(self)
416
+ end
417
+
418
+ def child_nodes
419
+ [*contents]
420
+ end
421
+
422
+ alias deconstruct child_nodes
423
+
424
+ def deconstruct_keys(keys)
425
+ {
426
+ opening: opening,
427
+ contents: contents,
428
+ closing: closing,
429
+ location: location
430
+ }
431
+ end
432
+ end
433
+
434
+ class HtmlComment < Node
435
+ attr_reader :token, :location
436
+
437
+ def initialize(token:, location:)
438
+ @token = token
439
+ @location = location
440
+ end
441
+
442
+ def accept(visitor)
443
+ visitor.visit_html_comment(self)
444
+ end
445
+
446
+ def child_nodes
447
+ []
448
+ end
449
+
450
+ alias deconstruct child_nodes
451
+
452
+ def deconstruct_keys(keys)
453
+ { token: token, location: location }
454
+ end
455
+ end
456
+
457
+ # A CharData contains either plain text or whitespace within an element.
458
+ # It wraps a single token value.
459
+ class CharData < Node
460
+ attr_reader :value, :location
461
+
462
+ def initialize(value:, location:)
463
+ @value = value
464
+ @location = location
465
+ end
466
+
467
+ def accept(visitor)
468
+ visitor.visit_char_data(self)
469
+ end
470
+
471
+ def child_nodes
472
+ [value]
473
+ end
474
+
475
+ alias deconstruct child_nodes
476
+
477
+ def deconstruct_keys(keys)
478
+ { value: value, location: location }
479
+ end
480
+ end
481
+
482
+ # A document type declaration is a special kind of tag that specifies the
483
+ # type of the document. It contains an opening declaration, the name of
484
+ # the document type, an optional external identifier, and a closing of the
485
+ # tag.
486
+ class Doctype < Node
487
+ attr_reader :opening, :name, :closing, :location
488
+
489
+ def initialize(opening:, name:, closing:, location:)
490
+ @opening = opening
491
+ @name = name
492
+ @closing = closing
493
+ @location = location
494
+ end
495
+
496
+ def accept(visitor)
497
+ visitor.visit_doctype(self)
498
+ end
499
+
500
+ def child_nodes
501
+ [opening, name, closing].compact
502
+ end
503
+
504
+ alias deconstruct child_nodes
505
+
506
+ def deconstruct_keys(keys)
507
+ { opening: opening, name: name, closing: closing, location: location }
508
+ end
509
+ end
510
+ end
511
+ end