From 568ced0c55ca2076951035ef5876f55fa0a84324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 16 Jun 2025 19:16:51 +0200 Subject: [PATCH] parser: recognize comments in macro calls Fixes issue --- askama_parser/src/expr.rs | 46 +++++++++++- askama_parser/src/tests.rs | 67 +++++++++++++++++ ...testcase-minimized-derive-6051930453639168 | 1 + testing/tests/ui/comments-in-macro-calls.rs | 41 +++++++++++ .../tests/ui/comments-in-macro-calls.stderr | 71 +++++++++++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 fuzzing/fuzz/artifacts/derive/clusterfuzz-testcase-minimized-derive-6051930453639168 create mode 100644 testing/tests/ui/comments-in-macro-calls.rs create mode 100644 testing/tests/ui/comments-in-macro-calls.stderr diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index ca3e7ec9..11b6a7be 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -4,9 +4,10 @@ use std::str; use winnow::Parser; use winnow::ascii::digit1; use winnow::combinator::{ - alt, cut_err, fail, not, opt, peek, preceded, repeat, separated, terminated, + alt, cut_err, empty, fail, not, opt, peek, preceded, repeat, separated, terminated, }; use winnow::error::ParserError as _; +use winnow::token::take_until; use crate::node::CondTest; use crate::{ @@ -780,6 +781,9 @@ impl<'a> Suffix<'a> { identifier_or_prefixed_string.value(Token::SomeOther), // lifetimes ('\'', identifier, not(peek('\''))).value(Token::SomeOther), + // comments + line_comment.value(Token::SomeOther), + block_comment.value(Token::SomeOther), // punctuations punctuation.value(Token::SomeOther), hash, @@ -787,6 +791,46 @@ impl<'a> Suffix<'a> { alt((open.map(Token::Open), close.map(Token::Close), some_other)).parse_next(i) } + fn line_comment<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { + let start = "//".parse_next(i)?; + let is_doc_comment = alt(( + ('/', not(peek('/'))).value(true), + '!'.value(true), + empty.value(false), + )) + .parse_next(i)?; + if opt(take_until(.., '\n')).parse_next(i)?.is_none() { + return Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!( + "you are probably missing a line break to end {}comment", + if is_doc_comment { "doc " } else { "" } + ), + start, + ))); + } + Ok(()) + } + + fn block_comment<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { + let start = "/*".parse_next(i)?; + let is_doc_comment = alt(( + ('*', not(peek('*'))).value(true), + '!'.value(true), + empty.value(false), + )) + .parse_next(i)?; + if opt(take_until(.., "*/")).parse_next(i)?.is_none() { + return Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!( + "missing `*/` to close block {}comment", + if is_doc_comment { "doc " } else { "" } + ), + start, + ))); + } + Ok(()) + } + fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { // diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index 65ecf506..08a26855 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -1441,3 +1441,70 @@ fn macro_calls_can_have_raw_prefixes() { )], ); } + +#[test] +fn macro_comments_in_macro_calls() { + // Related to issue . + let syntax = Syntax::default(); + + assert!(Ast::from_str("{{ e!(// hello) }}", None, &syntax).is_err()); + assert!(Ast::from_str("{{ e!(/// hello) }}", None, &syntax).is_err()); + assert!(Ast::from_str("{{ e!(// hello)\n }}", None, &syntax).is_err()); + assert!(Ast::from_str("{{ e!(/// hello)\n }}", None, &syntax).is_err()); + + assert_eq!( + Ast::from_str("{{ e!(// hello\n) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "// hello\n")), + )], + ); + assert_eq!( + Ast::from_str("{{ e!(/// hello\n) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "/// hello\n")), + )], + ); + assert_eq!( + Ast::from_str("{{ e!(//! hello\n) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "//! hello\n")), + )], + ); + + assert_eq!( + Ast::from_str("{{ e!(/* hello */) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "/* hello */")), + )], + ); + assert_eq!( + Ast::from_str("{{ e!(/** hello */) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "/** hello */")), + )], + ); + assert_eq!( + Ast::from_str("{{ e!(/*! hello */) }}", None, &syntax) + .unwrap() + .nodes, + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["e"], "/*! hello */")), + )], + ); +} diff --git a/fuzzing/fuzz/artifacts/derive/clusterfuzz-testcase-minimized-derive-6051930453639168 b/fuzzing/fuzz/artifacts/derive/clusterfuzz-testcase-minimized-derive-6051930453639168 new file mode 100644 index 00000000..394aec41 --- /dev/null +++ b/fuzzing/fuzz/artifacts/derive/clusterfuzz-testcase-minimized-derive-6051930453639168 @@ -0,0 +1 @@ +ÿÿÿ{{e!{//}}}ÿÿÿÿsÿ \ No newline at end of file diff --git a/testing/tests/ui/comments-in-macro-calls.rs b/testing/tests/ui/comments-in-macro-calls.rs new file mode 100644 index 00000000..864ab5ec --- /dev/null +++ b/testing/tests/ui/comments-in-macro-calls.rs @@ -0,0 +1,41 @@ +use askama::Template; + +// Regression test for . + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(// hello) }}")] +struct LineComment; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(// hello)\n}}")] +struct LineComment2; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(/* hello) }}")] +struct BlockComment; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(/// hello) }}")] +struct OuterLineDoc; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(/// hello)\n}}")] +struct OuterLineDoc2; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(/** hello) }}")] +struct OuterBlockDoc; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(//! hello) }}")] +struct InnerLineDoc; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(//! hello)\n}}")] +struct InnerLineDoc2; + +#[derive(Template)] +#[template(ext = "html", source = "{{ e!(/*! hello) }}")] +struct InnerBlockDoc; + +fn main() {} diff --git a/testing/tests/ui/comments-in-macro-calls.stderr b/testing/tests/ui/comments-in-macro-calls.stderr new file mode 100644 index 00000000..93dad5f5 --- /dev/null +++ b/testing/tests/ui/comments-in-macro-calls.stderr @@ -0,0 +1,71 @@ +error: you are probably missing a line break to end comment + --> :1:6 + "// hello) }}" + --> tests/ui/comments-in-macro-calls.rs:6:35 + | +6 | #[template(ext = "html", source = "{{ e!(// hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: expected `)` but found `}` + --> :1:15 + "}}" + --> tests/ui/comments-in-macro-calls.rs:10:35 + | +10 | #[template(ext = "html", source = "{{ e!(// hello)\n}}")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: missing `*/` to close block comment + --> :1:6 + "/* hello) }}" + --> tests/ui/comments-in-macro-calls.rs:14:35 + | +14 | #[template(ext = "html", source = "{{ e!(/* hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: you are probably missing a line break to end doc comment + --> :1:6 + "/// hello) }}" + --> tests/ui/comments-in-macro-calls.rs:18:35 + | +18 | #[template(ext = "html", source = "{{ e!(/// hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: expected `)` but found `}` + --> :1:16 + "}}" + --> tests/ui/comments-in-macro-calls.rs:22:35 + | +22 | #[template(ext = "html", source = "{{ e!(/// hello)\n}}")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: missing `*/` to close block doc comment + --> :1:6 + "/** hello) }}" + --> tests/ui/comments-in-macro-calls.rs:26:35 + | +26 | #[template(ext = "html", source = "{{ e!(/** hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: you are probably missing a line break to end doc comment + --> :1:6 + "//! hello) }}" + --> tests/ui/comments-in-macro-calls.rs:30:35 + | +30 | #[template(ext = "html", source = "{{ e!(//! hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: expected `)` but found `}` + --> :1:16 + "}}" + --> tests/ui/comments-in-macro-calls.rs:34:35 + | +34 | #[template(ext = "html", source = "{{ e!(//! hello)\n}}")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: missing `*/` to close block doc comment + --> :1:6 + "/*! hello) }}" + --> tests/ui/comments-in-macro-calls.rs:38:35 + | +38 | #[template(ext = "html", source = "{{ e!(/*! hello) }}")] + | ^^^^^^^^^^^^^^^^^^^^^