w_syntax_tree-erb 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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