1use rowan::NodeOrToken;
11pub use rowan::ast::AstChildren;
12pub use rowan::ast::AstNode;
13pub use rowan::ast::support;
14use std::fmt::{Debug, Display, Formatter};
15
16use crate::T;
17
18use super::untyped::{
19 SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TemplateLanguage, debug_tree,
20};
21
22macro_rules! ast_node {
34 ($ast:ident, $kind:path) => {
35 #[derive(Clone, PartialEq, Eq, Hash)]
36 pub struct $ast {
37 pub(crate) syntax: SyntaxNode,
38 }
39
40 impl AstNode for $ast {
41 type Language = TemplateLanguage;
42
43 fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool
44 where
45 Self: Sized,
46 {
47 kind == $kind
48 }
49
50 fn cast(node: rowan::SyntaxNode<Self::Language>) -> Option<Self>
51 where
52 Self: Sized,
53 {
54 if Self::can_cast(node.kind()) {
55 Some(Self { syntax: node })
56 } else {
57 None
58 }
59 }
60
61 fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
62 &self.syntax
63 }
64 }
65
66 impl Display for $ast {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 write!(f, "{}", self.syntax)?;
69 Ok(())
70 }
71 }
72
73 impl Debug for $ast {
74 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}", debug_tree(&self.syntax))?;
76 Ok(())
77 }
78 }
79 };
80}
81
82ast_node!(TwigBlock, SyntaxKind::TWIG_BLOCK);
83impl TwigBlock {
84 #[must_use]
86 pub fn name(&self) -> Option<SyntaxToken> {
87 match self.starting_block() {
88 None => None,
89 Some(n) => n.name(),
90 }
91 }
92
93 #[must_use]
94 pub fn starting_block(&self) -> Option<TwigStartingBlock> {
95 support::child(&self.syntax)
96 }
97
98 #[must_use]
99 pub fn body(&self) -> Option<Body> {
100 support::child(&self.syntax)
101 }
102
103 #[must_use]
104 pub fn ending_block(&self) -> Option<TwigEndingBlock> {
105 support::child(&self.syntax)
106 }
107}
108
109ast_node!(TwigStartingBlock, SyntaxKind::TWIG_STARTING_BLOCK);
110impl TwigStartingBlock {
111 #[must_use]
113 pub fn name(&self) -> Option<SyntaxToken> {
114 support::token(&self.syntax, T![word])
115 }
116
117 #[must_use]
119 pub fn twig_block(&self) -> Option<TwigBlock> {
120 match self.syntax.parent() {
121 Some(p) => TwigBlock::cast(p),
122 None => None,
123 }
124 }
125}
126
127ast_node!(TwigEndingBlock, SyntaxKind::TWIG_ENDING_BLOCK);
128impl TwigEndingBlock {
129 #[must_use]
131 pub fn twig_block(&self) -> Option<TwigBlock> {
132 match self.syntax.parent() {
133 Some(p) => TwigBlock::cast(p),
134 None => None,
135 }
136 }
137}
138
139ast_node!(HtmlTag, SyntaxKind::HTML_TAG);
140impl HtmlTag {
141 #[must_use]
143 pub fn name(&self) -> Option<SyntaxToken> {
144 match self.starting_tag() {
145 Some(n) => n.name(),
146 None => None,
147 }
148 }
149
150 #[must_use]
152 pub fn is_self_closing(&self) -> bool {
153 self.ending_tag().is_none()
154 }
155
156 #[must_use]
158 pub fn attributes(&self) -> AstChildren<HtmlAttribute> {
159 match self.starting_tag() {
160 Some(n) => n.attributes(),
161 None => support::children(&self.syntax),
163 }
164 }
165
166 #[must_use]
168 pub fn is_twig_component(&self) -> bool {
169 match self.starting_tag() {
170 Some(n) => n.is_twig_component(),
171 None => false,
172 }
173 }
174
175 #[must_use]
176 pub fn starting_tag(&self) -> Option<HtmlStartingTag> {
177 support::child(&self.syntax)
178 }
179
180 #[must_use]
181 pub fn body(&self) -> Option<Body> {
182 support::child(&self.syntax)
183 }
184
185 #[must_use]
186 pub fn ending_tag(&self) -> Option<HtmlEndingTag> {
187 support::child(&self.syntax)
188 }
189}
190
191ast_node!(HtmlStartingTag, SyntaxKind::HTML_STARTING_TAG);
192impl HtmlStartingTag {
193 #[must_use]
195 pub fn name(&self) -> Option<SyntaxToken> {
196 self.syntax
197 .children_with_tokens()
198 .filter_map(NodeOrToken::into_token)
199 .find(|it| it.kind() == T![word] || it.kind() == T![twig component name])
200 }
201
202 #[must_use]
204 pub fn attributes(&self) -> AstChildren<HtmlAttribute> {
205 match support::child::<HtmlAttributeList>(&self.syntax) {
206 Some(list) => support::children(&list.syntax),
207 None => support::children(&self.syntax),
209 }
210 }
211
212 #[must_use]
214 pub fn html_tag(&self) -> Option<HtmlTag> {
215 match self.syntax.parent() {
216 Some(p) => HtmlTag::cast(p),
217 None => None,
218 }
219 }
220
221 #[must_use]
223 pub fn is_twig_component(&self) -> bool {
224 support::token(&self.syntax, T![twig component name]).is_some()
225 }
226}
227
228ast_node!(HtmlAttribute, SyntaxKind::HTML_ATTRIBUTE);
229impl HtmlAttribute {
230 #[must_use]
232 pub fn name(&self) -> Option<SyntaxToken> {
233 support::token(&self.syntax, T![word])
234 }
235
236 #[must_use]
238 pub fn value(&self) -> Option<HtmlString> {
239 support::child(&self.syntax)
240 }
241
242 #[must_use]
244 pub fn html_tag(&self) -> Option<HtmlStartingTag> {
245 match self.syntax.parent()?.parent() {
247 Some(p) => HtmlStartingTag::cast(p),
248 None => None,
249 }
250 }
251}
252
253ast_node!(HtmlEndingTag, SyntaxKind::HTML_ENDING_TAG);
254impl HtmlEndingTag {
255 #[must_use]
257 pub fn name(&self) -> Option<SyntaxToken> {
258 self.syntax
259 .children_with_tokens()
260 .filter_map(NodeOrToken::into_token)
261 .find(|it| it.kind() == T![word] || it.kind() == T![twig component name])
262 }
263
264 #[must_use]
266 pub fn html_tag(&self) -> Option<HtmlTag> {
267 match self.syntax.parent() {
268 Some(p) => HtmlTag::cast(p),
269 None => None,
270 }
271 }
272
273 #[must_use]
275 pub fn is_twig_component(&self) -> bool {
276 support::token(&self.syntax, T![twig component name]).is_some()
277 }
278}
279
280ast_node!(TwigBinaryExpression, SyntaxKind::TWIG_BINARY_EXPRESSION);
281impl TwigBinaryExpression {
282 #[must_use]
283 pub fn operator(&self) -> Option<SyntaxToken> {
284 self.syntax
285 .children_with_tokens()
286 .find_map(|element| match element {
287 SyntaxElement::Token(t) if !t.kind().is_trivia() => Some(t),
288 _ => None,
289 })
290 }
291
292 #[must_use]
293 pub fn lhs_expression(&self) -> Option<TwigExpression> {
294 self.syntax.children().find_map(TwigExpression::cast)
295 }
296
297 #[must_use]
298 pub fn rhs_expression(&self) -> Option<TwigExpression> {
299 self.syntax
300 .children()
301 .filter_map(TwigExpression::cast)
302 .nth(1)
303 }
304}
305
306ast_node!(
307 LudtwigDirectiveRuleList,
308 SyntaxKind::LUDTWIG_DIRECTIVE_RULE_LIST
309);
310impl LudtwigDirectiveRuleList {
311 #[must_use]
312 pub fn get_rule_names(&self) -> Vec<String> {
313 self.syntax
314 .children_with_tokens()
315 .filter_map(|element| match element {
316 NodeOrToken::Token(t) if t.kind() == SyntaxKind::TK_WORD => {
317 Some(t.text().to_string())
318 }
319 _ => None,
320 })
321 .collect()
322 }
323}
324
325ast_node!(
326 LudtwigDirectiveFileIgnore,
327 SyntaxKind::LUDTWIG_DIRECTIVE_FILE_IGNORE
328);
329impl LudtwigDirectiveFileIgnore {
330 #[must_use]
331 pub fn get_rules(&self) -> Vec<String> {
332 match support::child::<LudtwigDirectiveRuleList>(&self.syntax) {
333 Some(rule_list) => rule_list.get_rule_names(),
334 None => vec![],
335 }
336 }
337}
338
339ast_node!(LudtwigDirectiveIgnore, SyntaxKind::LUDTWIG_DIRECTIVE_IGNORE);
340impl LudtwigDirectiveIgnore {
341 #[must_use]
342 pub fn get_rules(&self) -> Vec<String> {
343 match support::child::<LudtwigDirectiveRuleList>(&self.syntax) {
344 Some(rule_list) => rule_list.get_rule_names(),
345 None => vec![],
346 }
347 }
348}
349
350ast_node!(TwigLiteralString, SyntaxKind::TWIG_LITERAL_STRING);
351impl TwigLiteralString {
352 #[must_use]
353 pub fn get_inner(&self) -> Option<TwigLiteralStringInner> {
354 support::child(&self.syntax)
355 }
356
357 #[must_use]
358 pub fn get_opening_quote(&self) -> Option<SyntaxToken> {
359 self.syntax
360 .children_with_tokens()
361 .take_while(|element| {
362 if element.as_node().is_some() {
363 return false; }
365
366 true
367 })
368 .find_map(|element| match element {
369 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
371 _ => None,
372 })
373 }
374
375 #[must_use]
376 pub fn get_closing_quote(&self) -> Option<SyntaxToken> {
377 self.syntax
378 .children_with_tokens()
379 .skip_while(|element| {
380 if element.as_node().is_some() {
381 return false; }
383
384 true
385 })
386 .find_map(|element| match element {
387 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
389 _ => None,
390 })
391 }
392}
393
394ast_node!(
395 TwigLiteralStringInner,
396 SyntaxKind::TWIG_LITERAL_STRING_INNER
397);
398impl TwigLiteralStringInner {
399 #[must_use]
400 pub fn get_interpolations(&self) -> AstChildren<TwigLiteralStringInterpolation> {
401 support::children(&self.syntax)
402 }
403}
404
405ast_node!(HtmlString, SyntaxKind::HTML_STRING);
406impl HtmlString {
407 #[must_use]
408 pub fn get_inner(&self) -> Option<HtmlStringInner> {
409 support::child(&self.syntax)
410 }
411
412 #[must_use]
413 pub fn get_opening_quote(&self) -> Option<SyntaxToken> {
414 self.syntax
415 .children_with_tokens()
416 .take_while(|element| {
417 if element.as_node().is_some() {
418 return false; }
420
421 true
422 })
423 .find_map(|element| match element {
424 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
426 _ => None,
427 })
428 }
429
430 #[must_use]
431 pub fn get_closing_quote(&self) -> Option<SyntaxToken> {
432 self.syntax
433 .children_with_tokens()
434 .skip_while(|element| {
435 if element.as_node().is_some() {
436 return false; }
438
439 true
440 })
441 .find_map(|element| match element {
442 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
444 _ => None,
445 })
446 }
447}
448
449ast_node!(TwigExtends, SyntaxKind::TWIG_EXTENDS);
450impl TwigExtends {
451 #[must_use]
452 pub fn get_extends_keyword(&self) -> Option<SyntaxToken> {
453 support::token(&self.syntax, T!["extends"])
454 }
455}
456
457ast_node!(TwigVar, SyntaxKind::TWIG_VAR);
458impl TwigVar {
459 #[must_use]
460 pub fn get_expression(&self) -> Option<TwigExpression> {
461 support::child(&self.syntax)
462 }
463}
464
465ast_node!(TwigLiteralName, SyntaxKind::TWIG_LITERAL_NAME);
466impl TwigLiteralName {
467 #[must_use]
468 pub fn get_name(&self) -> Option<SyntaxToken> {
469 support::token(&self.syntax, SyntaxKind::TK_WORD)
470 }
471}
472
473ast_node!(Body, SyntaxKind::BODY);
474ast_node!(TwigExpression, SyntaxKind::TWIG_EXPRESSION);
475ast_node!(TwigUnaryExpression, SyntaxKind::TWIG_UNARY_EXPRESSION);
476ast_node!(
477 TwigParenthesesExpression,
478 SyntaxKind::TWIG_PARENTHESES_EXPRESSION
479);
480ast_node!(
481 TwigConditionalExpression,
482 SyntaxKind::TWIG_CONDITIONAL_EXPRESSION
483);
484ast_node!(TwigOperand, SyntaxKind::TWIG_OPERAND);
485ast_node!(TwigAccessor, SyntaxKind::TWIG_ACCESSOR);
486ast_node!(TwigFilter, SyntaxKind::TWIG_FILTER);
487impl TwigFilter {
488 #[must_use]
490 pub fn operand(&self) -> Option<TwigOperand> {
491 support::child(&self.syntax)
492 }
493
494 #[must_use]
497 pub fn filter(&self) -> Option<TwigOperand> {
498 support::children(&self.syntax).nth(1)
499 }
500}
501ast_node!(TwigIndexLookup, SyntaxKind::TWIG_INDEX_LOOKUP);
502ast_node!(TwigIndex, SyntaxKind::TWIG_INDEX);
503ast_node!(TwigIndexRange, SyntaxKind::TWIG_INDEX_RANGE);
504ast_node!(TwigFunctionCall, SyntaxKind::TWIG_FUNCTION_CALL);
505impl TwigFunctionCall {
506 #[must_use]
509 pub fn name_operand(&self) -> Option<TwigOperand> {
510 support::child(&self.syntax)
511 }
512
513 #[must_use]
515 pub fn arguments(&self) -> Option<TwigArguments> {
516 support::child(&self.syntax)
517 }
518}
519ast_node!(TwigArrowFunction, SyntaxKind::TWIG_ARROW_FUNCTION);
520ast_node!(TwigArguments, SyntaxKind::TWIG_ARGUMENTS);
521ast_node!(TwigNamedArgument, SyntaxKind::TWIG_NAMED_ARGUMENT);
522ast_node!(
523 TwigLiteralStringInterpolation,
524 SyntaxKind::TWIG_LITERAL_STRING_INTERPOLATION
525);
526ast_node!(TwigLiteralNumber, SyntaxKind::TWIG_LITERAL_NUMBER);
527ast_node!(TwigLiteralArray, SyntaxKind::TWIG_LITERAL_ARRAY);
528ast_node!(TwigLiteralArrayInner, SyntaxKind::TWIG_LITERAL_ARRAY_INNER);
529ast_node!(TwigLiteralNull, SyntaxKind::TWIG_LITERAL_NULL);
530ast_node!(TwigLiteralBoolean, SyntaxKind::TWIG_LITERAL_BOOLEAN);
531ast_node!(TwigLiteralHash, SyntaxKind::TWIG_LITERAL_HASH);
532ast_node!(TwigLiteralHashItems, SyntaxKind::TWIG_LITERAL_HASH_ITEMS);
533ast_node!(TwigLiteralHashPair, SyntaxKind::TWIG_LITERAL_HASH_PAIR);
534ast_node!(TwigLiteralHashKey, SyntaxKind::TWIG_LITERAL_HASH_KEY);
535ast_node!(TwigLiteralHashValue, SyntaxKind::TWIG_LITERAL_HASH_VALUE);
536ast_node!(TwigComment, SyntaxKind::TWIG_COMMENT);
537ast_node!(TwigIf, SyntaxKind::TWIG_IF);
538ast_node!(TwigIfBlock, SyntaxKind::TWIG_IF_BLOCK);
539ast_node!(TwigElseIfBlock, SyntaxKind::TWIG_ELSE_IF_BLOCK);
540ast_node!(TwigElseBlock, SyntaxKind::TWIG_ELSE_BLOCK);
541ast_node!(TwigEndIfBlock, SyntaxKind::TWIG_ENDIF_BLOCK);
542ast_node!(TwigSet, SyntaxKind::TWIG_SET);
543ast_node!(TwigSetBlock, SyntaxKind::TWIG_SET_BLOCK);
544ast_node!(TwigEndSetBlock, SyntaxKind::TWIG_ENDSET_BLOCK);
545ast_node!(TwigAssignment, SyntaxKind::TWIG_ASSIGNMENT);
546ast_node!(TwigFor, SyntaxKind::TWIG_FOR);
547ast_node!(TwigForBlock, SyntaxKind::TWIG_FOR_BLOCK);
548ast_node!(TwigForElseBlock, SyntaxKind::TWIG_FOR_ELSE_BLOCK);
549ast_node!(TwigEndForBlock, SyntaxKind::TWIG_ENDFOR_BLOCK);
550ast_node!(TwigInclude, SyntaxKind::TWIG_INCLUDE);
551ast_node!(TwigIncludeWith, SyntaxKind::TWIG_INCLUDE_WITH);
552ast_node!(TwigUse, SyntaxKind::TWIG_USE);
553ast_node!(TwigOverride, SyntaxKind::TWIG_OVERRIDE);
554ast_node!(TwigApply, SyntaxKind::TWIG_APPLY);
555ast_node!(
556 TwigApplyStartingBlock,
557 SyntaxKind::TWIG_APPLY_STARTING_BLOCK
558);
559ast_node!(TwigApplyEndingBlock, SyntaxKind::TWIG_APPLY_ENDING_BLOCK);
560ast_node!(TwigAutoescape, SyntaxKind::TWIG_AUTOESCAPE);
561ast_node!(
562 TwigAutoescapeStartingBlock,
563 SyntaxKind::TWIG_AUTOESCAPE_STARTING_BLOCK
564);
565ast_node!(
566 TwigAutoescapeEndingBlock,
567 SyntaxKind::TWIG_AUTOESCAPE_ENDING_BLOCK
568);
569ast_node!(TwigDeprecated, SyntaxKind::TWIG_DEPRECATED);
570ast_node!(TwigDo, SyntaxKind::TWIG_DO);
571ast_node!(TwigEmbed, SyntaxKind::TWIG_EMBED);
572ast_node!(
573 TwigEmbedStartingBlock,
574 SyntaxKind::TWIG_EMBED_STARTING_BLOCK
575);
576ast_node!(TwigEmbedEndingBlock, SyntaxKind::TWIG_EMBED_ENDING_BLOCK);
577ast_node!(TwigFlush, SyntaxKind::TWIG_FLUSH);
578ast_node!(TwigFrom, SyntaxKind::TWIG_FROM);
579ast_node!(TwigImport, SyntaxKind::TWIG_IMPORT);
580ast_node!(TwigSandbox, SyntaxKind::TWIG_SANDBOX);
581ast_node!(
582 TwigSandboxStartingBlock,
583 SyntaxKind::TWIG_SANDBOX_STARTING_BLOCK
584);
585ast_node!(
586 TwigSandboxEndingBlock,
587 SyntaxKind::TWIG_SANDBOX_ENDING_BLOCK
588);
589ast_node!(TwigVerbatim, SyntaxKind::TWIG_VERBATIM);
590ast_node!(
591 TwigVerbatimStartingBlock,
592 SyntaxKind::TWIG_VERBATIM_STARTING_BLOCK
593);
594ast_node!(
595 TwigVerbatimEndingBlock,
596 SyntaxKind::TWIG_VERBATIM_ENDING_BLOCK
597);
598ast_node!(TwigMacro, SyntaxKind::TWIG_MACRO);
599ast_node!(
600 TwigMacroStartingBlock,
601 SyntaxKind::TWIG_MACRO_STARTING_BLOCK
602);
603ast_node!(TwigMacroEndingBlock, SyntaxKind::TWIG_MACRO_ENDING_BLOCK);
604ast_node!(TwigWith, SyntaxKind::TWIG_WITH);
605ast_node!(TwigWithStartingBlock, SyntaxKind::TWIG_WITH_STARTING_BLOCK);
606ast_node!(TwigWithEndingBlock, SyntaxKind::TWIG_WITH_ENDING_BLOCK);
607ast_node!(TwigCache, SyntaxKind::TWIG_CACHE);
608ast_node!(TwigCacheTTL, SyntaxKind::TWIG_CACHE_TTL);
609ast_node!(TwigCacheTags, SyntaxKind::TWIG_CACHE_TAGS);
610ast_node!(
611 TwigCacheStartingBlock,
612 SyntaxKind::TWIG_CACHE_STARTING_BLOCK
613);
614ast_node!(TwigCacheEndingBlock, SyntaxKind::TWIG_CACHE_ENDING_BLOCK);
615ast_node!(TwigProps, SyntaxKind::TWIG_PROPS);
616ast_node!(TwigPropDeclaration, SyntaxKind::TWIG_PROP_DECLARATION);
617ast_node!(TwigComponent, SyntaxKind::TWIG_COMPONENT);
618ast_node!(
619 TwigComponentStartingBlock,
620 SyntaxKind::TWIG_COMPONENT_STARTING_BLOCK
621);
622ast_node!(
623 TwigComponentEndingBlock,
624 SyntaxKind::TWIG_COMPONENT_ENDING_BLOCK
625);
626ast_node!(ShopwareTwigExtends, SyntaxKind::SHOPWARE_TWIG_SW_EXTENDS);
627ast_node!(ShopwareTwigInclude, SyntaxKind::SHOPWARE_TWIG_SW_INCLUDE);
628ast_node!(
629 ShopwareSilentFeatureCall,
630 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL
631);
632ast_node!(
633 ShopwareSilentFeatureCallStartingBlock,
634 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL_STARTING_BLOCK
635);
636ast_node!(
637 ShopwareSilentFeatureCallEndingBlock,
638 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL_ENDING_BLOCK
639);
640ast_node!(ShopwareReturn, SyntaxKind::SHOPWARE_RETURN);
641ast_node!(ShopwareIcon, SyntaxKind::SHOPWARE_ICON);
642ast_node!(ShopwareIconStyle, SyntaxKind::SHOPWARE_ICON_STYLE);
643ast_node!(ShopwareThumbnails, SyntaxKind::SHOPWARE_THUMBNAILS);
644ast_node!(ShopwareThumbnailsWith, SyntaxKind::SHOPWARE_THUMBNAILS_WITH);
645ast_node!(HtmlDoctype, SyntaxKind::HTML_DOCTYPE);
646ast_node!(HtmlAttributeList, SyntaxKind::HTML_ATTRIBUTE_LIST);
647ast_node!(HtmlStringInner, SyntaxKind::HTML_STRING_INNER);
648ast_node!(HtmlText, SyntaxKind::HTML_TEXT);
649ast_node!(HtmlRawText, SyntaxKind::HTML_RAW_TEXT);
650ast_node!(HtmlComment, SyntaxKind::HTML_COMMENT);
651ast_node!(Error, SyntaxKind::ERROR);
652ast_node!(Root, SyntaxKind::ROOT);
653ast_node!(TwigTrans, SyntaxKind::TWIG_TRANS);
654ast_node!(
655 TwigTransStartingBlock,
656 SyntaxKind::TWIG_TRANS_STARTING_BLOCK
657);
658ast_node!(TwigTransEndingBlock, SyntaxKind::TWIG_TRANS_ENDING_BLOCK);
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663 use crate::parse;
664 use expect_test::expect;
665
666 fn parse_and_extract<T: AstNode<Language = TemplateLanguage>>(input: &str) -> T {
667 let (tree, errors) = parse(input).split();
668 assert_eq!(errors, vec![]);
669 support::child(&tree).unwrap()
670 }
671
672 #[test]
673 fn simple_html_tag() {
674 let raw = r#"<div class="hello">world {{ 42 }}</div>"#;
675 let html_tag: HtmlTag = parse_and_extract(raw);
676
677 assert_eq!(format!("{html_tag}"), raw.to_string());
678 expect![[r#"
679 HTML_TAG@0..39
680 HTML_STARTING_TAG@0..19
681 TK_LESS_THAN@0..1 "<"
682 TK_WORD@1..4 "div"
683 HTML_ATTRIBUTE_LIST@4..18
684 HTML_ATTRIBUTE@4..18
685 TK_WHITESPACE@4..5 " "
686 TK_WORD@5..10 "class"
687 TK_EQUAL@10..11 "="
688 HTML_STRING@11..18
689 TK_DOUBLE_QUOTES@11..12 "\""
690 HTML_STRING_INNER@12..17
691 TK_WORD@12..17 "hello"
692 TK_DOUBLE_QUOTES@17..18 "\""
693 TK_GREATER_THAN@18..19 ">"
694 BODY@19..33
695 HTML_TEXT@19..24
696 TK_WORD@19..24 "world"
697 TWIG_VAR@24..33
698 TK_WHITESPACE@24..25 " "
699 TK_OPEN_CURLY_CURLY@25..27 "{{"
700 TWIG_EXPRESSION@27..30
701 TWIG_LITERAL_NUMBER@27..30
702 TK_WHITESPACE@27..28 " "
703 TK_NUMBER@28..30 "42"
704 TK_WHITESPACE@30..31 " "
705 TK_CLOSE_CURLY_CURLY@31..33 "}}"
706 HTML_ENDING_TAG@33..39
707 TK_LESS_THAN_SLASH@33..35 "</"
708 TK_WORD@35..38 "div"
709 TK_GREATER_THAN@38..39 ">""#]]
710 .assert_eq(&format!("{html_tag:?}"));
711
712 assert!(!html_tag.is_self_closing());
713 assert_eq!(
714 html_tag.name().map(|t| t.to_string()),
715 Some("div".to_string())
716 );
717 assert_eq!(
718 html_tag.starting_tag().map(|t| t.to_string()),
719 Some(r#"<div class="hello">"#.to_string())
720 );
721 assert_eq!(
722 html_tag.body().map(|t| t.to_string()),
723 Some("world {{ 42 }}".to_string())
724 );
725 assert_eq!(
726 html_tag.ending_tag().map(|t| t.to_string()),
727 Some("</div>".to_string())
728 );
729 assert_eq!(html_tag.attributes().count(), 1);
730 assert_eq!(
731 html_tag
732 .attributes()
733 .next()
734 .and_then(|t| t.name())
735 .map(|t| t.to_string()),
736 Some("class".to_string())
737 );
738 assert_eq!(
739 html_tag
740 .attributes()
741 .next()
742 .and_then(|t| t.value())
743 .and_then(|t| t.get_inner())
744 .map(|t| t.to_string()),
745 Some("hello".to_string())
746 );
747 }
748}