use crate::node::{Lit, Whitespace, Ws}; use crate::{Ast, Expr, Filter, InnerSyntax, Node, Num, StrLit, Syntax, SyntaxBuilder, WithSpan}; impl WithSpan<'static, T> { fn no_span(inner: T) -> Self { Self { inner, span: "" } } } fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let Lit { lws, val, rws } = Lit::split_ws_parts(s); assert_eq!(lws, res.0); assert_eq!(val, res.1); assert_eq!(rws, res.2); } #[test] fn test_ws_splitter() { check_ws_split("", &("", "", "")); check_ws_split("a", &("", "a", "")); check_ws_split("\ta", &("\t", "a", "")); check_ws_split("b\n", &("", "b", "\n")); check_ws_split(" \t\r\n", &(" \t\r\n", "", "")); } #[test] #[should_panic] fn test_invalid_block() { Ast::from_str("{% extend \"blah\" %}", None, &Syntax::default()).unwrap(); } fn int_lit(i: &str) -> Expr<'_> { Expr::NumLit(i, Num::Int(i, None)) } #[test] fn test_parse_filter() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ strvar|e }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "e", arguments: vec![WithSpan::no_span(Expr::Var("strvar"))] })), )], ); assert_eq!( Ast::from_str("{{ 2|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(int_lit("2"))] })), )], ); assert_eq!( Ast::from_str("{{ -2|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Unary( "-", WithSpan::no_span(int_lit("2")).into() ))] })), )], ); assert_eq!( Ast::from_str("{{ (1 - 2)|abs }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Group( WithSpan::no_span(Expr::BinOp( "-", WithSpan::no_span(int_lit("1")).into(), WithSpan::no_span(int_lit("2")).into() )) .into() ))], })), )], ); } #[test] fn test_parse_numbers() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ 2 }}", None, &syntax).unwrap().nodes, vec![Node::Expr(Ws(None, None), WithSpan::no_span(int_lit("2")))], ); assert_eq!( Ast::from_str("{{ 2.5 }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::NumLit("2.5", Num::Float("2.5", None))) )], ); } #[test] fn test_parse_var() { let s = Syntax::default(); assert_eq!(Ast::from_str("{{ foo }}", None, &s).unwrap().nodes, vec![ Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Var("foo"))) ],); assert_eq!( Ast::from_str("{{ foo_bar }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Var("foo_bar")) )], ); assert_eq!(Ast::from_str("{{ none }}", None, &s).unwrap().nodes, vec![ Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Var("none"))) ],); } #[test] fn test_parse_const() { let s = Syntax::default(); assert_eq!(Ast::from_str("{{ FOO }}", None, &s).unwrap().nodes, vec![ Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Path(vec!["FOO"]))) ],); assert_eq!( Ast::from_str("{{ FOO_BAR }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Path(vec!["FOO_BAR"])) )], ); assert_eq!(Ast::from_str("{{ NONE }}", None, &s).unwrap().nodes, vec![ Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Path(vec!["NONE"]))) ],); } #[test] fn test_parse_path() { let s = Syntax::default(); assert_eq!(Ast::from_str("{{ None }}", None, &s).unwrap().nodes, vec![ Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Path(vec!["None"]))) ],); assert_eq!( Ast::from_str("{{ Some(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec!["Some"]))), vec![WithSpan::no_span(int_lit("123"))] )), )], ); assert_eq!( Ast::from_str("{{ Ok(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec!["Ok"]))), vec![WithSpan::no_span(int_lit("123"))] )), )], ); assert_eq!( Ast::from_str("{{ Err(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec!["Err"]))), vec![WithSpan::no_span(int_lit("123"))] )), )], ); } #[test] fn test_parse_var_call() { assert_eq!( Ast::from_str("{{ function(\"123\", 3) }}", None, &Syntax::default()) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Var("function"))), vec![ WithSpan::no_span(Expr::StrLit(StrLit { content: "123", prefix: None, })), WithSpan::no_span(int_lit("3")) ] )), )], ); } #[test] fn test_parse_path_call() { let s = Syntax::default(); assert_eq!( Ast::from_str("{{ Option::None }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Path(vec!["Option", "None"])) )], ); assert_eq!( Ast::from_str("{{ Option::Some(123) }}", None, &s) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec!["Option", "Some"]))), vec![WithSpan::no_span(int_lit("123"))], ),) )], ); assert_eq!( Ast::from_str("{{ self::function(\"123\", 3) }}", None, &s) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec!["self", "function"]))), vec![ WithSpan::no_span(Expr::StrLit(StrLit { content: "123", prefix: None, })), WithSpan::no_span(int_lit("3")) ], ),) )], ); } #[test] fn test_parse_root_path() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ std::string::String::new() }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec![ "std", "string", "String", "new" ]))), vec![] )), )], ); assert_eq!( Ast::from_str("{{ ::std::string::String::new() }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Path(vec![ "", "std", "string", "String", "new" ]))), vec![] )), )], ); } #[test] fn test_rust_macro() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ vec!(1, 2, 3) }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["vec"], "1, 2, 3")), )], ); assert_eq!( Ast::from_str("{{ alloc::vec!(1, 2, 3) }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["alloc", "vec"], "1, 2, 3")), )], ); assert_eq!(Ast::from_str("{{a!()}}", None, &syntax).unwrap().nodes, [ Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) ) ],); assert_eq!(Ast::from_str("{{a !()}}", None, &syntax).unwrap().nodes, [ Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) ) ],); assert_eq!(Ast::from_str("{{a! ()}}", None, &syntax).unwrap().nodes, [ Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) ) ],); assert_eq!(Ast::from_str("{{a ! ()}}", None, &syntax).unwrap().nodes, [ Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) ) ],); assert_eq!(Ast::from_str("{{A!()}}", None, &syntax).unwrap().nodes, [ Node::Expr( Ws(None, None), WithSpan::no_span(Expr::RustMacro(vec!["A"], "")) ) ],); assert_eq!( &*Ast::from_str("{{a.b.c!( hello )}}", None, &syntax) .unwrap_err() .to_string(), "failed to parse template source near offset 7", ); } #[test] fn change_delimiters_parse_filter() { let syntax = Syntax(InnerSyntax { expr_start: "{=", expr_end: "=}", ..InnerSyntax::default() }); Ast::from_str("{= strvar|e =}", None, &syntax).unwrap(); } #[test] fn unicode_delimiters_in_syntax() { let syntax = Syntax(InnerSyntax { expr_start: "🖎", // U+1F58E == b"\xf0\x9f\x96\x8e" expr_end: "✍", // U+270D = b'\xe2\x9c\x8d' ..InnerSyntax::default() }); assert_eq!( Ast::from_str("Here comes the expression: 🖎 e ✍.", None, &syntax) .unwrap() .nodes(), [ Node::Lit(WithSpan::no_span(Lit { lws: "", val: "Here comes the expression:", rws: " ", })), Node::Expr(Ws(None, None), WithSpan::no_span(Expr::Var("e")),), Node::Lit(WithSpan::no_span(Lit { lws: "", val: ".", rws: "", })), ], ); } #[test] fn test_precedence() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a + b == c }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "==", WithSpan::no_span(Expr::BinOp( "+", WithSpan::no_span(Expr::Var("a")).into(), WithSpan::no_span(Expr::Var("b")).into() )) .into(), WithSpan::no_span(Expr::Var("c")).into() ),) )], ); assert_eq!( Ast::from_str("{{ a + b * c - d / e }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "-", WithSpan::no_span(Expr::BinOp( "+", WithSpan::no_span(Expr::Var("a")).into(), WithSpan::no_span(Expr::BinOp( "*", WithSpan::no_span(Expr::Var("b")).into(), WithSpan::no_span(Expr::Var("c")).into() )) .into() )) .into(), WithSpan::no_span(Expr::BinOp( "/", WithSpan::no_span(Expr::Var("d")).into(), WithSpan::no_span(Expr::Var("e")).into() )) .into(), )) )], ); assert_eq!( Ast::from_str("{{ a * (b + c) / -d }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "/", Box::new(WithSpan::no_span(Expr::BinOp( "*", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::Var("b"))), Box::new(WithSpan::no_span(Expr::Var("c"))) ) ))))) ))), Box::new(WithSpan::no_span(Expr::Unary( "-", Box::new(WithSpan::no_span(Expr::Var("d"))) ))) )) )], ); assert_eq!( Ast::from_str("{{ a || b && c || d && e }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "||", Box::new(WithSpan::no_span(Expr::BinOp( "||", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::BinOp( "&&", Box::new(WithSpan::no_span(Expr::Var("b"))), Box::new(WithSpan::no_span(Expr::Var("c"))) ))), ))), Box::new(WithSpan::no_span(Expr::BinOp( "&&", Box::new(WithSpan::no_span(Expr::Var("d"))), Box::new(WithSpan::no_span(Expr::Var("e"))) ))), )) )], ); } #[test] fn test_associativity() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a + b + c }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), Box::new(WithSpan::no_span(Expr::Var("c"))) )) )], ); assert_eq!( Ast::from_str("{{ a * b * c }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "*", Box::new(WithSpan::no_span(Expr::BinOp( "*", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), Box::new(WithSpan::no_span(Expr::Var("c"))) )) )], ); assert_eq!( Ast::from_str("{{ a && b && c }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "&&", Box::new(WithSpan::no_span(Expr::BinOp( "&&", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), Box::new(WithSpan::no_span(Expr::Var("c"))) )) )], ); assert_eq!( Ast::from_str("{{ a + b - c + d }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::BinOp( "-", Box::new(WithSpan::no_span(Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), Box::new(WithSpan::no_span(Expr::Var("c"))) ))), Box::new(WithSpan::no_span(Expr::Var("d"))) )) )], ); assert_eq!( Ast::from_str("{{ a == b != c > d > e == f }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "==", Box::new(WithSpan::no_span(Expr::BinOp( ">", Box::new(WithSpan::no_span(Expr::BinOp( ">", Box::new(WithSpan::no_span(Expr::BinOp( "!=", Box::new(WithSpan::no_span(Expr::BinOp( "==", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), Box::new(WithSpan::no_span(Expr::Var("c"))) ))), Box::new(WithSpan::no_span(Expr::Var("d"))) ))), Box::new(WithSpan::no_span(Expr::Var("e"))) ))), Box::new(WithSpan::no_span(Expr::Var("f"))) )) )], ); } #[test] fn test_odd_calls() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ a[b](c) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Index( Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ))), vec![WithSpan::no_span(Expr::Var("c"))], ),) )], ); assert_eq!( Ast::from_str("{{ (a + b)(c) }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Var("b"))) ) ))))), vec![WithSpan::no_span(Expr::Var("c"))], ),) )], ); assert_eq!( Ast::from_str("{{ a + b(c) }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::BinOp( "+", Box::new(WithSpan::no_span(Expr::Var("a"))), Box::new(WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Var("b"))), vec![WithSpan::no_span(Expr::Var("c"))] ))), )), )], ); assert_eq!( Ast::from_str("{{ (-a)(b) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( Expr::Unary("-", Box::new(WithSpan::no_span(Expr::Var("a")))) ))))), vec![WithSpan::no_span(Expr::Var("b"))], ),) )], ); assert_eq!( Ast::from_str("{{ -a(b) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Unary( "-", Box::new(WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Var("a"))), vec![WithSpan::no_span(Expr::Var("b"))] ))) ),) )], ); assert_eq!( Ast::from_str("{{ a(b)|c }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "c", arguments: vec![WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Var("a"))), vec![WithSpan::no_span(Expr::Var("b"))] ))] }),) )] ); assert_eq!( Ast::from_str("{{ a(b)| c }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "c", arguments: vec![WithSpan::no_span(Expr::Call( Box::new(WithSpan::no_span(Expr::Var("a"))), vec![WithSpan::no_span(Expr::Var("b"))] ))] })), )] ); } #[test] fn test_parse_comments() { #[track_caller] fn one_comment_ws(source: &str, ws: Ws) { let s = &Syntax::default(); let mut nodes = Ast::from_str(source, None, s).unwrap().nodes; assert_eq!(nodes.len(), 1, "expected to parse one node"); match nodes.pop().unwrap() { Node::Comment(comment) => assert_eq!(comment.ws, ws), node => panic!("expected a comment not, but parsed {node:?}"), } } one_comment_ws("{##}", Ws(None, None)); one_comment_ws("{#- #}", Ws(Some(Whitespace::Suppress), None)); one_comment_ws("{# -#}", Ws(None, Some(Whitespace::Suppress))); one_comment_ws( "{#--#}", Ws(Some(Whitespace::Suppress), Some(Whitespace::Suppress)), ); one_comment_ws( "{#- foo\n bar -#}", Ws(Some(Whitespace::Suppress), Some(Whitespace::Suppress)), ); one_comment_ws( "{#- foo\n {#- bar\n -#} baz -#}", Ws(Some(Whitespace::Suppress), Some(Whitespace::Suppress)), ); one_comment_ws("{#+ #}", Ws(Some(Whitespace::Preserve), None)); one_comment_ws("{# +#}", Ws(None, Some(Whitespace::Preserve))); one_comment_ws( "{#++#}", Ws(Some(Whitespace::Preserve), Some(Whitespace::Preserve)), ); one_comment_ws( "{#+ foo\n bar +#}", Ws(Some(Whitespace::Preserve), Some(Whitespace::Preserve)), ); one_comment_ws( "{#+ foo\n {#+ bar\n +#} baz -+#}", Ws(Some(Whitespace::Preserve), Some(Whitespace::Preserve)), ); one_comment_ws("{#~ #}", Ws(Some(Whitespace::Minimize), None)); one_comment_ws("{# ~#}", Ws(None, Some(Whitespace::Minimize))); one_comment_ws( "{#~~#}", Ws(Some(Whitespace::Minimize), Some(Whitespace::Minimize)), ); one_comment_ws( "{#~ foo\n bar ~#}", Ws(Some(Whitespace::Minimize), Some(Whitespace::Minimize)), ); one_comment_ws( "{#~ foo\n {#~ bar\n ~#} baz -~#}", Ws(Some(Whitespace::Minimize), Some(Whitespace::Minimize)), ); one_comment_ws("{# foo {# bar #} {# {# baz #} qux #} #}", Ws(None, None)); } #[test] fn test_parse_tuple() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ () }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![]),) )], ); assert_eq!( Ast::from_str("{{ (1) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span(int_lit("1"))),)) )], ); assert_eq!( Ast::from_str("{{ (1,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1, ) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1 ,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1 , ) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(int_lit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1, 2) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])), )], ); assert_eq!( Ast::from_str("{{ (1, 2,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])), )], ); assert_eq!( Ast::from_str("{{ (1, 2, 3) }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Tuple(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")), WithSpan::no_span(int_lit("3")) ])), )], ); assert_eq!( Ast::from_str("{{ ()|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Tuple(vec![]))] })), )], ); assert_eq!( Ast::from_str("{{ (1)|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( int_lit("1") ))))] })), )], ); assert_eq!( Ast::from_str("{{ (1,)|abs }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span( int_lit("1") )]))] })), )], ); assert_eq!( Ast::from_str("{{ (1, 2)|abs }}", None, &syntax) .unwrap() .nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "abs", arguments: vec![WithSpan::no_span(Expr::Tuple(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ]))] })), )], ); } #[test] fn test_missing_space_after_kw() { let syntax = Syntax::default(); let err = Ast::from_str("{%leta=b%}", None, &syntax).unwrap_err(); assert_eq!( err.to_string(), "unknown node `leta`\nfailed to parse template source near offset 2", ); } #[test] fn test_parse_array() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ [] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![])) )], ); assert_eq!( Ast::from_str("{{ [1] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [ 1] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [1 ] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(int_lit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [1,2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])) )], ); assert_eq!( Ast::from_str("{{ [1 ,2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])) )], ); assert_eq!( Ast::from_str("{{ [1, 2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])) )], ); assert_eq!( Ast::from_str("{{ [1,2 ] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![ WithSpan::no_span(int_lit("1")), WithSpan::no_span(int_lit("2")) ])) )], ); assert_eq!( Ast::from_str("{{ []|foo }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "foo", arguments: vec![WithSpan::no_span(Expr::Array(vec![]))] })) )], ); assert_eq!( Ast::from_str("{{ []| foo }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Filter(Filter { name: "foo", arguments: vec![WithSpan::no_span(Expr::Array(vec![]))] })) )], ); let n = || { Node::Expr( Ws(None, None), WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit( "1", Num::Int("1", None), ))])), ) }; assert_eq!( Ast::from_str( "{{ [1,] }}{{ [1 ,] }}{{ [1, ] }}{{ [1 , ] }}", None, &syntax ) .unwrap() .nodes, vec![n(), n(), n(), n()], ); } #[test] fn fuzzed_unicode_slice() { let d = "{eeuuu{b&{!!&{!!11{{ 0!(!1q҄א!)!!!!!!n!"; assert!(Ast::from_str(d, None, &Syntax::default()).is_err()); } #[test] fn fuzzed_macro_no_end() { let s = "{%macro super%}{%endmacro"; assert!(Ast::from_str(s, None, &Syntax::default()).is_err()); } #[test] fn fuzzed_target_recursion() { const TEMPLATE: &str = include_str!("../tests/target-recursion.txt"); assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_err()); } #[test] fn fuzzed_unary_recursion() { const TEMPLATE: &str = include_str!("../tests/unary-recursion.txt"); assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_err()); } #[test] fn fuzzed_comment_depth() { let (sender, receiver) = std::sync::mpsc::channel(); let test = std::thread::spawn(move || { const TEMPLATE: &str = include_str!("../tests/comment-depth.txt"); assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_ok()); sender.send(()).unwrap(); }); receiver .recv_timeout(std::time::Duration::from_secs(3)) .expect("timeout"); test.join().unwrap(); } #[test] fn let_set() { assert_eq!( Ast::from_str("{% let a %}", None, &Syntax::default()) .unwrap() .nodes(), Ast::from_str("{% set a %}", None, &Syntax::default()) .unwrap() .nodes(), ); } #[test] fn fuzzed_filter_recursion() { const TEMPLATE: &str = include_str!("../tests/filter-recursion.txt"); assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_err()); } #[test] #[should_panic = "called `Result::unwrap()` on an `Err` value: \ \"delimiters must be at most 32 characters long. The closing block delimiter \ (\\\"\\\\0]***NEWFILE\\\\u{1f}***\\\"...) is too long\""] fn fuzzed_excessive_syntax_lengths() { let syntax = SyntaxBuilder { name: "test", block_start: Some("\0*DD\0\0"), block_end: Some( "\0]***NEWFILE\u{1f}***:7/v/.-3/\u{1b}/~~~~z~0/*:7/v/./t/t/.p//NEWVILE**::7/v", ), expr_start: Some("/."), expr_end: None, comment_start: Some(include_str!( "../tests/excessive_syntax_lengths-comment-start.bin" )), comment_end: None, } .to_syntax() .unwrap(); let (sender, receiver) = std::sync::mpsc::channel(); let test = std::thread::spawn(move || { let result = Ast::from_str( include_str!("../tests/excessive_syntax_lengths-source.bin"), None, &syntax, ); sender.send(result).unwrap(); }); let _: Result, crate::ParseError> = receiver .recv_timeout(std::time::Duration::from_secs(3)) .expect("timeout"); test.join().unwrap(); }