1use quote::{ToTokens, TokenStreamExt};
12use rstml::{
13 node::{CustomNode, Node as RNode},
14 recoverable::{ParseRecoverable, RecoverableContext},
15 visitor::Visitor,
16};
17use syn::{
18 braced,
19 parse::{Parse, ParseStream},
20 token::Brace,
21 Expr, Pat, Token,
22};
23
24use crate::{Either, TryIntoOrCloneRef};
25
26#[cfg(not(feature = "extendable"))]
27type CustomNodeType = EscapeCode;
28
29#[cfg(feature = "extendable")]
30type CustomNodeType = crate::ExtendableCustomNode;
31
32type Node = RNode<CustomNodeType>;
33
34#[derive(Clone, Debug, syn_derive::ToTokens)]
35pub struct Block {
36 #[syn(braced)]
37 pub brace_token: Brace,
38 #[syn(in = brace_token)]
39 #[to_tokens(|tokens, val| tokens.append_all(val))]
40 pub body: Vec<Node>,
41}
42
43impl ParseRecoverable for Block {
44 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
45 let inner_parser = |parser: &mut RecoverableContext, input: ParseStream| {
48 let content;
49 let brace_token = braced!(content in input);
50 let mut body = vec![];
51 while !content.is_empty() {
52 let Some(val) = parser.parse_recoverable(&content) else {
53 return Ok(None);
54 };
55 body.push(val);
56 }
57 Ok(Some(Block { brace_token, body }))
58 };
59 parser.parse_mixed_fn(input, inner_parser)?
60 }
61}
62
63#[derive(Clone, Debug, syn_derive::ToTokens)]
64pub struct ElseIf {
65 pub else_token: Token![else],
66 pub if_token: Token![if],
67 pub condition: Expr,
68 pub then_branch: Block,
69}
70
71impl ParseRecoverable for ElseIf {
72 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
73 Some(ElseIf {
74 else_token: parser.parse_simple(input)?,
75 if_token: parser.parse_simple(input)?,
76 condition: parser.parse_mixed_fn(input, |_, input| {
77 input.call(Expr::parse_without_eager_brace)
78 })?,
79 then_branch: parser.parse_recoverable(input)?,
80 })
81 }
82}
83
84#[derive(Clone, Debug, syn_derive::ToTokens)]
85pub struct Else {
86 pub else_token: Token![else],
87 pub then_branch: Block,
88}
89impl ParseRecoverable for Else {
90 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
91 Some(Else {
92 else_token: parser.parse_simple(input)?,
93 then_branch: parser.parse_recoverable(input)?,
94 })
95 }
96}
97
98#[derive(Clone, Debug, syn_derive::ToTokens)]
109pub struct IfExpr {
110 pub keyword: Token![if],
111 pub condition: Expr,
112 pub then_branch: Block,
113 #[to_tokens(TokenStreamExt::append_all)]
114 pub else_ifs: Vec<ElseIf>,
115 pub else_branch: Option<Else>,
116}
117
118impl ParseRecoverable for IfExpr {
119 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
120 let keyword = parser.parse_simple(input)?;
121
122 let condition = parser.parse_mixed_fn(input, |_, input| {
123 input.call(Expr::parse_without_eager_brace)
124 })?;
125
126 let then_branch = parser.parse_recoverable(input)?;
127 let mut else_ifs = vec![];
128
129 while input.peek(Token![else]) && input.peek2(Token![if]) {
130 else_ifs.push(parser.parse_recoverable(input)?)
131 }
132 let mut else_branch = None;
133 if input.peek(Token![else]) {
134 else_branch = Some(parser.parse_recoverable(input)?)
135 }
136 Some(IfExpr {
137 keyword,
138 condition,
139 then_branch,
140 else_ifs,
141 else_branch,
142 })
143 }
144}
145
146#[derive(Clone, Debug, syn_derive::ToTokens)]
147pub struct ForExpr {
148 pub keyword: Token![for],
149 pub pat: Pat,
150 pub token_in: Token![in],
151 pub expr: Expr,
152 pub block: Block,
153}
154
155impl ParseRecoverable for ForExpr {
156 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
157 let keyword = parser.parse_simple(input)?;
158 let pat = parser.parse_mixed_fn(input, |_parse, input| {
159 Pat::parse_multi_with_leading_vert(input)
160 })?;
161 let token_in = parser.parse_simple(input)?;
162 let expr = parser.parse_mixed_fn(input, |_, input| {
163 input.call(Expr::parse_without_eager_brace)
164 })?;
165 let block = parser.parse_recoverable(input)?;
166 Some(ForExpr {
167 keyword,
168 pat,
169 token_in,
170 expr,
171 block,
172 })
173 }
174}
175
176#[derive(Clone, Debug, syn_derive::ToTokens)]
177pub struct Arm {
178 pub pat: Pat,
179 pub fat_arrow_token: Token![=>],
181 pub body: Block,
182 pub comma: Option<Token![,]>,
183}
184
185impl ParseRecoverable for Arm {
186 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
187 Some(Arm {
188 pat: parser
189 .parse_mixed_fn(input, |_, input| Pat::parse_multi_with_leading_vert(input))?,
190 fat_arrow_token: parser.parse_simple(input)?,
191 body: parser.parse_recoverable(input)?,
192 comma: parser.parse_simple(input)?,
193 })
194 }
195}
196
197#[derive(Clone, Debug, syn_derive::ToTokens)]
204pub struct MatchExpr {
205 pub keyword: Token![match],
206 pub expr: Expr,
207 #[syn(braced)]
208 pub brace_token: Brace,
209 #[syn(in = brace_token)]
210 #[to_tokens(TokenStreamExt::append_all)]
211 pub arms: Vec<Arm>,
212}
213
214impl ParseRecoverable for MatchExpr {
215 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
216 let inner_parser = |parser: &mut RecoverableContext, input: ParseStream| {
219 let Some(keyword) = parser.parse_simple(input) else {
220 return Ok(None);
221 };
222 let content;
223 let expr = Expr::parse_without_eager_brace(input)?;
224 let brace_token = braced!(content in input);
225 let mut arms = Vec::new();
226 while !content.is_empty() {
227 let Some(val) = parser.parse_recoverable(&content) else {
228 return Ok(None);
229 };
230 arms.push(val);
231 }
232 Ok(Some(MatchExpr {
233 keyword,
234 expr,
235 brace_token,
236 arms,
237 }))
238 };
239 parser.parse_mixed_fn(input, inner_parser)?
240 }
241}
242
243#[derive(Clone, Debug, syn_derive::ToTokens)]
247pub enum EscapedExpr {
248 If(IfExpr),
249 For(ForExpr),
250 Match(MatchExpr),
251}
252
253impl ParseRecoverable for EscapedExpr {
254 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
255 let res = if input.peek(Token![if]) {
256 EscapedExpr::If(parser.parse_recoverable(input)?)
257 } else if input.peek(Token![for]) {
258 EscapedExpr::For(parser.parse_recoverable(input)?)
259 } else if input.peek(Token![match]) {
260 EscapedExpr::Match(parser.parse_recoverable(input)?)
261 } else {
262 return None;
263 };
264
265 Some(res)
266 }
267}
268#[cfg(feature = "extendable")]
269impl TryIntoOrCloneRef<EscapeCode> for crate::ExtendableCustomNode {
270 fn try_into_or_clone_ref(self) -> Either<EscapeCode, Self> {
271 if let Some(val) = self.try_downcast_ref::<EscapeCode>() {
272 Either::A(val.clone())
273 } else {
274 Either::B(self.clone())
275 }
276 }
277 fn new_from_value(value: EscapeCode) -> Self {
278 Self::from_value(value)
279 }
280}
281
282#[derive(Clone, Debug, syn_derive::ToTokens)]
283pub struct EscapeCode<T: ToTokens = Token![@]> {
284 pub escape_token: T,
285 pub expression: EscapedExpr,
286}
287
288impl<T: ToTokens + Parse> ParseRecoverable for EscapeCode<T> {
289 fn parse_recoverable(parser: &mut RecoverableContext, input: ParseStream) -> Option<Self> {
290 let escape_token = parser.parse_simple(input)?;
291 let expression = parser.parse_recoverable(input)?;
292
293 Some(Self {
294 escape_token,
295 expression,
296 })
297 }
298}
299
300impl<T> CustomNode for EscapeCode<T>
301where
302 T: Parse + ToTokens,
303{
304 fn peek_element(input: syn::parse::ParseStream) -> bool {
305 if input.parse::<T>().is_err() {
306 return false;
307 }
308 input.peek(Token![if]) || input.peek(Token![for]) || input.peek(Token![match])
309 }
310}
311
312pub mod visitor_impl {
313 use rstml::visitor::{CustomNodeWalker, RustCode};
314
315 use super::*;
316
317 pub struct EscapeCodeWalker;
318 impl CustomNodeWalker for EscapeCodeWalker {
319 type Custom = CustomNodeType;
320 fn walk_custom_node_fields<VisitorImpl>(
321 visitor: &mut VisitorImpl,
322 node: &mut Self::Custom,
323 ) -> bool
324 where
325 VisitorImpl: Visitor<CustomNodeType>,
326 {
327 EscapeCode::visit_custom_children(visitor, node)
328 }
329 }
330
331 impl Block {
332 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
333 visitor: &mut V,
334 block: &mut Self,
335 ) -> bool {
336 block.body.iter_mut().all(|val| visitor.visit_node(val))
337 }
338 }
339
340 impl ElseIf {
341 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
342 visitor: &mut V,
343 expr: &mut Self,
344 ) -> bool {
345 visitor.visit_rust_code(RustCode::Expr(&mut expr.condition));
346 Block::visit_custom_children(visitor, &mut expr.then_branch)
347 }
348 }
349
350 impl Else {
351 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
352 visitor: &mut V,
353 expr: &mut Self,
354 ) -> bool {
355 Block::visit_custom_children(visitor, &mut expr.then_branch)
356 }
357 }
358
359 impl IfExpr {
360 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
361 visitor: &mut V,
362 expr: &mut Self,
363 ) -> bool {
364 visitor.visit_rust_code(RustCode::Expr(&mut expr.condition));
365 Block::visit_custom_children(visitor, &mut expr.then_branch)
366 || expr
367 .else_ifs
368 .iter_mut()
369 .all(|val| ElseIf::visit_custom_children(visitor, val))
370 || expr
371 .else_branch
372 .as_mut()
373 .map(|val| Else::visit_custom_children(visitor, val))
374 .unwrap_or(true)
375 }
376 }
377 impl ForExpr {
378 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
379 visitor: &mut V,
380 expr: &mut Self,
381 ) -> bool {
382 visitor.visit_rust_code(RustCode::Pat(&mut expr.pat));
383 visitor.visit_rust_code(RustCode::Expr(&mut expr.expr));
384 Block::visit_custom_children(visitor, &mut expr.block)
385 }
386 }
387 impl Arm {
388 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
389 visitor: &mut V,
390 expr: &mut Self,
391 ) -> bool {
392 visitor.visit_rust_code(RustCode::Pat(&mut expr.pat));
393 Block::visit_custom_children(visitor, &mut expr.body)
394 }
395 }
396 impl MatchExpr {
397 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
398 visitor: &mut V,
399 expr: &mut Self,
400 ) -> bool {
401 visitor.visit_rust_code(RustCode::Expr(&mut expr.expr));
402
403 expr.arms
404 .iter_mut()
405 .all(|val| Arm::visit_custom_children(visitor, val))
406 }
407 }
408 impl EscapedExpr {
409 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
410 visitor: &mut V,
411 expr: &mut Self,
412 ) -> bool {
413 match expr {
414 EscapedExpr::If(expr) => IfExpr::visit_custom_children(visitor, expr),
415 EscapedExpr::For(expr) => ForExpr::visit_custom_children(visitor, expr),
416 EscapedExpr::Match(expr) => MatchExpr::visit_custom_children(visitor, expr),
417 }
418 }
419 }
420 impl EscapeCode {
421 pub fn visit_custom_children<V: Visitor<CustomNodeType>>(
422 visitor: &mut V,
423 node: &mut CustomNodeType,
424 ) -> bool {
425 let Either::A(mut this): Either<EscapeCode, _> = node.clone().try_into_or_clone_ref()
426 else {
427 return true;
428 };
429 let result = EscapedExpr::visit_custom_children(visitor, &mut this.expression);
430 *node = TryIntoOrCloneRef::new_from_value(this);
431 result
432 }
433 }
434}
435
436#[cfg(test)]
437#[cfg(not(feature = "extendable"))]
438mod test_typed {
439 use quote::quote;
440 use rstml::{node::Node, recoverable::Recoverable, Parser, ParserConfig};
441 use syn::{parse_quote, Token};
442
443 use super::{EscapeCode, EscapedExpr};
444
445 type MyCustomNode = EscapeCode<Token![@]>;
446 type MyNode = Node<MyCustomNode>;
447 #[test]
448 fn if_complex_expression() {
449 let actual: Recoverable<MyNode> = parse_quote! {
450 @if just && an || expression {
451 <div/>
452 }
453 };
454 let Node::Custom(actual) = actual.inner() else {
455 panic!()
456 };
457
458 let EscapedExpr::If(expr) = actual.expression else {
459 panic!()
460 };
461
462 assert_eq!(expr.condition, parse_quote!(just && an || expression));
463 }
464
465 #[test]
466 fn if_let_expr() {
467 let actual: Recoverable<MyNode> = parse_quote! {
468 @if let (foo) = Bar {
469 <div/>
470 }
471 };
472 let Node::Custom(actual) = actual.inner() else {
473 panic!()
474 };
475
476 let EscapedExpr::If(expr) = actual.expression else {
477 panic!()
478 };
479
480 assert_eq!(expr.condition, parse_quote!(let (foo) = Bar));
481 }
482
483 #[test]
484 fn if_simple() {
485 let actual: Recoverable<MyNode> = parse_quote! {
486 @if foo > bar {
487 <a regular="html" component/>
488 <div>
489 </div>
490 }
491 };
492
493 let Node::Custom(actual) = actual.inner() else {
494 panic!()
495 };
496
497 let EscapedExpr::If(expr) = &actual.expression else {
498 panic!()
499 };
500
501 assert_eq!(expr.condition, parse_quote!(foo > bar));
502 }
503
504 #[test]
505 fn if_else_if() {
506 let actual: Recoverable<MyNode> = parse_quote! {
507 @if foo > bar {
508 <first/>
509 } else if foo == 2 {
510 <second/>
511 } else if foo == 3 {
512 <third/>
513 } else {
514 <default/>
515 }
516 };
517
518 let Node::Custom(actual) = actual.inner() else {
519 panic!()
520 };
521
522 let EscapedExpr::If(expr) = &actual.expression else {
523 panic!()
524 };
525
526 assert_eq!(expr.condition, parse_quote!(foo > bar));
527 assert_eq!(expr.else_ifs[0].condition, parse_quote!(foo == 2));
528 assert_eq!(expr.else_ifs[1].condition, parse_quote!(foo == 3));
529 assert!(expr.else_branch.is_some());
530 }
531
532 #[test]
533 fn for_simple() {
534 let actual: Recoverable<MyNode> = parse_quote! {
535 @for x in foo {
536 <div>
537 {x}
538 </div>
539 }
540 };
541
542 let Node::Custom(actual) = actual.inner() else {
543 panic!()
544 };
545
546 let EscapedExpr::For(expr) = &actual.expression else {
547 panic!()
548 };
549
550 assert_eq!(expr.pat, parse_quote!(x));
551 assert_eq!(expr.expr, parse_quote!(foo));
552 }
553
554 #[test]
555 fn for_binding() {
556 let actual: Recoverable<MyNode> = parse_quote! {
557 @for (ref x, f) in foo {
558 <div>
559 {x}
560 </div>
561 }
562 };
563
564 let Node::Custom(actual) = actual.inner() else {
565 panic!()
566 };
567
568 let EscapedExpr::For(expr) = &actual.expression else {
569 panic!()
570 };
571
572 assert_eq!(expr.pat, parse_quote!((ref x, f)));
573 assert_eq!(expr.expr, parse_quote!(foo));
574 }
575
576 #[test]
577 fn match_simple() {
578 let actual: Recoverable<MyNode> = parse_quote! {
579 @match foo {
580 x => {<x/>}
581 _ => {<default/>}
582 }
583 };
584
585 let Node::Custom(actual) = actual.inner() else {
586 panic!()
587 };
588
589 let EscapedExpr::Match(expr) = &actual.expression else {
590 panic!()
591 };
592
593 assert_eq!(expr.expr, parse_quote!(foo));
594 assert_eq!(expr.arms[0].pat, parse_quote!(x));
595 assert_eq!(expr.arms[1].pat, parse_quote!(_));
596 }
597
598 #[test]
599 fn match_complex_exprs() {
600 let actual: Recoverable<MyNode> = parse_quote! {
601 @match foo > 2 * 2 {
602 Some(x) => {<x/>}
603 y|z => {<default/>}
604 }
605 };
606
607 let Node::Custom(actual) = actual.inner() else {
608 panic!()
609 };
610
611 let EscapedExpr::Match(expr) = &actual.expression else {
612 panic!()
613 };
614
615 assert_eq!(expr.expr, parse_quote!(foo > 2 * 2));
616 assert_eq!(expr.arms[0].pat, parse_quote!(Some(x)));
617 assert_eq!(expr.arms[1].pat, parse_quote!(y | z));
618 }
619
620 #[test]
621 fn check_if_inside_if() {
622 let actual: Recoverable<MyNode> = parse_quote! {
623 @if just && an || expression {
624 @if foo > bar {
625 <div/>
626 }
627 }
628 };
629 let Node::Custom(actual) = actual.inner() else {
630 panic!()
631 };
632
633 let EscapedExpr::If(expr) = actual.expression else {
634 panic!()
635 };
636
637 assert_eq!(expr.condition, parse_quote!(just && an || expression));
638 let node = expr.then_branch.body.iter().next().unwrap();
639 let Node::Custom(actual) = node else { panic!() };
640 let EscapedExpr::If(expr) = &actual.expression else {
641 panic!()
642 };
643 assert_eq!(expr.condition, parse_quote!(foo > bar));
644 }
645
646 #[test]
647 fn for_inside_if() {
648 let actual: Recoverable<MyNode> = parse_quote! {
649 @if just && an || expression {
650 @for x in foo {
651 <div/>
652 }
653 }
654 };
655 let Node::Custom(actual) = actual.inner() else {
656 panic!()
657 };
658
659 let EscapedExpr::If(expr) = actual.expression else {
660 panic!()
661 };
662
663 assert_eq!(expr.condition, parse_quote!(just && an || expression));
664 let node = expr.then_branch.body.iter().next().unwrap();
665 let Node::Custom(actual) = node else { panic!() };
666 let EscapedExpr::For(expr) = &actual.expression else {
667 panic!()
668 };
669 assert_eq!(expr.pat, parse_quote!(x));
670 assert_eq!(expr.expr, parse_quote!(foo));
671 }
672
673 #[test]
674 fn custom_node_using_config() {
675 let actual = Parser::new(
676 ParserConfig::new()
677 .element_close_use_default_wildcard_ident(false)
678 .custom_node::<MyCustomNode>(),
679 )
680 .parse_simple(quote! {
681 @if just && an || expression {
682 <a regular="html" component/>
683 <div>
684 </div>
685 }
686 })
687 .unwrap();
688 let Node::Custom(actual) = &actual[0] else {
689 panic!()
690 };
691
692 let EscapedExpr::If(expr) = &actual.expression else {
693 panic!()
694 };
695
696 assert_eq!(expr.condition, parse_quote!(just && an || expression));
697 }
698}
699
700#[cfg(test)]
701mod test_universal {
702 use proc_macro2::TokenStream;
703 use quote::quote;
704 use rstml::{
705 recoverable::Recoverable, visitor::visit_nodes_with_custom, ParserConfig, ParsingResult,
706 };
707 use syn::{parse_quote, visit_mut::VisitMut};
708
709 use super::*;
710 use crate::escape::visitor_impl::EscapeCodeWalker;
711
712 #[cfg(not(feature = "extendable"))]
714 fn parse_universal(input: TokenStream) -> ParsingResult<Vec<Node>> {
715 use rstml::Parser;
716
717 let actual =
718 Parser::new(ParserConfig::new().custom_node::<EscapeCode>()).parse_recoverable(input);
719
720 return actual;
721 }
722 #[cfg(feature = "extendable")]
723 fn parse_universal(input: TokenStream) -> ParsingResult<Vec<Node>> {
724 use crate::ExtendableCustomNode;
725
726 let result =
727 ExtendableCustomNode::parse2_with_config::<(EscapeCode,)>(ParserConfig::new(), input);
728 return result;
729 }
730
731 fn reparse_concrete<A: ToTokens>(val: A) -> EscapeCode {
733 let recoverable: Recoverable<_> = parse_quote!(#val);
734 recoverable.inner()
735 }
736
737 #[test]
738 fn if_node_reparsable() {
739 let tokens = quote! {
740 @if just && an || expression {
741 <div/>
742 }
743 };
744
745 let actual = &parse_universal(tokens).into_result().unwrap()[0];
746
747 let Node::Custom(actual) = actual else {
748 panic!()
749 };
750 let actual = reparse_concrete(actual);
751
752 let EscapedExpr::If(expr) = actual.expression else {
753 panic!()
754 };
755
756 assert_eq!(expr.condition, parse_quote!(just && an || expression));
757 }
758 #[test]
759 fn if_node_visitor_rstml() {
760 let tokens = quote! {
761 @if just && an || expression {
762 <div/>
763 }
764 };
765
766 let mut actual = parse_universal(tokens).into_result().unwrap();
767
768 struct ElementVisitor {
769 elements: Vec<String>,
770 }
771 impl<C: std::fmt::Debug> Visitor<C> for ElementVisitor {
772 fn visit_element(&mut self, node: &mut rstml::node::NodeElement<C>) -> bool {
773 self.elements.push(node.open_tag.name.to_string());
774 true
775 }
776 }
777 impl VisitMut for ElementVisitor {}
778
779 let visitor = ElementVisitor {
780 elements: Vec::new(),
781 };
782 let elements =
783 visit_nodes_with_custom::<_, _, EscapeCodeWalker>(&mut actual, visitor).elements;
784
785 assert_eq!(&elements, &["div"]);
786 }
787
788 #[test]
789 fn if_node_visitor_syn_expr() {
790 let tokens = quote! {
791 @if just && an || expression {
792 <div/>
793 }
794 };
795
796 let mut actual = parse_universal(tokens).into_result().unwrap();
797
798 struct ElementVisitor {
799 exprs: Vec<String>,
800 }
801 impl<C: std::fmt::Debug> Visitor<C> for ElementVisitor {}
802 impl VisitMut for ElementVisitor {
803 fn visit_expr_mut(&mut self, exprs: &mut syn::Expr) {
804 self.exprs.push(exprs.to_token_stream().to_string());
805 }
806 }
807
808 let visitor = ElementVisitor { exprs: Vec::new() };
809 let elements =
810 visit_nodes_with_custom::<_, _, EscapeCodeWalker>(&mut actual, visitor).exprs;
811
812 assert_eq!(&elements, &["just && an || expression"]);
813 }
814}