1pub use rowan::ast::support;
11pub use rowan::ast::AstChildren;
12pub use rowan::ast::AstNode;
13use rowan::NodeOrToken;
14use std::fmt::{Debug, Display, Formatter};
15
16use crate::T;
17
18use super::untyped::{
19 debug_tree, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TemplateLanguage,
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]
167 pub fn starting_tag(&self) -> Option<HtmlStartingTag> {
168 support::child(&self.syntax)
169 }
170
171 #[must_use]
172 pub fn body(&self) -> Option<Body> {
173 support::child(&self.syntax)
174 }
175
176 #[must_use]
177 pub fn ending_tag(&self) -> Option<HtmlEndingTag> {
178 support::child(&self.syntax)
179 }
180}
181
182ast_node!(HtmlStartingTag, SyntaxKind::HTML_STARTING_TAG);
183impl HtmlStartingTag {
184 #[must_use]
186 pub fn name(&self) -> Option<SyntaxToken> {
187 support::token(&self.syntax, T![word])
188 }
189
190 #[must_use]
192 pub fn attributes(&self) -> AstChildren<HtmlAttribute> {
193 match support::child::<HtmlAttributeList>(&self.syntax) {
194 Some(list) => support::children(&list.syntax),
195 None => support::children(&self.syntax),
197 }
198 }
199
200 #[must_use]
202 pub fn html_tag(&self) -> Option<HtmlTag> {
203 match self.syntax.parent() {
204 Some(p) => HtmlTag::cast(p),
205 None => None,
206 }
207 }
208}
209
210ast_node!(HtmlAttribute, SyntaxKind::HTML_ATTRIBUTE);
211impl HtmlAttribute {
212 #[must_use]
214 pub fn name(&self) -> Option<SyntaxToken> {
215 support::token(&self.syntax, T![word])
216 }
217
218 #[must_use]
220 pub fn value(&self) -> Option<HtmlString> {
221 support::child(&self.syntax)
222 }
223
224 #[must_use]
226 pub fn html_tag(&self) -> Option<HtmlStartingTag> {
227 match self.syntax.parent() {
228 Some(p) => HtmlStartingTag::cast(p),
229 None => None,
230 }
231 }
232}
233
234ast_node!(HtmlEndingTag, SyntaxKind::HTML_ENDING_TAG);
235impl HtmlEndingTag {
236 #[must_use]
238 pub fn name(&self) -> Option<SyntaxToken> {
239 support::token(&self.syntax, T![word])
240 }
241
242 #[must_use]
244 pub fn html_tag(&self) -> Option<HtmlTag> {
245 match self.syntax.parent() {
246 Some(p) => HtmlTag::cast(p),
247 None => None,
248 }
249 }
250}
251
252ast_node!(TwigBinaryExpression, SyntaxKind::TWIG_BINARY_EXPRESSION);
253impl TwigBinaryExpression {
254 #[must_use]
255 pub fn operator(&self) -> Option<SyntaxToken> {
256 self.syntax
257 .children_with_tokens()
258 .find_map(|element| match element {
259 SyntaxElement::Token(t) if !t.kind().is_trivia() => Some(t),
260 _ => None,
261 })
262 }
263
264 #[must_use]
265 pub fn lhs_expression(&self) -> Option<TwigExpression> {
266 self.syntax.children().find_map(TwigExpression::cast)
267 }
268
269 #[must_use]
270 pub fn rhs_expression(&self) -> Option<TwigExpression> {
271 self.syntax
272 .children()
273 .filter_map(TwigExpression::cast)
274 .nth(1)
275 }
276}
277
278ast_node!(
279 LudtwigDirectiveRuleList,
280 SyntaxKind::LUDTWIG_DIRECTIVE_RULE_LIST
281);
282impl LudtwigDirectiveRuleList {
283 #[must_use]
284 pub fn get_rule_names(&self) -> Vec<String> {
285 self.syntax
286 .children_with_tokens()
287 .filter_map(|element| match element {
288 NodeOrToken::Token(t) if t.kind() == SyntaxKind::TK_WORD => {
289 Some(t.text().to_string())
290 }
291 _ => None,
292 })
293 .collect()
294 }
295}
296
297ast_node!(
298 LudtwigDirectiveFileIgnore,
299 SyntaxKind::LUDTWIG_DIRECTIVE_FILE_IGNORE
300);
301impl LudtwigDirectiveFileIgnore {
302 #[must_use]
303 pub fn get_rules(&self) -> Vec<String> {
304 match support::child::<LudtwigDirectiveRuleList>(&self.syntax) {
305 Some(rule_list) => rule_list.get_rule_names(),
306 None => vec![],
307 }
308 }
309}
310
311ast_node!(LudtwigDirectiveIgnore, SyntaxKind::LUDTWIG_DIRECTIVE_IGNORE);
312impl LudtwigDirectiveIgnore {
313 #[must_use]
314 pub fn get_rules(&self) -> Vec<String> {
315 match support::child::<LudtwigDirectiveRuleList>(&self.syntax) {
316 Some(rule_list) => rule_list.get_rule_names(),
317 None => vec![],
318 }
319 }
320}
321
322ast_node!(TwigLiteralString, SyntaxKind::TWIG_LITERAL_STRING);
323impl TwigLiteralString {
324 #[must_use]
325 pub fn get_inner(&self) -> Option<TwigLiteralStringInner> {
326 support::child(&self.syntax)
327 }
328
329 #[must_use]
330 pub fn get_opening_quote(&self) -> Option<SyntaxToken> {
331 self.syntax
332 .children_with_tokens()
333 .take_while(|element| {
334 if element.as_node().is_some() {
335 return false; }
337
338 true
339 })
340 .find_map(|element| match element {
341 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
343 _ => None,
344 })
345 }
346
347 #[must_use]
348 pub fn get_closing_quote(&self) -> Option<SyntaxToken> {
349 self.syntax
350 .children_with_tokens()
351 .skip_while(|element| {
352 if element.as_node().is_some() {
353 return false; }
355
356 true
357 })
358 .find_map(|element| match element {
359 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
361 _ => None,
362 })
363 }
364}
365
366ast_node!(
367 TwigLiteralStringInner,
368 SyntaxKind::TWIG_LITERAL_STRING_INNER
369);
370impl TwigLiteralStringInner {
371 #[must_use]
372 pub fn get_interpolations(&self) -> AstChildren<TwigLiteralStringInterpolation> {
373 support::children(&self.syntax)
374 }
375}
376
377ast_node!(HtmlString, SyntaxKind::HTML_STRING);
378impl HtmlString {
379 #[must_use]
380 pub fn get_inner(&self) -> Option<HtmlStringInner> {
381 support::child(&self.syntax)
382 }
383
384 #[must_use]
385 pub fn get_opening_quote(&self) -> Option<SyntaxToken> {
386 self.syntax
387 .children_with_tokens()
388 .take_while(|element| {
389 if element.as_node().is_some() {
390 return false; }
392
393 true
394 })
395 .find_map(|element| match element {
396 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
398 _ => None,
399 })
400 }
401
402 #[must_use]
403 pub fn get_closing_quote(&self) -> Option<SyntaxToken> {
404 self.syntax
405 .children_with_tokens()
406 .skip_while(|element| {
407 if element.as_node().is_some() {
408 return false; }
410
411 true
412 })
413 .find_map(|element| match element {
414 NodeOrToken::Token(t) if !t.kind().is_trivia() => Some(t),
416 _ => None,
417 })
418 }
419}
420
421ast_node!(TwigExtends, SyntaxKind::TWIG_EXTENDS);
422impl TwigExtends {
423 #[must_use]
424 pub fn get_extends_keyword(&self) -> Option<SyntaxToken> {
425 support::token(&self.syntax, T!["extends"])
426 }
427}
428
429ast_node!(TwigVar, SyntaxKind::TWIG_VAR);
430impl TwigVar {
431 #[must_use]
432 pub fn get_expression(&self) -> Option<TwigExpression> {
433 support::child(&self.syntax)
434 }
435}
436
437ast_node!(TwigLiteralName, SyntaxKind::TWIG_LITERAL_NAME);
438impl TwigLiteralName {
439 #[must_use]
440 pub fn get_name(&self) -> Option<SyntaxToken> {
441 support::token(&self.syntax, SyntaxKind::TK_WORD)
442 }
443}
444
445ast_node!(Body, SyntaxKind::BODY);
446ast_node!(TwigExpression, SyntaxKind::TWIG_EXPRESSION);
447ast_node!(TwigUnaryExpression, SyntaxKind::TWIG_UNARY_EXPRESSION);
448ast_node!(
449 TwigParenthesesExpression,
450 SyntaxKind::TWIG_PARENTHESES_EXPRESSION
451);
452ast_node!(
453 TwigConditionalExpression,
454 SyntaxKind::TWIG_CONDITIONAL_EXPRESSION
455);
456ast_node!(TwigOperand, SyntaxKind::TWIG_OPERAND);
457ast_node!(TwigAccessor, SyntaxKind::TWIG_ACCESSOR);
458ast_node!(TwigFilter, SyntaxKind::TWIG_FILTER);
459ast_node!(TwigIndexLookup, SyntaxKind::TWIG_INDEX_LOOKUP);
460ast_node!(TwigIndex, SyntaxKind::TWIG_INDEX);
461ast_node!(TwigIndexRange, SyntaxKind::TWIG_INDEX_RANGE);
462ast_node!(TwigFunctionCall, SyntaxKind::TWIG_FUNCTION_CALL);
463ast_node!(TwigArrowFunction, SyntaxKind::TWIG_ARROW_FUNCTION);
464ast_node!(TwigArguments, SyntaxKind::TWIG_ARGUMENTS);
465ast_node!(TwigNamedArgument, SyntaxKind::TWIG_NAMED_ARGUMENT);
466ast_node!(
467 TwigLiteralStringInterpolation,
468 SyntaxKind::TWIG_LITERAL_STRING_INTERPOLATION
469);
470ast_node!(TwigLiteralNumber, SyntaxKind::TWIG_LITERAL_NUMBER);
471ast_node!(TwigLiteralArray, SyntaxKind::TWIG_LITERAL_ARRAY);
472ast_node!(TwigLiteralArrayInner, SyntaxKind::TWIG_LITERAL_ARRAY_INNER);
473ast_node!(TwigLiteralNull, SyntaxKind::TWIG_LITERAL_NULL);
474ast_node!(TwigLiteralBoolean, SyntaxKind::TWIG_LITERAL_BOOLEAN);
475ast_node!(TwigLiteralHash, SyntaxKind::TWIG_LITERAL_HASH);
476ast_node!(TwigLiteralHashItems, SyntaxKind::TWIG_LITERAL_HASH_ITEMS);
477ast_node!(TwigLiteralHashPair, SyntaxKind::TWIG_LITERAL_HASH_PAIR);
478ast_node!(TwigLiteralHashKey, SyntaxKind::TWIG_LITERAL_HASH_KEY);
479ast_node!(TwigLiteralHashValue, SyntaxKind::TWIG_LITERAL_HASH_VALUE);
480ast_node!(TwigComment, SyntaxKind::TWIG_COMMENT);
481ast_node!(TwigIf, SyntaxKind::TWIG_IF);
482ast_node!(TwigIfBlock, SyntaxKind::TWIG_IF_BLOCK);
483ast_node!(TwigElseIfBlock, SyntaxKind::TWIG_ELSE_IF_BLOCK);
484ast_node!(TwigElseBlock, SyntaxKind::TWIG_ELSE_BLOCK);
485ast_node!(TwigEndIfBlock, SyntaxKind::TWIG_ENDIF_BLOCK);
486ast_node!(TwigSet, SyntaxKind::TWIG_SET);
487ast_node!(TwigSetBlock, SyntaxKind::TWIG_SET_BLOCK);
488ast_node!(TwigEndSetBlock, SyntaxKind::TWIG_ENDSET_BLOCK);
489ast_node!(TwigAssignment, SyntaxKind::TWIG_ASSIGNMENT);
490ast_node!(TwigFor, SyntaxKind::TWIG_FOR);
491ast_node!(TwigForBlock, SyntaxKind::TWIG_FOR_BLOCK);
492ast_node!(TwigForElseBlock, SyntaxKind::TWIG_FOR_ELSE_BLOCK);
493ast_node!(TwigEndForBlock, SyntaxKind::TWIG_ENDFOR_BLOCK);
494ast_node!(TwigInclude, SyntaxKind::TWIG_INCLUDE);
495ast_node!(TwigIncludeWith, SyntaxKind::TWIG_INCLUDE_WITH);
496ast_node!(TwigUse, SyntaxKind::TWIG_USE);
497ast_node!(TwigOverride, SyntaxKind::TWIG_OVERRIDE);
498ast_node!(TwigApply, SyntaxKind::TWIG_APPLY);
499ast_node!(
500 TwigApplyStartingBlock,
501 SyntaxKind::TWIG_APPLY_STARTING_BLOCK
502);
503ast_node!(TwigApplyEndingBlock, SyntaxKind::TWIG_APPLY_ENDING_BLOCK);
504ast_node!(TwigAutoescape, SyntaxKind::TWIG_AUTOESCAPE);
505ast_node!(
506 TwigAutoescapeStartingBlock,
507 SyntaxKind::TWIG_AUTOESCAPE_STARTING_BLOCK
508);
509ast_node!(
510 TwigAutoescapeEndingBlock,
511 SyntaxKind::TWIG_AUTOESCAPE_ENDING_BLOCK
512);
513ast_node!(TwigDeprecated, SyntaxKind::TWIG_DEPRECATED);
514ast_node!(TwigDo, SyntaxKind::TWIG_DO);
515ast_node!(TwigEmbed, SyntaxKind::TWIG_EMBED);
516ast_node!(
517 TwigEmbedStartingBlock,
518 SyntaxKind::TWIG_EMBED_STARTING_BLOCK
519);
520ast_node!(TwigEmbedEndingBlock, SyntaxKind::TWIG_EMBED_ENDING_BLOCK);
521ast_node!(TwigFlush, SyntaxKind::TWIG_FLUSH);
522ast_node!(TwigFrom, SyntaxKind::TWIG_FROM);
523ast_node!(TwigImport, SyntaxKind::TWIG_IMPORT);
524ast_node!(TwigSandbox, SyntaxKind::TWIG_SANDBOX);
525ast_node!(
526 TwigSandboxStartingBlock,
527 SyntaxKind::TWIG_SANDBOX_STARTING_BLOCK
528);
529ast_node!(
530 TwigSandboxEndingBlock,
531 SyntaxKind::TWIG_SANDBOX_ENDING_BLOCK
532);
533ast_node!(TwigVerbatim, SyntaxKind::TWIG_VERBATIM);
534ast_node!(
535 TwigVerbatimStartingBlock,
536 SyntaxKind::TWIG_VERBATIM_STARTING_BLOCK
537);
538ast_node!(
539 TwigVerbatimEndingBlock,
540 SyntaxKind::TWIG_VERBATIM_ENDING_BLOCK
541);
542ast_node!(TwigMacro, SyntaxKind::TWIG_MACRO);
543ast_node!(
544 TwigMacroStartingBlock,
545 SyntaxKind::TWIG_MACRO_STARTING_BLOCK
546);
547ast_node!(TwigMacroEndingBlock, SyntaxKind::TWIG_MACRO_ENDING_BLOCK);
548ast_node!(TwigWith, SyntaxKind::TWIG_WITH);
549ast_node!(TwigWithStartingBlock, SyntaxKind::TWIG_WITH_STARTING_BLOCK);
550ast_node!(TwigWithEndingBlock, SyntaxKind::TWIG_WITH_ENDING_BLOCK);
551ast_node!(TwigCache, SyntaxKind::TWIG_CACHE);
552ast_node!(TwigCacheTTL, SyntaxKind::TWIG_CACHE_TTL);
553ast_node!(TwigCacheTags, SyntaxKind::TWIG_CACHE_TAGS);
554ast_node!(
555 TwigCacheStartingBlock,
556 SyntaxKind::TWIG_CACHE_STARTING_BLOCK
557);
558ast_node!(TwigCacheEndingBlock, SyntaxKind::TWIG_CACHE_ENDING_BLOCK);
559ast_node!(TwigProps, SyntaxKind::TWIG_PROPS);
560ast_node!(TwigPropDeclaration, SyntaxKind::TWIG_PROP_DECLARATION);
561ast_node!(TwigComponent, SyntaxKind::TWIG_COMPONENT);
562ast_node!(
563 TwigComponentStartingBlock,
564 SyntaxKind::TWIG_COMPONENT_STARTING_BLOCK
565);
566ast_node!(
567 TwigComponentEndingBlock,
568 SyntaxKind::TWIG_COMPONENT_ENDING_BLOCK
569);
570ast_node!(ShopwareTwigExtends, SyntaxKind::SHOPWARE_TWIG_SW_EXTENDS);
571ast_node!(ShopwareTwigInclude, SyntaxKind::SHOPWARE_TWIG_SW_INCLUDE);
572ast_node!(
573 ShopwareSilentFeatureCall,
574 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL
575);
576ast_node!(
577 ShopwareSilentFeatureCallStartingBlock,
578 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL_STARTING_BLOCK
579);
580ast_node!(
581 ShopwareSilentFeatureCallEndingBlock,
582 SyntaxKind::SHOPWARE_SILENT_FEATURE_CALL_ENDING_BLOCK
583);
584ast_node!(ShopwareReturn, SyntaxKind::SHOPWARE_RETURN);
585ast_node!(ShopwareIcon, SyntaxKind::SHOPWARE_ICON);
586ast_node!(ShopwareIconStyle, SyntaxKind::SHOPWARE_ICON_STYLE);
587ast_node!(ShopwareThumbnails, SyntaxKind::SHOPWARE_THUMBNAILS);
588ast_node!(ShopwareThumbnailsWith, SyntaxKind::SHOPWARE_THUMBNAILS_WITH);
589ast_node!(HtmlDoctype, SyntaxKind::HTML_DOCTYPE);
590ast_node!(HtmlAttributeList, SyntaxKind::HTML_ATTRIBUTE_LIST);
591ast_node!(HtmlStringInner, SyntaxKind::HTML_STRING_INNER);
592ast_node!(HtmlText, SyntaxKind::HTML_TEXT);
593ast_node!(HtmlRawText, SyntaxKind::HTML_RAW_TEXT);
594ast_node!(HtmlComment, SyntaxKind::HTML_COMMENT);
595ast_node!(Error, SyntaxKind::ERROR);
596ast_node!(Root, SyntaxKind::ROOT);
597ast_node!(TwigTrans, SyntaxKind::TWIG_TRANS);
598ast_node!(
599 TwigTransStartingBlock,
600 SyntaxKind::TWIG_TRANS_STARTING_BLOCK
601);
602ast_node!(TwigTransEndingBlock, SyntaxKind::TWIG_TRANS_ENDING_BLOCK);
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607 use crate::parse;
608 use expect_test::expect;
609
610 fn parse_and_extract<T: AstNode<Language = TemplateLanguage>>(input: &str) -> T {
611 let (tree, errors) = parse(input).split();
612 assert_eq!(errors, vec![]);
613 support::child(&tree).unwrap()
614 }
615
616 #[test]
617 fn simple_html_tag() {
618 let raw = r#"<div class="hello">world {{ 42 }}</div>"#;
619 let html_tag: HtmlTag = parse_and_extract(raw);
620
621 assert_eq!(format!("{html_tag}"), raw.to_string());
622 expect![[r#"
623 HTML_TAG@0..39
624 HTML_STARTING_TAG@0..19
625 TK_LESS_THAN@0..1 "<"
626 TK_WORD@1..4 "div"
627 HTML_ATTRIBUTE_LIST@4..18
628 HTML_ATTRIBUTE@4..18
629 TK_WHITESPACE@4..5 " "
630 TK_WORD@5..10 "class"
631 TK_EQUAL@10..11 "="
632 HTML_STRING@11..18
633 TK_DOUBLE_QUOTES@11..12 "\""
634 HTML_STRING_INNER@12..17
635 TK_WORD@12..17 "hello"
636 TK_DOUBLE_QUOTES@17..18 "\""
637 TK_GREATER_THAN@18..19 ">"
638 BODY@19..33
639 HTML_TEXT@19..24
640 TK_WORD@19..24 "world"
641 TWIG_VAR@24..33
642 TK_WHITESPACE@24..25 " "
643 TK_OPEN_CURLY_CURLY@25..27 "{{"
644 TWIG_EXPRESSION@27..30
645 TWIG_LITERAL_NUMBER@27..30
646 TK_WHITESPACE@27..28 " "
647 TK_NUMBER@28..30 "42"
648 TK_WHITESPACE@30..31 " "
649 TK_CLOSE_CURLY_CURLY@31..33 "}}"
650 HTML_ENDING_TAG@33..39
651 TK_LESS_THAN_SLASH@33..35 "</"
652 TK_WORD@35..38 "div"
653 TK_GREATER_THAN@38..39 ">""#]]
654 .assert_eq(&format!("{html_tag:?}"));
655
656 assert!(!html_tag.is_self_closing());
657 assert_eq!(
658 html_tag.name().map(|t| t.to_string()),
659 Some("div".to_string())
660 );
661 assert_eq!(
662 html_tag.starting_tag().map(|t| t.to_string()),
663 Some(r#"<div class="hello">"#.to_string())
664 );
665 assert_eq!(
666 html_tag.body().map(|t| t.to_string()),
667 Some("world {{ 42 }}".to_string())
668 );
669 assert_eq!(
670 html_tag.ending_tag().map(|t| t.to_string()),
671 Some("</div>".to_string())
672 );
673 assert_eq!(html_tag.attributes().count(), 1);
674 assert_eq!(
675 html_tag
676 .attributes()
677 .next()
678 .and_then(|t| t.name())
679 .map(|t| t.to_string()),
680 Some("class".to_string())
681 );
682 assert_eq!(
683 html_tag
684 .attributes()
685 .next()
686 .and_then(|t| t.value())
687 .and_then(|t| t.get_inner())
688 .map(|t| t.to_string()),
689 Some("hello".to_string())
690 );
691 }
692}