Skip to main content

ludtwig_parser/syntax/
untyped.rs

1//! This module contains all the rowan types.
2//!
3//! All GreenNodes and consequently RedNodes as well as AST Nodes
4//! contain a single [SyntaxKind] variant for each GreenNode.
5//!
6//! You can use the macro (T![])[crate::T] to quickly construct a [SyntaxKind]
7//! and for example compare it to a different one. For example
8//! ```
9//! use ludtwig_parser::syntax::untyped::SyntaxKind;
10//! use ludtwig_parser::T;
11//! assert_eq!(T!["%}"], SyntaxKind::TK_PERCENT_CURLY);
12//! ```
13//!
14//! An overview of the syntax tree concept can be found
15//! at the [crate level documentation](crate#syntax-trees).
16
17use logos::{Lexer, Logos};
18pub use rowan::Direction;
19// `GreenNode` is an immutable tree, which is cheap to change,
20// but doesn't contain offsets and parent pointers.
21pub use rowan::GreenNode;
22// You can construct `GreenNodes` by hand, but a builder
23// is helpful for top-down parsers: it maintains a stack
24// of currently in-progress nodes
25pub use rowan::GreenNodeBuilder;
26pub use rowan::Language;
27pub use rowan::SyntaxText;
28pub use rowan::TextLen;
29pub use rowan::TextRange;
30pub use rowan::TextSize;
31pub use rowan::WalkEvent;
32use std::fmt;
33use std::fmt::Formatter;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Logos)]
36#[allow(non_camel_case_types)]
37#[repr(u16)]
38pub enum SyntaxKind {
39    /*
40    Tokens without any meaning / semantic attached to them.
41    These are produced by the lexer and provide a small abstraction over plain text
42     */
43    #[regex(r"[ \t]+")]
44    TK_WHITESPACE = 0,
45    #[regex(r"((\n)|(\r\n))+")]
46    TK_LINE_BREAK,
47    /// a single word containing only characters, numbers or symbols
48    /// must start with an alpha or one of the special starting characters followed by a normal alpha
49    /// special case: allows a single underscore as a valid word
50    #[regex(r"([a-zA-Z]|([@\#_\$][a-zA-Z])|_)[a-zA-Z0-9_\-]*")]
51    TK_WORD,
52    /// matches namespaced twig component names like 'twig:my:component'
53    #[regex(r"twig(\:[a-zA-Z0-9_\-]+)+")]
54    TK_TWIG_COMPONENT_NAME,
55    /// a valid twig number
56    #[regex(r"[0-9]+(\.[0-9]+)?([Ee][\+\-][0-9]+)?")]
57    TK_NUMBER,
58    /// a html escape character like '
' or '
' or '
'
59    #[regex(r"\&(([a-zA-Z][a-zA-Z0-9]*)|(\#[0-9]+)|(\#x[0-9a-fA-F]+));")]
60    TK_HTML_ESCAPE_CHARACTER,
61    #[token(".")]
62    TK_DOT,
63    #[token("..")]
64    TK_DOUBLE_DOT,
65    #[token("...")]
66    TK_TRIPLE_DOT,
67    #[token(",")]
68    TK_COMMA,
69    #[token(":")]
70    TK_COLON,
71    #[token(";")]
72    TK_SEMICOLON,
73    #[token("!")]
74    TK_EXCLAMATION_MARK,
75    #[token("!=")]
76    TK_EXCLAMATION_MARK_EQUALS,
77    #[token("!==")]
78    TK_EXCLAMATION_MARK_DOUBLE_EQUALS,
79    #[token("?")]
80    TK_QUESTION_MARK,
81    #[token("??")]
82    TK_DOUBLE_QUESTION_MARK,
83    #[token("%")]
84    TK_PERCENT,
85    #[token("~")]
86    TK_TILDE,
87    #[token("|")]
88    TK_SINGLE_PIPE,
89    #[token("||")]
90    TK_DOUBLE_PIPE,
91    #[token("&")]
92    TK_AMPERSAND,
93    #[token("&&")]
94    TK_DOUBLE_AMPERSAND,
95    #[token("/")]
96    TK_FORWARD_SLASH,
97    #[token("//")]
98    TK_DOUBLE_FORWARD_SLASH,
99    #[token("\\")]
100    TK_BACKWARD_SLASH,
101    #[token("(")]
102    TK_OPEN_PARENTHESIS,
103    #[token(")")]
104    TK_CLOSE_PARENTHESIS,
105    #[token("{")]
106    TK_OPEN_CURLY,
107    #[token("}")]
108    TK_CLOSE_CURLY,
109    #[token("[")]
110    TK_OPEN_SQUARE,
111    #[token("]")]
112    TK_CLOSE_SQUARE,
113    #[token("<")]
114    TK_LESS_THAN,
115    #[token("<=")]
116    TK_LESS_THAN_EQUAL,
117    #[token("<=>")]
118    TK_LESS_THAN_EQUAL_GREATER_THAN,
119    #[token("</")]
120    TK_LESS_THAN_SLASH,
121    #[token("<!")]
122    TK_LESS_THAN_EXCLAMATION_MARK,
123    #[token("DOCTYPE", ignore(case))]
124    TK_DOCTYPE,
125    #[token(">")]
126    TK_GREATER_THAN,
127    #[token(">=")]
128    TK_GREATER_THAN_EQUAL,
129    #[token("=>")]
130    TK_EQUAL_GREATER_THAN,
131    #[token("/>")]
132    TK_SLASH_GREATER_THAN,
133    #[token("<!--")]
134    TK_LESS_THAN_EXCLAMATION_MARK_MINUS_MINUS,
135    #[token("-->")]
136    TK_MINUS_MINUS_GREATER_THAN,
137    #[token("=")]
138    TK_EQUAL,
139    #[token("==")]
140    TK_DOUBLE_EQUAL,
141    #[token("===")]
142    TK_TRIPLE_EQUAL,
143    #[token("+")]
144    TK_PLUS,
145    #[token("-")]
146    TK_MINUS,
147    #[token("*")]
148    TK_STAR,
149    #[token("**")]
150    TK_DOUBLE_STAR,
151    #[token("\"")]
152    TK_DOUBLE_QUOTES,
153    #[token("'")]
154    TK_SINGLE_QUOTES,
155    #[token("`")]
156    TK_GRAVE_ACCENT_QUOTES,
157    #[token("{%")]
158    TK_CURLY_PERCENT,
159    #[token("%}")]
160    TK_PERCENT_CURLY,
161    #[token("{{")]
162    TK_OPEN_CURLY_CURLY,
163    #[token("}}")]
164    TK_CLOSE_CURLY_CURLY,
165    #[token("{#")]
166    TK_OPEN_CURLY_HASHTAG,
167    #[token("#}")]
168    TK_HASHTAG_CLOSE_CURLY,
169    #[token("#")]
170    TK_HASHTAG,
171
172    #[token("true", ignore(case))]
173    TK_TRUE,
174    #[token("false", ignore(case))]
175    TK_FALSE,
176
177    /* twig tag tokens */
178    #[token("block")]
179    TK_BLOCK,
180    #[token("endblock")]
181    TK_ENDBLOCK,
182    #[token("if")]
183    TK_IF,
184    #[token("elseif")]
185    TK_ELSE_IF,
186    #[token("else")]
187    TK_ELSE,
188    #[token("endif")]
189    TK_ENDIF,
190    #[token("apply")]
191    TK_APPLY,
192    #[token("endapply")]
193    TK_ENDAPPLY,
194    #[token("autoescape")]
195    TK_AUTOESCAPE,
196    #[token("endautoescape")]
197    TK_ENDAUTOESCAPE,
198    #[token("cache")]
199    TK_CACHE,
200    #[token("endcache")]
201    TK_ENDCACHE,
202    #[token("deprecated")]
203    TK_DEPRECATED,
204    #[token("do")]
205    TK_DO,
206    #[token("embed")]
207    TK_EMBED,
208    #[token("endembed")]
209    TK_ENDEMBED,
210    #[token("extends")]
211    TK_EXTENDS,
212    #[token("flush")]
213    TK_FLUSH,
214    #[token("for")]
215    TK_FOR,
216    #[token("endfor")]
217    TK_ENDFOR,
218    #[token("from")]
219    TK_FROM,
220    #[token("import")]
221    TK_IMPORT,
222    #[token("macro")]
223    TK_MACRO,
224    #[token("endmacro")]
225    TK_ENDMACRO,
226    #[token("sandbox")]
227    TK_SANDBOX,
228    #[token("endsandbox")]
229    TK_ENDSANDBOX,
230    #[token("set")]
231    TK_SET,
232    #[token("endset")]
233    TK_ENDSET,
234    #[token("use")]
235    TK_USE,
236    #[token("verbatim")]
237    TK_VERBATIM,
238    #[token("endverbatim")]
239    TK_ENDVERBATIM,
240    #[token("only")]
241    TK_ONLY,
242    #[token("ignore missing")]
243    TK_IGNORE_MISSING,
244    #[token("with")]
245    TK_WITH,
246    #[token("endwith")]
247    TK_ENDWITH,
248    #[token("ttl")]
249    TK_TTL,
250    #[token("tags")]
251    TK_TAGS,
252    #[token("props")]
253    TK_PROPS,
254    #[token("component")]
255    TK_COMPONENT,
256    #[token("endcomponent")]
257    TK_ENDCOMPONENT,
258    /* twig operators */
259    #[token("not", cb_not)]
260    TK_NOT,
261    TK_NOT_IN,
262    #[token("or")]
263    TK_OR,
264    #[token("and")]
265    TK_AND,
266    #[token("b-or")]
267    TK_BINARY_OR,
268    #[token("b-xor")]
269    TK_BINARY_XOR,
270    #[token("b-and")]
271    TK_BINARY_AND,
272    #[token("in")]
273    TK_IN,
274    #[token("matches")]
275    TK_MATCHES,
276    #[token("starts with")]
277    TK_STARTS_WITH,
278    #[token("ends with")]
279    TK_ENDS_WITH,
280    #[token("is", cb_is)]
281    TK_IS,
282    TK_IS_NOT,
283    /* twig tests */
284    #[token("even")]
285    TK_EVEN,
286    #[token("odd")]
287    TK_ODD,
288    #[token("defined")]
289    TK_DEFINED,
290    #[token("same as")]
291    TK_SAME_AS,
292    #[token("as")]
293    TK_AS,
294    #[token("none", ignore(case))]
295    TK_NONE,
296    #[token("null", ignore(case))]
297    TK_NULL,
298    #[token("divisible by")]
299    TK_DIVISIBLE_BY,
300    #[token("constant")]
301    TK_CONSTANT,
302    #[token("empty")]
303    TK_EMPTY,
304    #[token("iterable")]
305    TK_ITERABLE,
306    /* twig functions */
307    #[token("max")]
308    TK_MAX,
309    #[token("min")]
310    TK_MIN,
311    #[token("range")]
312    TK_RANGE,
313    #[token("cycle")]
314    TK_CYCLE,
315    #[token("random")]
316    TK_RANDOM,
317    #[token("date")]
318    TK_DATE,
319    #[token("include")]
320    TK_INCLUDE,
321    #[token("source")]
322    TK_SOURCE,
323
324    /* Drupal Trans */
325    #[token("trans")]
326    TK_TRANS,
327    #[token("endtrans")]
328    TK_ENDTRANS,
329
330    /* shopware specific */
331    #[token("sw_extends")]
332    TK_SW_EXTENDS,
333    #[token("sw_silent_feature_call")]
334    TK_SW_SILENT_FEATURE_CALL,
335    #[token("endsw_silent_feature_call")]
336    TK_ENDSW_SILENT_FEATURE_CALL,
337    #[token("sw_include")]
338    TK_SW_INCLUDE,
339    #[token("return")]
340    TK_RETURN,
341    #[token("sw_icon")]
342    TK_SW_ICON,
343    #[token("sw_thumbnails")]
344    TK_SW_THUMBNAILS,
345    #[token("style")]
346    TK_STYLE,
347    #[token("sw_embed")]
348    TK_SW_EMBED,
349    #[token("sw_end_embed")]
350    TK_SW_END_EMBED,
351    #[token("sw_use")]
352    TK_SW_USE,
353    #[token("sw_import")]
354    TK_SW_IMPORT,
355    #[token("sw_from")]
356    TK_SW_FROM,
357
358    /* special tokens */
359    #[token("ludtwig-ignore-file", ignore(case))]
360    TK_LUDTWIG_IGNORE_FILE,
361    #[token("ludtwig-ignore", ignore(case))]
362    TK_LUDTWIG_IGNORE,
363    TK_UNKNOWN, // contains invalid / unrecognized syntax (used for error recovery).
364
365    /*
366    Composite nodes (which can have children and ast / typed counterparts)
367    These do have a meaning and are constructed by the parser
368    */
369    BODY,
370    TWIG_VAR,
371    TWIG_EXPRESSION, // covers every expression (binary / unary) or literals (where expressions are allowed)
372    TWIG_BINARY_EXPRESSION,
373    TWIG_UNARY_EXPRESSION,
374    TWIG_PARENTHESES_EXPRESSION,
375    TWIG_CONDITIONAL_EXPRESSION,
376
377    TWIG_OPERAND, // covers the operands in TWIG_ACCESSOR, TWIG_INDEX_LOOKUP, TWIG_PIPE and TWIG_FUNCTION_CALL
378    TWIG_ACCESSOR, // accessor node like 'product.price'
379    TWIG_FILTER,  // filter node like 'name|title'
380
381    TWIG_INDEX_LOOKUP, // indexer node like 'products[0]'
382    TWIG_INDEX,        // covers a array index '5' inside []
383    TWIG_INDEX_RANGE,  // covers a array index range like '0:10' inside []
384
385    TWIG_FUNCTION_CALL,
386    TWIG_ARROW_FUNCTION, // like 'i => i % 2' or '(a, b) => a >= b'
387    TWIG_ARGUMENTS,
388    TWIG_NAMED_ARGUMENT,
389
390    // twig literals
391    TWIG_LITERAL_STRING,
392    TWIG_LITERAL_STRING_INNER,
393    TWIG_LITERAL_STRING_INTERPOLATION,
394    TWIG_LITERAL_NUMBER,
395    TWIG_LITERAL_ARRAY,
396    TWIG_LITERAL_ARRAY_INNER,
397    TWIG_LITERAL_NULL,
398    TWIG_LITERAL_BOOLEAN,
399    TWIG_LITERAL_HASH,
400    TWIG_LITERAL_HASH_ITEMS,
401    TWIG_LITERAL_HASH_PAIR,
402    TWIG_LITERAL_HASH_KEY,
403    TWIG_LITERAL_HASH_VALUE,
404    TWIG_LITERAL_NAME,
405
406    // twig block like structures
407    TWIG_COMMENT,
408    // twig block
409    TWIG_BLOCK,
410    TWIG_STARTING_BLOCK,
411    TWIG_ENDING_BLOCK,
412    // twig if
413    TWIG_IF,
414    TWIG_IF_BLOCK,
415    TWIG_ELSE_IF_BLOCK,
416    TWIG_ELSE_BLOCK,
417    TWIG_ENDIF_BLOCK,
418    // twig set
419    TWIG_SET,
420    TWIG_SET_BLOCK,
421    TWIG_ENDSET_BLOCK,
422    TWIG_ASSIGNMENT,
423    // twig for
424    TWIG_FOR,
425    TWIG_FOR_BLOCK,
426    TWIG_FOR_ELSE_BLOCK,
427    TWIG_ENDFOR_BLOCK,
428    // twig extends
429    TWIG_EXTENDS,
430    // twig include
431    TWIG_INCLUDE,
432    TWIG_INCLUDE_WITH,
433    // twig use
434    TWIG_USE,
435    TWIG_OVERRIDE,
436    // twig apply
437    TWIG_APPLY,
438    TWIG_APPLY_STARTING_BLOCK,
439    TWIG_APPLY_ENDING_BLOCK,
440    // twig autoescape
441    TWIG_AUTOESCAPE,
442    TWIG_AUTOESCAPE_STARTING_BLOCK,
443    TWIG_AUTOESCAPE_ENDING_BLOCK,
444    // twig deprecated
445    TWIG_DEPRECATED,
446    // twig do
447    TWIG_DO,
448    // twig embed
449    TWIG_EMBED,
450    TWIG_EMBED_STARTING_BLOCK,
451    TWIG_EMBED_ENDING_BLOCK,
452    // twig flush
453    TWIG_FLUSH,
454    // twig from
455    TWIG_FROM, // shares TWIG_OVERRIDE with twig use tag
456    // twig import
457    TWIG_IMPORT,
458    // twig sandbox
459    TWIG_SANDBOX,
460    TWIG_SANDBOX_STARTING_BLOCK,
461    TWIG_SANDBOX_ENDING_BLOCK,
462    // twig verbatim
463    TWIG_VERBATIM,
464    TWIG_VERBATIM_STARTING_BLOCK,
465    TWIG_VERBATIM_ENDING_BLOCK,
466    // twig macro
467    TWIG_MACRO,
468    TWIG_MACRO_STARTING_BLOCK,
469    TWIG_MACRO_ENDING_BLOCK,
470    // twig with
471    TWIG_WITH,
472    TWIG_WITH_STARTING_BLOCK,
473    TWIG_WITH_ENDING_BLOCK,
474    // twig cache
475    TWIG_CACHE,
476    TWIG_CACHE_TTL,
477    TWIG_CACHE_TAGS,
478    TWIG_CACHE_STARTING_BLOCK,
479    TWIG_CACHE_ENDING_BLOCK,
480    // twig props
481    TWIG_PROPS,
482    TWIG_PROP_DECLARATION,
483    // twig component
484    TWIG_COMPONENT,
485    TWIG_COMPONENT_STARTING_BLOCK,
486    TWIG_COMPONENT_ENDING_BLOCK,
487
488    // Drupal Trans
489    TWIG_TRANS,
490    TWIG_TRANS_STARTING_BLOCK,
491    TWIG_TRANS_ENDING_BLOCK,
492
493    // shopware specific
494    SHOPWARE_TWIG_SW_EXTENDS,
495    SHOPWARE_TWIG_SW_INCLUDE,
496    SHOPWARE_SILENT_FEATURE_CALL,
497    SHOPWARE_SILENT_FEATURE_CALL_STARTING_BLOCK,
498    SHOPWARE_SILENT_FEATURE_CALL_ENDING_BLOCK,
499    SHOPWARE_RETURN,
500    SHOPWARE_ICON,
501    SHOPWARE_ICON_STYLE,
502    SHOPWARE_THUMBNAILS,
503    SHOPWARE_THUMBNAILS_WITH,
504
505    // html
506    HTML_DOCTYPE,
507    HTML_ATTRIBUTE_LIST,
508    HTML_ATTRIBUTE,
509    HTML_STRING,       // used as attribute values
510    HTML_STRING_INNER, // content inside the quotes of html attribute values
511    HTML_TEXT,         // used as plain text between html tags / twig blocks
512    HTML_RAW_TEXT, // used as raw text between "raw text" html elements like inline script and style
513    HTML_COMMENT,
514    HTML_TAG,
515    HTML_STARTING_TAG,
516    HTML_ENDING_TAG,
517
518    // special ludtwig directive
519    LUDTWIG_DIRECTIVE_FILE_IGNORE,
520    LUDTWIG_DIRECTIVE_IGNORE,
521    LUDTWIG_DIRECTIVE_RULE_LIST,
522    /*
523    Special Nodes
524     */
525    ERROR, // syntax node which wraps invalid syntax
526    /// SAFETY: this must be the last enum element for u16 conversion!
527    ROOT, // top-level node: list of elements inside the template (must be last item of enum for safety check!)
528}
529
530/// workaround to properly lex 'not insider' to TK_NOT + TK_WS + TK_WORD instead of TK_NOT_IN + TK_WORD
531fn cb_not(lexer: &mut Lexer<'_, SyntaxKind>) -> SyntaxKind {
532    const SUFFIX: &str = " in";
533    if lexer.remainder().starts_with(SUFFIX) {
534        let after = lexer.remainder().get(SUFFIX.len()..);
535        if after.is_none_or(|s| s.starts_with([' ', '\t', '\n']) || s.is_empty()) {
536            lexer.bump(SUFFIX.len());
537            SyntaxKind::TK_NOT_IN
538        } else {
539            SyntaxKind::TK_NOT
540        }
541    } else {
542        SyntaxKind::TK_NOT
543    }
544}
545
546/// workaround to properly lex 'is nothing' to TK_IS + TK_WS + TK_WORD instead of TK_IS_NOT + TK_WORD
547fn cb_is(lexer: &mut Lexer<'_, SyntaxKind>) -> SyntaxKind {
548    const SUFFIX: &str = " not";
549    if lexer.remainder().starts_with(SUFFIX) {
550        let after = lexer.remainder().get(SUFFIX.len()..);
551        if after.is_none_or(|s| s.starts_with([' ', '\t', '\n']) || s.is_empty()) {
552            lexer.bump(SUFFIX.len());
553            SyntaxKind::TK_IS_NOT
554        } else {
555            SyntaxKind::TK_IS
556        }
557    } else {
558        SyntaxKind::TK_IS
559    }
560}
561
562#[macro_export]
563macro_rules! T {
564    [ws] => { $crate::syntax::untyped::SyntaxKind::TK_WHITESPACE };
565    [lb] => { $crate::syntax::untyped::SyntaxKind::TK_LINE_BREAK };
566    [word] => { $crate::syntax::untyped::SyntaxKind::TK_WORD };
567    [twig component name] => { $crate::syntax::untyped::SyntaxKind::TK_TWIG_COMPONENT_NAME };
568    [number] => { $crate::syntax::untyped::SyntaxKind::TK_NUMBER };
569    [html escape character] => { $crate::syntax::untyped::SyntaxKind::TK_HTML_ESCAPE_CHARACTER };
570    [unknown] => { $crate::syntax::untyped::SyntaxKind::TK_UNKNOWN };
571    ["."] => { $crate::syntax::untyped::SyntaxKind::TK_DOT };
572    [".."] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_DOT };
573    ["..."] => { $crate::syntax::untyped::SyntaxKind::TK_TRIPLE_DOT };
574    [","] => { $crate::syntax::untyped::SyntaxKind::TK_COMMA };
575    [":"] => { $crate::syntax::untyped::SyntaxKind::TK_COLON };
576    [";"] => { $crate::syntax::untyped::SyntaxKind::TK_SEMICOLON };
577    ["!"] => { $crate::syntax::untyped::SyntaxKind::TK_EXCLAMATION_MARK };
578    ["!="] => { $crate::syntax::untyped::SyntaxKind::TK_EXCLAMATION_MARK_EQUALS };
579    ["!=="] => { $crate::syntax::untyped::SyntaxKind::TK_EXCLAMATION_MARK_DOUBLE_EQUALS };
580    ["?"] => { $crate::syntax::untyped::SyntaxKind::TK_QUESTION_MARK };
581    ["??"] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_QUESTION_MARK };
582    ["%"] => { $crate::syntax::untyped::SyntaxKind::TK_PERCENT };
583    ["~"] => { $crate::syntax::untyped::SyntaxKind::TK_TILDE };
584    ["|"] => { $crate::syntax::untyped::SyntaxKind::TK_SINGLE_PIPE };
585    ["||"] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_PIPE };
586    ["&"] => { $crate::syntax::untyped::SyntaxKind::TK_AMPERSAND };
587    ["&&"] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_AMPERSAND };
588    ["/"] => { $crate::syntax::untyped::SyntaxKind::TK_FORWARD_SLASH };
589    ["//"] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_FORWARD_SLASH };
590    ["\\"] => { $crate::syntax::untyped::SyntaxKind::TK_BACKWARD_SLASH };
591    ["("] => { $crate::syntax::untyped::SyntaxKind::TK_OPEN_PARENTHESIS };
592    [")"] => { $crate::syntax::untyped::SyntaxKind::TK_CLOSE_PARENTHESIS };
593    ["{"] => { $crate::syntax::untyped::SyntaxKind::TK_OPEN_CURLY };
594    ["}"] => { $crate::syntax::untyped::SyntaxKind::TK_CLOSE_CURLY };
595    ["["] => { $crate::syntax::untyped::SyntaxKind::TK_OPEN_SQUARE };
596    ["]"] => { $crate::syntax::untyped::SyntaxKind::TK_CLOSE_SQUARE };
597    ["<"] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN };
598    ["<="] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN_EQUAL };
599    ["<=>"] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN_EQUAL_GREATER_THAN };
600    ["</"] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN_SLASH };
601    ["<!"] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN_EXCLAMATION_MARK };
602    ["DOCTYPE"] => { $crate::syntax::untyped::SyntaxKind::TK_DOCTYPE };
603    [">"] => { $crate::syntax::untyped::SyntaxKind::TK_GREATER_THAN };
604    [">="] => { $crate::syntax::untyped::SyntaxKind::TK_GREATER_THAN_EQUAL };
605    ["=>"] => { $crate::syntax::untyped::SyntaxKind::TK_EQUAL_GREATER_THAN };
606    ["/>"] => { $crate::syntax::untyped::SyntaxKind::TK_SLASH_GREATER_THAN };
607    ["<!--"] => { $crate::syntax::untyped::SyntaxKind::TK_LESS_THAN_EXCLAMATION_MARK_MINUS_MINUS };
608    ["-->"] => { $crate::syntax::untyped::SyntaxKind::TK_MINUS_MINUS_GREATER_THAN };
609    ["="] => { $crate::syntax::untyped::SyntaxKind::TK_EQUAL };
610    ["=="] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_EQUAL };
611    ["==="] => { $crate::syntax::untyped::SyntaxKind::TK_TRIPLE_EQUAL };
612    ["+"] => { $crate::syntax::untyped::SyntaxKind::TK_PLUS };
613    ["-"] => { $crate::syntax::untyped::SyntaxKind::TK_MINUS };
614    ["*"] => { $crate::syntax::untyped::SyntaxKind::TK_STAR };
615    ["**"] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_STAR };
616    ["\""] => { $crate::syntax::untyped::SyntaxKind::TK_DOUBLE_QUOTES };
617    ["'"] => { $crate::syntax::untyped::SyntaxKind::TK_SINGLE_QUOTES };
618    ["`"] => { $crate::syntax::untyped::SyntaxKind::TK_GRAVE_ACCENT_QUOTES };
619    ["{%"] => { $crate::syntax::untyped::SyntaxKind::TK_CURLY_PERCENT };
620    ["%}"] => { $crate::syntax::untyped::SyntaxKind::TK_PERCENT_CURLY };
621    ["{{"] => { $crate::syntax::untyped::SyntaxKind::TK_OPEN_CURLY_CURLY };
622    ["}}"] => { $crate::syntax::untyped::SyntaxKind::TK_CLOSE_CURLY_CURLY };
623    ["{#"] => { $crate::syntax::untyped::SyntaxKind::TK_OPEN_CURLY_HASHTAG };
624    ["#}"] => { $crate::syntax::untyped::SyntaxKind::TK_HASHTAG_CLOSE_CURLY };
625    ["#"] => { $crate::syntax::untyped::SyntaxKind::TK_HASHTAG };
626    ["true"] => { $crate::syntax::untyped::SyntaxKind::TK_TRUE };
627    ["false"] => { $crate::syntax::untyped::SyntaxKind::TK_FALSE };
628    ["block"] => { $crate::syntax::untyped::SyntaxKind::TK_BLOCK };
629    ["endblock"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDBLOCK };
630    ["if"] => { $crate::syntax::untyped::SyntaxKind::TK_IF };
631    ["elseif"] => { $crate::syntax::untyped::SyntaxKind::TK_ELSE_IF };
632    ["else"] => { $crate::syntax::untyped::SyntaxKind::TK_ELSE };
633    ["endif"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDIF };
634    ["apply"] => { $crate::syntax::untyped::SyntaxKind::TK_APPLY };
635    ["endapply"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDAPPLY };
636    ["autoescape"] => { $crate::syntax::untyped::SyntaxKind::TK_AUTOESCAPE };
637    ["endautoescape"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDAUTOESCAPE };
638    ["cache"] => { $crate::syntax::untyped::SyntaxKind::TK_CACHE };
639    ["endcache"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDCACHE };
640    ["deprecated"] => { $crate::syntax::untyped::SyntaxKind::TK_DEPRECATED };
641    ["do"] => { $crate::syntax::untyped::SyntaxKind::TK_DO };
642    ["embed"] => { $crate::syntax::untyped::SyntaxKind::TK_EMBED };
643    ["endembed"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDEMBED };
644    ["extends"] => { $crate::syntax::untyped::SyntaxKind::TK_EXTENDS };
645    ["flush"] => { $crate::syntax::untyped::SyntaxKind::TK_FLUSH };
646    ["for"] => { $crate::syntax::untyped::SyntaxKind::TK_FOR };
647    ["endfor"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDFOR };
648    ["from"] => { $crate::syntax::untyped::SyntaxKind::TK_FROM };
649    ["import"] => { $crate::syntax::untyped::SyntaxKind::TK_IMPORT };
650    ["macro"] => { $crate::syntax::untyped::SyntaxKind::TK_MACRO };
651    ["endmacro"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDMACRO };
652    ["sandbox"] => { $crate::syntax::untyped::SyntaxKind::TK_SANDBOX };
653    ["endsandbox"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDSANDBOX };
654    ["set"] => { $crate::syntax::untyped::SyntaxKind::TK_SET };
655    ["endset"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDSET };
656    ["use"] => { $crate::syntax::untyped::SyntaxKind::TK_USE };
657    ["verbatim"] => { $crate::syntax::untyped::SyntaxKind::TK_VERBATIM };
658    ["endverbatim"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDVERBATIM };
659    ["only"] => { $crate::syntax::untyped::SyntaxKind::TK_ONLY };
660    ["ignore missing"] => { $crate::syntax::untyped::SyntaxKind::TK_IGNORE_MISSING };
661    ["with"] => { $crate::syntax::untyped::SyntaxKind::TK_WITH };
662    ["endwith"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDWITH };
663    ["ttl"] => { $crate::syntax::untyped::SyntaxKind::TK_TTL };
664    ["tags"] => { $crate::syntax::untyped::SyntaxKind::TK_TAGS };
665    ["props"] => { $crate::syntax::untyped::SyntaxKind::TK_PROPS };
666    ["component"] => { $crate::syntax::untyped::SyntaxKind::TK_COMPONENT };
667    ["endcomponent"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDCOMPONENT };
668    ["not"] => { $crate::syntax::untyped::SyntaxKind::TK_NOT };
669    ["not in"] => { $crate::syntax::untyped::SyntaxKind::TK_NOT_IN };
670    ["or"] => { $crate::syntax::untyped::SyntaxKind::TK_OR };
671    ["and"] => { $crate::syntax::untyped::SyntaxKind::TK_AND };
672    ["b-or"] => { $crate::syntax::untyped::SyntaxKind::TK_BINARY_OR };
673    ["b-xor"] => { $crate::syntax::untyped::SyntaxKind::TK_BINARY_XOR };
674    ["b-and"] => { $crate::syntax::untyped::SyntaxKind::TK_BINARY_AND };
675    ["in"] => { $crate::syntax::untyped::SyntaxKind::TK_IN };
676    ["matches"] => { $crate::syntax::untyped::SyntaxKind::TK_MATCHES };
677    ["starts with"] => { $crate::syntax::untyped::SyntaxKind::TK_STARTS_WITH };
678    ["ends with"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDS_WITH };
679    ["is"] => { $crate::syntax::untyped::SyntaxKind::TK_IS };
680    ["is not"] => { $crate::syntax::untyped::SyntaxKind::TK_IS_NOT };
681    ["even"] => { $crate::syntax::untyped::SyntaxKind::TK_EVEN };
682    ["odd"] => { $crate::syntax::untyped::SyntaxKind::TK_ODD };
683    ["defined"] => { $crate::syntax::untyped::SyntaxKind::TK_DEFINED };
684    ["same as"] => { $crate::syntax::untyped::SyntaxKind::TK_SAME_AS };
685    ["as"] => { $crate::syntax::untyped::SyntaxKind::TK_AS };
686    ["none"] => { $crate::syntax::untyped::SyntaxKind::TK_NONE };
687    ["null"] => { $crate::syntax::untyped::SyntaxKind::TK_NULL };
688    ["divisible by"] => { $crate::syntax::untyped::SyntaxKind::TK_DIVISIBLE_BY };
689    ["constant"] => { $crate::syntax::untyped::SyntaxKind::TK_CONSTANT };
690    ["empty"] => { $crate::syntax::untyped::SyntaxKind::TK_EMPTY };
691    ["iterable"] => { $crate::syntax::untyped::SyntaxKind::TK_ITERABLE };
692    ["max"] => { $crate::syntax::untyped::SyntaxKind::TK_MAX };
693    ["min"] => { $crate::syntax::untyped::SyntaxKind::TK_MIN };
694    ["range"] => { $crate::syntax::untyped::SyntaxKind::TK_RANGE };
695    ["cycle"] => { $crate::syntax::untyped::SyntaxKind::TK_CYCLE };
696    ["random"] => { $crate::syntax::untyped::SyntaxKind::TK_RANDOM };
697    ["date"] => { $crate::syntax::untyped::SyntaxKind::TK_DATE };
698    ["include"] => { $crate::syntax::untyped::SyntaxKind::TK_INCLUDE };
699    ["source"] => { $crate::syntax::untyped::SyntaxKind::TK_SOURCE };
700    ["trans"] => { $crate::syntax::untyped::SyntaxKind::TK_TRANS };
701    ["endtrans"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDTRANS };
702    ["sw_extends"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_EXTENDS };
703    ["sw_silent_feature_call"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_SILENT_FEATURE_CALL };
704    ["endsw_silent_feature_call"] => { $crate::syntax::untyped::SyntaxKind::TK_ENDSW_SILENT_FEATURE_CALL };
705    ["sw_include"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_INCLUDE };
706    ["return"] => { $crate::syntax::untyped::SyntaxKind::TK_RETURN };
707    ["sw_icon"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_ICON };
708    ["sw_thumbnails"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_THUMBNAILS };
709    ["style"] => { $crate::syntax::untyped::SyntaxKind::TK_STYLE };
710    ["sw_embed"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_EMBED };
711    ["sw_end_embed"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_END_EMBED };
712    ["sw_use"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_USE };
713    ["sw_import"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_IMPORT };
714    ["sw_from"] => { $crate::syntax::untyped::SyntaxKind::TK_SW_FROM };
715    ["ludtwig-ignore-file"] => { $crate::syntax::untyped::SyntaxKind::TK_LUDTWIG_IGNORE_FILE };
716    ["ludtwig-ignore"] => { $crate::syntax::untyped::SyntaxKind::TK_LUDTWIG_IGNORE };
717}
718
719impl SyntaxKind {
720    #[must_use]
721    pub fn is_trivia(self) -> bool {
722        // Add comments and other non interesting things for the parser here in the future
723        matches!(self, T![ws] | T![lb])
724    }
725}
726
727#[allow(clippy::too_many_lines)]
728impl fmt::Display for SyntaxKind {
729    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
730        f.write_str(match self {
731            SyntaxKind::TK_WHITESPACE => "whitespace",
732            SyntaxKind::TK_LINE_BREAK => "line break",
733            SyntaxKind::TK_WORD => "word",
734            SyntaxKind::TK_TWIG_COMPONENT_NAME => "twig component name",
735            SyntaxKind::TK_NUMBER => "number",
736            SyntaxKind::TK_HTML_ESCAPE_CHARACTER => "html escape character",
737            SyntaxKind::TK_DOT => ".",
738            SyntaxKind::TK_DOUBLE_DOT => "..",
739            SyntaxKind::TK_TRIPLE_DOT => "...",
740            SyntaxKind::TK_COMMA => ",",
741            SyntaxKind::TK_COLON => ":",
742            SyntaxKind::TK_SEMICOLON => ";",
743            SyntaxKind::TK_EXCLAMATION_MARK => "!",
744            SyntaxKind::TK_EXCLAMATION_MARK_EQUALS => "!=",
745            SyntaxKind::TK_EXCLAMATION_MARK_DOUBLE_EQUALS => "!==",
746            SyntaxKind::TK_QUESTION_MARK => "?",
747            SyntaxKind::TK_DOUBLE_QUESTION_MARK => "??",
748            SyntaxKind::TK_PERCENT => "%",
749            SyntaxKind::TK_TILDE => "~",
750            SyntaxKind::TK_SINGLE_PIPE => "|",
751            SyntaxKind::TK_DOUBLE_PIPE => "||",
752            SyntaxKind::TK_AMPERSAND => "&",
753            SyntaxKind::TK_DOUBLE_AMPERSAND => "&&",
754            SyntaxKind::TK_FORWARD_SLASH => "/",
755            SyntaxKind::TK_DOUBLE_FORWARD_SLASH => "//",
756            SyntaxKind::TK_BACKWARD_SLASH => "\\",
757            SyntaxKind::TK_OPEN_PARENTHESIS => "(",
758            SyntaxKind::TK_CLOSE_PARENTHESIS => ")",
759            SyntaxKind::TK_OPEN_CURLY => "{",
760            SyntaxKind::TK_CLOSE_CURLY => "}",
761            SyntaxKind::TK_OPEN_SQUARE => "[",
762            SyntaxKind::TK_CLOSE_SQUARE => "]",
763            SyntaxKind::TK_LESS_THAN => "<",
764            SyntaxKind::TK_LESS_THAN_EQUAL => "<=",
765            SyntaxKind::TK_LESS_THAN_EQUAL_GREATER_THAN => "<=>",
766            SyntaxKind::TK_LESS_THAN_SLASH => "</",
767            SyntaxKind::TK_LESS_THAN_EXCLAMATION_MARK => "<!",
768            SyntaxKind::TK_DOCTYPE => "DOCTYPE",
769            SyntaxKind::TK_GREATER_THAN => ">",
770            SyntaxKind::TK_GREATER_THAN_EQUAL => ">=",
771            SyntaxKind::TK_EQUAL_GREATER_THAN => "=>",
772            SyntaxKind::TK_SLASH_GREATER_THAN => "/>",
773            SyntaxKind::TK_LESS_THAN_EXCLAMATION_MARK_MINUS_MINUS => "<!--",
774            SyntaxKind::TK_MINUS_MINUS_GREATER_THAN => "-->",
775            SyntaxKind::TK_EQUAL => "=",
776            SyntaxKind::TK_DOUBLE_EQUAL => "==",
777            SyntaxKind::TK_TRIPLE_EQUAL => "===",
778            SyntaxKind::TK_PLUS => "+",
779            SyntaxKind::TK_MINUS => "-",
780            SyntaxKind::TK_STAR => "*",
781            SyntaxKind::TK_DOUBLE_STAR => "**",
782            SyntaxKind::TK_DOUBLE_QUOTES => "\"",
783            SyntaxKind::TK_SINGLE_QUOTES => "'",
784            SyntaxKind::TK_GRAVE_ACCENT_QUOTES => "`",
785            SyntaxKind::TK_CURLY_PERCENT => "{%",
786            SyntaxKind::TK_PERCENT_CURLY => "%}",
787            SyntaxKind::TK_OPEN_CURLY_CURLY => "{{",
788            SyntaxKind::TK_CLOSE_CURLY_CURLY => "}}",
789            SyntaxKind::TK_OPEN_CURLY_HASHTAG => "{#",
790            SyntaxKind::TK_HASHTAG_CLOSE_CURLY => "#}",
791            SyntaxKind::TK_HASHTAG => "#",
792            SyntaxKind::TK_TRUE => "true",
793            SyntaxKind::TK_FALSE => "false",
794            SyntaxKind::TK_BLOCK => "block",
795            SyntaxKind::TK_ENDBLOCK => "endblock",
796            SyntaxKind::TK_IF => "if",
797            SyntaxKind::TK_ELSE_IF => "elseif",
798            SyntaxKind::TK_ELSE => "else",
799            SyntaxKind::TK_ENDIF => "endif",
800            SyntaxKind::TK_APPLY => "apply",
801            SyntaxKind::TK_ENDAPPLY => "endapply",
802            SyntaxKind::TK_AUTOESCAPE => "autoescape",
803            SyntaxKind::TK_ENDAUTOESCAPE => "endautoescape",
804            SyntaxKind::TK_CACHE => "cache",
805            SyntaxKind::TK_ENDCACHE => "endcache",
806            SyntaxKind::TK_DEPRECATED => "deprecated",
807            SyntaxKind::TK_DO => "do",
808            SyntaxKind::TK_EMBED => "embed",
809            SyntaxKind::TK_ENDEMBED => "endembed",
810            SyntaxKind::TK_EXTENDS => "extends",
811            SyntaxKind::TK_FLUSH => "flush",
812            SyntaxKind::TK_FOR => "for",
813            SyntaxKind::TK_ENDFOR => "endfor",
814            SyntaxKind::TK_FROM => "from",
815            SyntaxKind::TK_IMPORT => "import",
816            SyntaxKind::TK_MACRO => "macro",
817            SyntaxKind::TK_ENDMACRO => "endmacro",
818            SyntaxKind::TK_SANDBOX => "sandbox",
819            SyntaxKind::TK_ENDSANDBOX => "endsandbox",
820            SyntaxKind::TK_SET => "set",
821            SyntaxKind::TK_ENDSET => "endset",
822            SyntaxKind::TK_USE => "use",
823            SyntaxKind::TK_VERBATIM => "verbatim",
824            SyntaxKind::TK_ENDVERBATIM => "endverbatim",
825            SyntaxKind::TK_ONLY => "only",
826            SyntaxKind::TK_IGNORE_MISSING => "ignore missing",
827            SyntaxKind::TK_WITH => "with",
828            SyntaxKind::TK_ENDWITH => "endwith",
829            SyntaxKind::TK_TTL => "ttl",
830            SyntaxKind::TK_TAGS => "tags",
831            SyntaxKind::TK_PROPS => "props",
832            SyntaxKind::TK_COMPONENT => "component",
833            SyntaxKind::TK_ENDCOMPONENT => "endcomponent",
834            SyntaxKind::TK_NOT => "not",
835            SyntaxKind::TK_NOT_IN => "not in",
836            SyntaxKind::TK_OR => "or",
837            SyntaxKind::TK_AND => "and",
838            SyntaxKind::TK_BINARY_OR => "b-or",
839            SyntaxKind::TK_BINARY_XOR => "b-xor",
840            SyntaxKind::TK_BINARY_AND => "b-and",
841            SyntaxKind::TK_IN => "in",
842            SyntaxKind::TK_MATCHES => "matches",
843            SyntaxKind::TK_STARTS_WITH => "starts with",
844            SyntaxKind::TK_ENDS_WITH => "ends with",
845            SyntaxKind::TK_IS => "is",
846            SyntaxKind::TK_IS_NOT => "is not",
847            SyntaxKind::TK_EVEN => "even",
848            SyntaxKind::TK_ODD => "odd",
849            SyntaxKind::TK_DEFINED => "defined",
850            SyntaxKind::TK_SAME_AS => "same as",
851            SyntaxKind::TK_AS => "as",
852            SyntaxKind::TK_NONE => "none",
853            SyntaxKind::TK_NULL => "null",
854            SyntaxKind::TK_DIVISIBLE_BY => "divisible by",
855            SyntaxKind::TK_CONSTANT => "constant",
856            SyntaxKind::TK_EMPTY => "empty",
857            SyntaxKind::TK_ITERABLE => "iterable",
858            SyntaxKind::TK_MAX => "max",
859            SyntaxKind::TK_MIN => "min",
860            SyntaxKind::TK_RANGE => "range",
861            SyntaxKind::TK_CYCLE => "cycle",
862            SyntaxKind::TK_RANDOM => "random",
863            SyntaxKind::TK_DATE => "date",
864            SyntaxKind::TK_INCLUDE => "include",
865            SyntaxKind::TK_SOURCE => "source",
866            SyntaxKind::TK_TRANS => "trans",
867            SyntaxKind::TK_ENDTRANS => "endtrans",
868            SyntaxKind::TK_SW_EXTENDS => "sw_extends",
869            SyntaxKind::TK_SW_SILENT_FEATURE_CALL => "sw_silent_feature_call",
870            SyntaxKind::TK_ENDSW_SILENT_FEATURE_CALL => "endsw_silent_feature_call",
871            SyntaxKind::TK_SW_INCLUDE => "sw_include",
872            SyntaxKind::TK_RETURN => "return",
873            SyntaxKind::TK_SW_ICON => "sw_icon",
874            SyntaxKind::TK_SW_THUMBNAILS => "sw_thumbnails",
875            SyntaxKind::TK_STYLE => "style",
876            SyntaxKind::TK_SW_EMBED => "sw_embed",
877            SyntaxKind::TK_SW_END_EMBED => "sw_end_embed",
878            SyntaxKind::TK_SW_USE => "sw_use",
879            SyntaxKind::TK_SW_IMPORT => "sw_import",
880            SyntaxKind::TK_SW_FROM => "sw_from",
881            SyntaxKind::TK_LUDTWIG_IGNORE_FILE => "ludtwig-ignore-file",
882            SyntaxKind::TK_LUDTWIG_IGNORE => "ludtwig-ignore",
883            SyntaxKind::TK_UNKNOWN => "unknown",
884            SyntaxKind::ERROR => "error",
885            t => unreachable!("Display not implemented for {:?}", t),
886        })
887    }
888}
889
890// Some boilerplate is needed, as rowan settled on using its own
891// `struct SyntaxKind(u16)` internally, instead of accepting the
892// user's `enum SyntaxKind` as a type parameter.
893//
894// First, to easily pass the enum variants into rowan via `.into()`:
895impl From<SyntaxKind> for rowan::SyntaxKind {
896    fn from(kind: SyntaxKind) -> Self {
897        Self(kind as u16)
898    }
899}
900
901// Second, implementing the `Language` trait teaches rowan to convert between
902// these two `SyntaxKind` types, allowing for a nicer `SyntaxNode` API where
903// "kinds" are values from our `enum SyntaxKind`, instead of plain u16 values.
904#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
905pub enum TemplateLanguage {}
906impl Language for TemplateLanguage {
907    type Kind = SyntaxKind;
908    fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
909        assert!(raw.0 <= SyntaxKind::ROOT as u16);
910        unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
911    }
912    fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
913        kind.into()
914    }
915}
916
917// To work with the parse results we need a view into the
918// green tree - the Syntax tree.
919// It is also immutable, like a `GreenNode`,
920// but it contains parent pointers, offsets, and
921// has identity semantics.
922pub type SyntaxNode = rowan::SyntaxNode<TemplateLanguage>;
923pub type SyntaxToken = rowan::SyntaxToken<TemplateLanguage>;
924pub type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
925pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<TemplateLanguage>;
926pub type SyntaxElementChildren = rowan::SyntaxElementChildren<TemplateLanguage>;
927pub type Preorder = rowan::api::Preorder<TemplateLanguage>;
928pub type PreorderWithTokens = rowan::api::PreorderWithTokens<TemplateLanguage>;
929
930#[must_use]
931pub fn debug_tree(syntax_node: &SyntaxNode) -> String {
932    let formatted = format!("{syntax_node:#?}");
933    // We cut off the last byte because formatting the SyntaxNode adds on a newline at the end.
934    formatted[0..formatted.len() - 1].to_string()
935}
936
937pub trait SyntaxNodeExt {
938    fn text_range_trimmed_trivia(&self) -> TextRange;
939}
940
941impl SyntaxNodeExt for SyntaxNode {
942    /// Trims leading trivia from the original `text_range`
943    fn text_range_trimmed_trivia(&self) -> TextRange {
944        let mut range = self.text_range();
945
946        for element in self.children_with_tokens() {
947            match element {
948                SyntaxElement::Token(t) if t.kind().is_trivia() => {
949                    let new_start = range.start() + t.text_range().len();
950                    if new_start < range.end() {
951                        range = TextRange::new(new_start, range.end());
952                    }
953                }
954                _ => return range,
955            }
956        }
957
958        range
959    }
960}