mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-27 04:50:40 +00:00
Merge pull request #476 from Kijewski/issue-475
parser: recognize/reject prefixed ids and lits in macro calls
This commit is contained in:
commit
c8acf8b270
@ -1199,8 +1199,7 @@ impl<'a> Generator<'a, '_> {
|
||||
// locals in the normalized manner
|
||||
let normalized_arg = normalize_identifier(arg);
|
||||
buf.write(format_args!(
|
||||
"let {} = {before}{value}{after};",
|
||||
normalized_arg
|
||||
"let {normalized_arg} = {before}{value}{after};"
|
||||
));
|
||||
this.locals
|
||||
.insert_with_default(Cow::Borrowed(normalized_arg));
|
||||
|
@ -9,8 +9,8 @@ use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use similar::{Algorithm, ChangeTag, TextDiffConfig};
|
||||
|
||||
use crate::AnyTemplateArgs;
|
||||
use crate::integration::Buffer;
|
||||
use crate::{AnyTemplateArgs, derive_template};
|
||||
|
||||
#[track_caller]
|
||||
fn build_template(ast: &syn::DeriveInput) -> Result<String, crate::CompileError> {
|
||||
@ -1186,7 +1186,7 @@ fn test_generated_with_error() {
|
||||
#[template(ext = "txt", source = "test {#")]
|
||||
struct HelloWorld;
|
||||
};
|
||||
let ts = crate::derive_template(ts, import_askama);
|
||||
let ts = derive_template(ts, import_askama);
|
||||
let _: syn::File = syn::parse2(ts).unwrap();
|
||||
}
|
||||
|
||||
@ -1221,7 +1221,7 @@ fn fuzzed_0b85() -> Result<(), syn::Error> {
|
||||
)]
|
||||
struct a {}
|
||||
};
|
||||
let output = crate::derive_template(input, import_askama);
|
||||
let output = derive_template(input, import_askama);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1235,7 +1235,7 @@ fn fuzzed_comparator_chain() -> Result<(), syn::Error> {
|
||||
)]
|
||||
enum fff {}
|
||||
};
|
||||
let output = crate::derive_template(input, import_askama);
|
||||
let output = derive_template(input, import_askama);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1293,8 +1293,43 @@ fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
||||
)]
|
||||
struct f {}
|
||||
};
|
||||
let output = crate::derive_template(input, import_askama);
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(output.to_string().contains("expected valid tokens in macro call"));
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_call_raw_prefix_without_data() -> Result<(), syn::Error> {
|
||||
// Regression test for <https://github.com/askama-rs/askama/issues/475>.
|
||||
// The parser must reject wrong usage of raw prefixes.
|
||||
let input = quote! {
|
||||
#[template(ext = "", source = "{{ z!{r#} }}")]
|
||||
enum q {}
|
||||
};
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(
|
||||
output
|
||||
.to_string()
|
||||
.contains("raw prefix `r#` is only allowed with raw identifiers and raw strings")
|
||||
);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_call_reserved_prefix() -> Result<(), syn::Error> {
|
||||
// The parser must reject reserved prefixes.
|
||||
let input = quote! {
|
||||
#[template(ext = "", source = "{{ z!{hello#world} }}")]
|
||||
enum q {}
|
||||
};
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(
|
||||
output
|
||||
.to_string()
|
||||
.contains("reserved prefix `hello#`, only `r#` is allowed")
|
||||
);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -773,8 +773,8 @@ impl<'a> Suffix<'a> {
|
||||
fn token<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||
// <https://doc.rust-lang.org/reference/tokens.html>
|
||||
let some_other = alt((
|
||||
// keywords + identifiers
|
||||
identifier.value(Token::SomeOther),
|
||||
// keywords + (raw) identifiers + raw strings
|
||||
identifier_or_prefixed_string,
|
||||
// literals
|
||||
Expr::char.value(Token::SomeOther),
|
||||
Expr::str.value(Token::SomeOther),
|
||||
@ -783,15 +783,76 @@ impl<'a> Suffix<'a> {
|
||||
('\'', identifier, not(peek('\''))).value(Token::SomeOther),
|
||||
// punctuations
|
||||
punctuation.value(Token::SomeOther),
|
||||
hash,
|
||||
));
|
||||
alt((open.map(Token::Open), close.map(Token::Close), some_other)).parse_next(i)
|
||||
}
|
||||
|
||||
fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||
let prefix = identifier.parse_next(i)?;
|
||||
if opt('#').parse_next(i)?.is_none() {
|
||||
// a simple identifier
|
||||
return Ok(Token::SomeOther);
|
||||
}
|
||||
|
||||
match prefix {
|
||||
// raw cstring or byte slice
|
||||
"cr" | "br" => {}
|
||||
// raw string string or identifier
|
||||
"r" => {
|
||||
if opt(identifier).parse_next(i)?.is_some() {
|
||||
return Ok(Token::SomeOther);
|
||||
}
|
||||
}
|
||||
// reserved prefix: reject
|
||||
_ => {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!(
|
||||
"reserved prefix `{}#`, only `r#` is allowed",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
prefix,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let hashes: usize = repeat(.., '#').parse_next(i)?;
|
||||
if opt('"').parse_next(i)?.is_none() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"raw prefix `r#` is only allowed with raw identifiers and raw strings",
|
||||
prefix,
|
||||
)));
|
||||
}
|
||||
|
||||
let Some((_, j)) = i.split_once(&format!("\"#{:#<hashes$}", "")) else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unterminated raw string",
|
||||
prefix,
|
||||
)));
|
||||
};
|
||||
*i = j;
|
||||
|
||||
Ok(Token::SomeOther)
|
||||
}
|
||||
|
||||
fn hash<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||
let start = *i;
|
||||
'#'.parse_next(i)?;
|
||||
if opt('"').parse_next(i)?.is_some() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unprefixed guarded string literals are reserved for future use",
|
||||
start,
|
||||
)));
|
||||
}
|
||||
Ok(Token::SomeOther)
|
||||
}
|
||||
|
||||
fn punctuation<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
||||
// <https://doc.rust-lang.org/reference/tokens.html#punctuation>
|
||||
// hash '#' omitted
|
||||
let one = one_of([
|
||||
'+', '-', '*', '/', '%', '^', '!', '&', '|', '=', '>', '<', '@', '_', '.', ',',
|
||||
';', ':', '#', '$', '?', '~',
|
||||
';', ':', '$', '?', '~',
|
||||
]);
|
||||
let two = alt((
|
||||
"&&", "||", "<<", ">>", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "==", "!=",
|
||||
|
@ -1417,3 +1417,19 @@ fn comparison_operators_cannot_be_chained() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_calls_can_have_raw_prefixes() {
|
||||
// Related to issue <https://github.com/askama-rs/askama/issues/475>.
|
||||
let syntax = Syntax::default();
|
||||
let inner = r####"r#"test"# r##"test"## r###"test"### r#loop"####;
|
||||
assert_eq!(
|
||||
Ast::from_str(&format!("{{{{ z!{{{inner}}} }}}}"), None, &syntax)
|
||||
.unwrap()
|
||||
.nodes,
|
||||
vec![Node::Expr(
|
||||
Ws(None, None),
|
||||
WithSpan::no_span(Expr::RustMacro(vec!["z"], inner)),
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
˙˙˙{{z!{r#}}} ˙q˙ř
|
Loading…
x
Reference in New Issue
Block a user