diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index fbe79b5d..417bec0d 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -36,7 +36,7 @@ pub(crate) fn template_to_string( heritage, MapChain::default(), input.block.is_some(), - 0, + BlockInfo::new(), ); let size_hint = match generator.impl_template(buf, tmpl_kind) { Err(mut err) if err.span.is_none() => { @@ -72,6 +72,34 @@ enum RenderFor { Extends, } +#[derive(Clone, Copy)] +struct BlockInfo { + block_name: &'static str, + level: usize, +} + +impl BlockInfo { + fn new() -> Self { + Self { + block_name: "", + level: 0, + } + } + + // FIXME: Instead of this error-prone API, we should use something relying on `Drop` to + // decrement, or use a callback which would decrement on exit. + fn increase(&mut self, block_name: &'static str) { + if self.level == 0 { + self.block_name = block_name; + } + self.level += 1; + } + + fn decrease(&mut self) { + self.level -= 1; + } +} + struct Generator<'a, 'h> { /// The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, @@ -92,8 +120,8 @@ struct Generator<'a, 'h> { super_block: Option<(&'a str, usize)>, /// Buffer for writable buf_writable: WritableBuffer<'a>, - /// Used in blocks to check if we are inside a filter block. - is_in_filter_block: usize, + /// Used in blocks to check if we are inside a filter/let block. + is_in_block: BlockInfo, /// Set of called macros we are currently in. Used to prevent (indirect) recursions. seen_callers: Vec<(&'a Macro<'a>, Option>)>, /// The directory path of the calling file. @@ -113,7 +141,7 @@ impl<'a, 'h> Generator<'a, 'h> { heritage: Option<&'h Heritage<'a, 'h>>, locals: MapChain<'a>, buf_writable_discard: bool, - is_in_filter_block: usize, + is_in_block: BlockInfo, ) -> Self { Self { input, @@ -127,7 +155,7 @@ impl<'a, 'h> Generator<'a, 'h> { discard: buf_writable_discard, ..Default::default() }, - is_in_filter_block, + is_in_block, seen_callers: Vec::new(), caller_dir: CallerDir::Unresolved, } diff --git a/askama_derive/src/generator/node.rs b/askama_derive/src/generator/node.rs index 154ce35a..ee47ca00 100644 --- a/askama_derive/src/generator/node.rs +++ b/askama_derive/src/generator/node.rs @@ -10,7 +10,7 @@ use parser::node::{ Call, Comment, Compound, Cond, CondTest, Declare, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws, }; -use parser::{Expr, Node, Span, Target, WithSpan}; +use parser::{Expr, LetValueOrBlock, Node, Span, Target, WithSpan}; use proc_macro2::TokenStream; use quote::quote_spanned; use rustc_hash::FxBuildHasher; @@ -82,7 +82,7 @@ impl<'a> Generator<'a, '_> { heritage, locals, self.buf_writable.discard, - self.is_in_filter_block, + self.is_in_block, ); child.buf_writable = buf_writable; let res = callback(&mut child); @@ -727,7 +727,7 @@ impl<'a> Generator<'a, '_> { let mut size_hint = self.write_buf_writable(ctx, buf)?; self.flush_ws(filter.ws1); - self.is_in_filter_block += 1; + self.is_in_block.increase("filter"); size_hint += self.write_buf_writable(ctx, buf)?; let span = ctx.span_for_node(filter.span()); @@ -785,7 +785,7 @@ impl<'a> Generator<'a, '_> { } } }); - self.is_in_filter_block -= 1; + self.is_in_block.decrease(); self.prepare_ws(filter.ws2); Ok(size_hint) } @@ -926,47 +926,25 @@ impl<'a> Generator<'a, '_> { fn write_let( &mut self, - ctx: &Context<'_>, + ctx: &Context<'a>, buf: &mut Buffer, l: &'a WithSpan>, ) -> Result { self.handle_ws(l.ws); - let span = ctx.span_for_node(l.span()); - let Some(val) = &l.val else { - let file_info = ctx - .file_info_of(l.span()) - .map(|info| format!(" {info}:")) - .unwrap_or_default(); - eprintln!( - "⚠️{file_info} `let` tag will stop supporting declaring variables without value. \ - Use `create` instead for this case", - ); - let size_hint = self.write_buf_writable(ctx, buf)?; - buf.write_token(Token![let], span); - if l.is_mutable { - buf.write_token(Token![mut], span); - } - self.visit_target(ctx, buf, false, true, &l.var, span); - buf.write_token(Token![;], span); - return Ok(size_hint); - }; - - // Handle when this statement creates a new alias of a caller variable (or of another alias), - if let Target::Name(dstvar) = l.var - && let Expr::Var(srcvar) = ***val - && let Some(caller_alias) = self.locals.get_caller(srcvar) - { - self.locals.insert( - Cow::Borrowed(*dstvar), - LocalMeta::CallerAlias(caller_alias.clone()), - ); - return Ok(SizeHint::EMPTY); + match &l.val { + LetValueOrBlock::Value(val) => self.write_let_value(ctx, buf, l, val), + LetValueOrBlock::Block { nodes, ws } => self.write_let_block(ctx, buf, l, nodes, *ws), } + } - let mut expr_buf = Buffer::new(); - self.visit_expr(ctx, &mut expr_buf, val)?; - + fn write_let_target( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + l: &'a WithSpan>, + span: proc_macro2::Span, + ) -> Result { let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?; let size_hint = if shadowed { // Need to flush the buffer if the variable is being shadowed, @@ -986,6 +964,104 @@ impl<'a> Generator<'a, '_> { } self.visit_target(ctx, buf, true, true, &l.var, span); + Ok(size_hint) + } + + fn write_let_block( + &mut self, + ctx: &Context<'a>, + buf: &mut Buffer, + l: &'a WithSpan>, + nodes: &'a [Box>], + ws: Ws, + ) -> Result { + let var_let_source = crate::var_let_source(); + + let mut size_hint = self.write_buf_writable(ctx, buf)?; + self.flush_ws(l.ws); + self.is_in_block.increase("let/set"); + size_hint += self.write_buf_writable(ctx, buf)?; + let span = ctx.span_for_node(l.span()); + + // build `FmtCell` that contains the inner block + let mut filter_def_buf = Buffer::new(); + size_hint += self.push_locals(|this| { + this.prepare_ws(l.ws); + let mut size_hint = this.handle( + ctx, + nodes, + &mut filter_def_buf, + AstLevel::Nested, + RenderFor::Template, + )?; + this.flush_ws(ws); + size_hint += this.write_buf_writable(ctx, &mut filter_def_buf)?; + Ok(size_hint) + })?; + let filter_def_buf = filter_def_buf.into_token_stream(); + + size_hint += self.write_let_target(ctx, buf, l, span)?; + buf.write_token(Token![=], span); + + let var_writer = crate::var_writer(); + let filter_def_buf = quote_spanned!(span=> + let #var_let_source = askama::helpers::FmtCell::new( + |#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> { + #filter_def_buf + askama::Result::Ok(()) + } + ); + ); + + // display the `FmtCell` + let mut filter_buf = Buffer::new(); + quote_into!(&mut filter_buf, span, { askama::filters::Safe(&#var_let_source) }); + let filter_buf = filter_buf.into_token_stream(); + let escaper = TokenStream::from_str(self.input.escaper).unwrap(); + let filter_buf = quote_spanned!(span=> + (&&askama::filters::AutoEscaper::new( + &(#filter_buf), #escaper + )).askama_auto_escape()? + ); + quote_into!(buf, span, { { + #filter_def_buf + let mut __askama_tmp_write = String::new(); + if askama::helpers::core::write!(&mut __askama_tmp_write, "{}", #filter_buf).is_err() { + return #var_let_source.take_err(); + } + __askama_tmp_write + }; }); + + self.is_in_block.decrease(); + self.prepare_ws(ws); + Ok(size_hint) + } + + fn write_let_value( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + l: &'a WithSpan>, + val: &WithSpan>>, + ) -> Result { + let span = ctx.span_for_node(l.span()); + // Handle when this statement creates a new alias of a caller variable (or of another alias), + if let Target::Name(dstvar) = l.var + && let Expr::Var(srcvar) = ***val + && let Some(caller_alias) = self.locals.get_caller(srcvar) + { + self.locals.insert( + Cow::Borrowed(*dstvar), + LocalMeta::CallerAlias(caller_alias.clone()), + ); + return Ok(SizeHint::EMPTY); + } + + let mut expr_buf = Buffer::new(); + self.visit_expr(ctx, &mut expr_buf, val)?; + + let size_hint = self.write_let_target(ctx, buf, l, span)?; + // If it's not taking the ownership of a local variable or copyable, then we need to add // a reference. let borrow = !matches!(***val, Expr::Try(..)) @@ -1036,8 +1112,14 @@ impl<'a> Generator<'a, '_> { outer: Ws, node: Span, ) -> Result { - if self.is_in_filter_block > 0 { - return Err(ctx.generate_error("cannot have a block inside a filter block", node)); + if self.is_in_block.level > 0 { + return Err(ctx.generate_error( + format!( + "cannot have a block inside a {} block", + self.is_in_block.block_name + ), + node, + )); } // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer); diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 51c8c878..fd5fa4a7 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -753,6 +753,10 @@ fn var_filter_source() -> Ident { syn::Ident::new("__askama_filter_block", proc_macro2::Span::call_site()) } +fn var_let_source() -> Ident { + syn::Ident::new("__askama_let_block", proc_macro2::Span::call_site()) +} + fn var_values() -> Ident { syn::Ident::new("__askama_values", proc_macro2::Span::call_site()) } diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index efb4d6ab..8b81afd6 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -31,7 +31,7 @@ use winnow::{LocatingSlice, ModalParser, ModalResult, Parser, Stateful}; use crate::ascii_str::{AsciiChar, AsciiStr}; pub use crate::expr::{AssociatedItem, Expr, Filter, PathComponent, TyGenerics}; -pub use crate::node::Node; +pub use crate::node::{LetValueOrBlock, Node}; pub use crate::target::{NamedTarget, Target}; mod _parsed { diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 5b36980a..d537fd23 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -1239,11 +1239,17 @@ impl<'a: 'l, 'l> Declare<'a> { } } +#[derive(Debug, PartialEq)] +pub enum LetValueOrBlock<'a> { + Value(WithSpan>>), + Block { nodes: Vec>>, ws: Ws }, +} + #[derive(Debug, PartialEq)] pub struct Let<'a> { pub ws: Ws, pub var: Target<'a>, - pub val: Option>>>, + pub val: LetValueOrBlock<'a>, pub is_mutable: bool, } @@ -1322,11 +1328,57 @@ impl<'a: 'l, 'l> Let<'a> { ); } + if let Some(val) = val { + return Ok(Box::new(Node::Let(WithSpan::new( + Let { + ws: Ws(pws, nws), + var, + val: LetValueOrBlock::Value(val), + is_mutable: is_mut.is_some(), + }, + span, + )))); + } + + // We do this operation + if block_end.parse_next(i).is_err() { + return Err( + ErrorContext::unclosed("block", i.state.syntax.block_end, Span::new(span)).cut(), + ); + } + + let (keyword, end_keyword) = if tag == "let" { + ("let", "endlet") + } else { + ("set", "endset") + }; + + let keyword_span = Span::new(span.clone()); + let mut end = cut_node( + Some(keyword), + ( + Node::many, + cut_node( + Some(keyword), + ( + |i: &mut _| check_block_start(i, keyword_span, keyword, end_keyword), + opt(Whitespace::parse), + end_node(keyword, end_keyword), + opt(Whitespace::parse), + ), + ), + ), + ); + let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?; + Ok(Box::new(Node::Let(WithSpan::new( Let { ws: Ws(pws, nws), var, - val, + val: LetValueOrBlock::Block { + nodes, + ws: Ws(pws2, nws2), + }, is_mutable: is_mut.is_some(), }, span, diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index 909bbb87..5e5144bf 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -5,8 +5,8 @@ use winnow::{LocatingSlice, Parser}; use crate::expr::BinOp; use crate::node::{Let, Lit, Raw, Whitespace, Ws}; use crate::{ - Ast, Expr, Filter, InnerSyntax, InputStream, Level, Node, Num, PathComponent, PathOrIdentifier, - State, StrLit, Syntax, SyntaxBuilder, Target, WithSpan, + Ast, Expr, Filter, InnerSyntax, InputStream, LetValueOrBlock, Level, Node, Num, PathComponent, + PathOrIdentifier, State, StrLit, Syntax, SyntaxBuilder, Target, WithSpan, }; fn as_path<'a>(path: &'a [&'a str]) -> Vec> { @@ -1085,8 +1085,12 @@ fn fuzzed_comment_depth() { fn let_set() { let syntax = Syntax::default(); assert_eq!( - Ast::from_str("{% let a %}", None, &syntax).unwrap().nodes(), - Ast::from_str("{% set a %}", None, &syntax).unwrap().nodes(), + Ast::from_str("{% let a = 1 %}", None, &syntax) + .unwrap() + .nodes(), + Ast::from_str("{% set a = 1 %}", None, &syntax) + .unwrap() + .nodes(), ); } @@ -1655,7 +1659,9 @@ fn regression_tests_span_change() { var: Target::Array(WithSpan::no_span(vec![Target::Placeholder( WithSpan::no_span(()) )])), - val: Some(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit("2")])))), + val: LetValueOrBlock::Value(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit( + "2" + )])))), is_mutable: false, })))], ); @@ -1667,7 +1673,9 @@ fn regression_tests_span_change() { [Box::new(Node::Let(WithSpan::no_span(Let { ws: Ws(Some(Whitespace::Suppress), Some(Whitespace::Suppress)), var: Target::Placeholder(WithSpan::no_span(())), - val: Some(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit("2")])))), + val: LetValueOrBlock::Value(WithSpan::no_span(Box::new(Expr::Array(vec![int_lit( + "2" + )])))), is_mutable: false, })))], ); diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md index 4c5c7c07..78f057ab 100644 --- a/book/src/template_syntax.md +++ b/book/src/template_syntax.md @@ -91,6 +91,16 @@ you need it to be mutable #} For compatibility with Jinja, `set` can be used in place of `let`. +### Let/set blocks + +You can create a variable and initialize it with a block computed string: + +```jinja +{% let x %} +{{ crate::some_function() }} = {{ a * b}} +{% endlet %} +``` + ### Set variable values later If you want to create a variable but set its value based on a condition, you can diff --git a/testing/templates/let-decl.html b/testing/templates/let-decl.html index d3489039..383c03b7 100644 --- a/testing/templates/let-decl.html +++ b/testing/templates/let-decl.html @@ -1,4 +1,4 @@ -{% let val -%} +{% decl val -%} {% if cond -%} {% let val = "foo" -%} {% else -%} diff --git a/testing/templates/let-shadow.html b/testing/templates/let-shadow.html index 938c5bfe..66ea65d2 100644 --- a/testing/templates/let-shadow.html +++ b/testing/templates/let-shadow.html @@ -1,5 +1,5 @@ {%- let a = 1 -%} -{%- let b -%} +{%- decl b -%} {%- if cond -%} {%- let b = 22 -%} diff --git a/testing/tests/default.rs b/testing/tests/default.rs index 78f269ad..1c1c464a 100644 --- a/testing/tests/default.rs +++ b/testing/tests/default.rs @@ -535,7 +535,7 @@ fn test_default_with_forward_declaration() { #[derive(Template)] #[template( source = "\ - {% let var %}\ + {% decl var %}\ {{ var | default(\"unknown\") }}\ {% if true %}{% let var = 42 %}{% endif %}", ext = "html" @@ -547,7 +547,7 @@ fn test_default_with_forward_declaration() { #[derive(Template)] #[template( source = "\ - {% let var %}{# shadowing happens here #}\ + {% decl var %}{# shadowing happens here #}\ {{ var | default(\"unknown\") }}\ {% if true %}{% let var = 42 %}{% endif %}", ext = "html" @@ -568,7 +568,7 @@ fn test_defined_or_with_forward_declaration() { #[derive(Template)] #[template( source = "\ - {% let var %}\ + {% decl var %}\ {{ var | defined_or(\"unknown\") }}\ {% if true %}{% let var = 42 %}{% endif %}", ext = "html" @@ -580,7 +580,7 @@ fn test_defined_or_with_forward_declaration() { #[derive(Template)] #[template( source = "\ - {% let var %}{# shadowing happens here #}\ + {% decl var %}{# shadowing happens here #}\ {{ var | defined_or(\"unknown\") }}\ {% if true %}{% let var = 42 %}{% endif %}", ext = "html" diff --git a/testing/tests/let.rs b/testing/tests/let.rs index a12abce2..9427def1 100644 --- a/testing/tests/let.rs +++ b/testing/tests/let.rs @@ -5,7 +5,7 @@ use askama::Template; fn let_macro() { #[derive(Template)] #[template( - source = r#"{%- let x -%} + source = r#"{%- decl x -%} {%- if y -%} {%- let x = String::new() %} {%- else -%} @@ -53,3 +53,20 @@ fn underscore_ident2() { assert_eq!(X.render().unwrap(), "hey\nhoy\nmatched"); } + +#[test] +fn let_block() { + #[derive(Template)] + #[template( + source = r#" +{%- set navigation %}{{b}}: c{% endset -%} +{{ navigation -}} +"#, + ext = "txt" + )] + struct Foo { + b: u32, + } + + assert_eq!(Foo { b: 0 }.render().unwrap(), "0: c"); +} diff --git a/testing/tests/ui/block_in_filter_block.rs b/testing/tests/ui/block_in_filter_block.rs index aa680a4c..4410a13f 100644 --- a/testing/tests/ui/block_in_filter_block.rs +++ b/testing/tests/ui/block_in_filter_block.rs @@ -17,5 +17,22 @@ use askama::Template; )] struct BlockInFilter; +#[derive(Template)] +#[template( + source = r#"{% extends "html-base.html" %} + +{%- block body -%} +

Metadata

+ + {% let x %} + {% block title %}New title{% endblock %} + a b + {% endlet %} +{%- endblock body %} +"#, + ext = "html" +)] +struct BlockInLetBlock; + fn main() { } diff --git a/testing/tests/ui/block_in_filter_block.stderr b/testing/tests/ui/block_in_filter_block.stderr index b19bc02b..3718d0af 100644 --- a/testing/tests/ui/block_in_filter_block.stderr +++ b/testing/tests/ui/block_in_filter_block.stderr @@ -12,3 +12,18 @@ error: cannot have a block inside a filter block 14 | | {%- endblock body %} 15 | | "#, | |__^ + +error: cannot have a block inside a let/set block + --> BlockInLetBlock.html:7:11 + "block title %}New title{% endblock %}\n a b\n {% endlet %}\n{%- endblock "... + --> tests/ui/block_in_filter_block.rs:22:14 + | +22 | source = r#"{% extends "html-base.html" %} + | ______________^ +23 | | +24 | | {%- block body -%} +25 | |

Metadata

+... | +31 | | {%- endblock body %} +32 | | "#, + | |__^ diff --git a/testing/tests/ui/unclosed-nodes.rs b/testing/tests/ui/unclosed-nodes.rs index 9fc69ecc..07da572d 100644 --- a/testing/tests/ui/unclosed-nodes.rs +++ b/testing/tests/ui/unclosed-nodes.rs @@ -32,6 +32,18 @@ struct Node3; #[template(source = "{% let x -%", ext = "txt")] struct Node4; +#[derive(Template)] +#[template(source = "{% let x %}{% endlet", ext = "txt")] +struct Node5; + +#[derive(Template)] +#[template(source = "{% let x %}{% endset %}", ext = "txt")] +struct Node6; + +#[derive(Template)] +#[template(source = "{% set x %}{% endlet %}", ext = "txt")] +struct Node7; + #[derive(Template)] #[template(source = "{# comment", ext = "txt")] struct Comment1; diff --git a/testing/tests/ui/unclosed-nodes.stderr b/testing/tests/ui/unclosed-nodes.stderr index 6cd4e472..fe7fe2a3 100644 --- a/testing/tests/ui/unclosed-nodes.stderr +++ b/testing/tests/ui/unclosed-nodes.stderr @@ -31,65 +31,89 @@ error: failed to parse template source | ^^^^^^^^^^^^ error: unclosed block, missing "%}" - --> :1:0 - "{% let x" + --> :1:3 + "let x" --> tests/ui/unclosed-nodes.rs:20:21 | 20 | #[template(source = "{% let x", ext = "txt")] | ^^^^^^^^^^ error: unclosed block, missing "%}" - --> :1:0 - "{% let x " + --> :1:3 + "let x " --> tests/ui/unclosed-nodes.rs:24:21 | 24 | #[template(source = "{% let x ", ext = "txt")] | ^^^^^^^^^^^ error: unclosed block, missing "%}" - --> :1:0 - "{% let x -" + --> :1:3 + "let x -" --> tests/ui/unclosed-nodes.rs:28:21 | 28 | #[template(source = "{% let x -", ext = "txt")] | ^^^^^^^^^^^^ -error: failed to parse template source - --> :1:10 - "%" +error: unclosed block, missing "%}" + --> :1:3 + "let x -%" --> tests/ui/unclosed-nodes.rs:32:21 | 32 | #[template(source = "{% let x -%", ext = "txt")] | ^^^^^^^^^^^^^ +error: unclosed block, missing "%}" + --> :1:0 + "{% let x %}{% endlet" + --> tests/ui/unclosed-nodes.rs:36:21 + | +36 | #[template(source = "{% let x %}{% endlet", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endlet` to terminate `let` node, found `endset` + --> :1:14 + "endset %}" + --> tests/ui/unclosed-nodes.rs:40:21 + | +40 | #[template(source = "{% let x %}{% endset %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endset` to terminate `set` node, found `endlet` + --> :1:14 + "endlet %}" + --> tests/ui/unclosed-nodes.rs:44:21 + | +44 | #[template(source = "{% set x %}{% endlet %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + error: unclosed comment, missing "#}" --> :1:0 "{# comment" - --> tests/ui/unclosed-nodes.rs:36:21 + --> tests/ui/unclosed-nodes.rs:48:21 | -36 | #[template(source = "{# comment", ext = "txt")] +48 | #[template(source = "{# comment", ext = "txt")] | ^^^^^^^^^^^^ error: unclosed comment, missing "#}" --> :1:0 "{# comment " - --> tests/ui/unclosed-nodes.rs:40:21 + --> tests/ui/unclosed-nodes.rs:52:21 | -40 | #[template(source = "{# comment ", ext = "txt")] +52 | #[template(source = "{# comment ", ext = "txt")] | ^^^^^^^^^^^^^ error: unclosed comment, missing "#}" --> :1:0 "{# comment -" - --> tests/ui/unclosed-nodes.rs:44:21 + --> tests/ui/unclosed-nodes.rs:56:21 | -44 | #[template(source = "{# comment -", ext = "txt")] +56 | #[template(source = "{# comment -", ext = "txt")] | ^^^^^^^^^^^^^^ error: unclosed comment, missing "#}" --> :1:0 "{# comment -#" - --> tests/ui/unclosed-nodes.rs:48:21 + --> tests/ui/unclosed-nodes.rs:60:21 | -48 | #[template(source = "{# comment -#", ext = "txt")] +60 | #[template(source = "{# comment -#", ext = "txt")] | ^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/unexpected-tag.stderr b/testing/tests/ui/unexpected-tag.stderr index 1dbc1eff..577f95a3 100644 --- a/testing/tests/ui/unexpected-tag.stderr +++ b/testing/tests/ui/unexpected-tag.stderr @@ -30,14 +30,6 @@ error: node `when` was not expected in the current context 34 | /// ```askama | ^^^^^^^^^^^^^ -error: unexpected closing tag `endlet` - --> :1:21 - "endlet %}" - --> tests/ui/unexpected-tag.rs:43:1 - | -43 | /// ```askama - | ^^^^^^^^^^^^^ - error: unknown node `syntax` --> :1:3 "syntax error %}" diff --git a/testing/tests/vars.rs b/testing/tests/vars.rs index 274b9c85..bdac2d03 100644 --- a/testing/tests/vars.rs +++ b/testing/tests/vars.rs @@ -123,7 +123,7 @@ fn test_decl_range() { fn test_decl_assign_range() { #[derive(Template)] #[template( - source = "{% let x %}{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}", + source = "{% decl x %}{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}", ext = "txt" )] struct DeclAssignRange;