mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-27 04:50:40 +00:00
Merge pull request #479 from Kijewski/issue-425
parser: reject illegal string literals
This commit is contained in:
commit
9309acaa01
@ -394,6 +394,7 @@ fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a
|
||||
Expr::StrLit(StrLit {
|
||||
prefix: None,
|
||||
content,
|
||||
..
|
||||
}) => {
|
||||
if content.find('\\').is_none() {
|
||||
// if the literal does not contain any backslashes, then it does not need unescaping
|
||||
|
@ -275,11 +275,19 @@ impl<'a> Generator<'a, '_> {
|
||||
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
||||
prefix: None,
|
||||
content: "",
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
}));
|
||||
const PLURAL: &WithSpan<'static, Expr<'static>> =
|
||||
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
||||
prefix: None,
|
||||
content: "s",
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
}));
|
||||
const ARGUMENTS: &[&FilterArgument; 3] = &[
|
||||
FILTER_SOURCE,
|
||||
@ -494,7 +502,10 @@ impl<'a> Generator<'a, '_> {
|
||||
|
||||
let [source, opt_escaper] = collect_filter_args(ctx, "escape", node, args, ARGUMENTS)?;
|
||||
let opt_escaper = if !is_argument_placeholder(opt_escaper) {
|
||||
let Expr::StrLit(StrLit { prefix, content }) = **opt_escaper else {
|
||||
let Expr::StrLit(StrLit {
|
||||
prefix, content, ..
|
||||
}) = **opt_escaper
|
||||
else {
|
||||
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
||||
};
|
||||
if let Some(prefix) = prefix {
|
||||
|
@ -1278,12 +1278,12 @@ fn test_macro_names_that_need_escaping() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/6565>
|
||||
fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
||||
// Regression test for fuzzed error <https://github.com/askama-rs/askama/issues/459>.
|
||||
// Macro calls can contains any valid tokens, but only valid tokens.
|
||||
// Invalid tokens will be rejected by rust, so we must not emit them.
|
||||
|
||||
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/5489>
|
||||
let input = quote! {
|
||||
#[template(
|
||||
ext = "",
|
||||
@ -1294,7 +1294,11 @@ fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
||||
struct f {}
|
||||
};
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(output.to_string().contains("expected valid tokens in macro call"));
|
||||
assert!(
|
||||
output
|
||||
.to_string()
|
||||
.contains("expected valid tokens in macro call")
|
||||
);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1311,7 +1315,7 @@ fn test_macro_call_raw_prefix_without_data() -> Result<(), syn::Error> {
|
||||
assert!(
|
||||
output
|
||||
.to_string()
|
||||
.contains("raw prefix `r#` is only allowed with raw identifiers and raw strings")
|
||||
.contains("prefix `r#` is only allowed with raw identifiers and raw strings")
|
||||
);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
@ -1325,10 +1329,27 @@ fn test_macro_call_reserved_prefix() -> Result<(), syn::Error> {
|
||||
enum q {}
|
||||
};
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(output.to_string().contains("reserved prefix `hello#`"));
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_call_valid_raw_cstring() -> Result<(), syn::Error> {
|
||||
// Regression test for <https://github.com/askama-rs/askama/issues/478>.
|
||||
// CString literals must not contain NULs.
|
||||
|
||||
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/5489>
|
||||
let input = quote! {
|
||||
#[template(ext = "", source = "{{ c\"\0\" }}")]
|
||||
// ^^ NUL is not allowed in cstring literals
|
||||
enum l {}
|
||||
};
|
||||
let output = derive_template(input, import_askama);
|
||||
assert!(
|
||||
output
|
||||
.to_string()
|
||||
.contains("reserved prefix `hello#`, only `r#` is allowed")
|
||||
.contains("null characters in C string literals are not supported")
|
||||
);
|
||||
let _: syn::File = syn::parse2(output)?;
|
||||
Ok(())
|
||||
|
@ -7,13 +7,12 @@ use winnow::combinator::{
|
||||
alt, cut_err, fail, not, opt, peek, preceded, repeat, separated, terminated,
|
||||
};
|
||||
use winnow::error::ParserError as _;
|
||||
use winnow::token::one_of;
|
||||
|
||||
use crate::node::CondTest;
|
||||
use crate::{
|
||||
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
|
||||
WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0,
|
||||
skip_ws1, str_lit, ws,
|
||||
StrPrefix, WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier,
|
||||
skip_ws0, skip_ws1, str_lit, ws,
|
||||
};
|
||||
|
||||
macro_rules! expr_prec_layer {
|
||||
@ -773,12 +772,12 @@ 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 + (raw) identifiers + raw strings
|
||||
identifier_or_prefixed_string,
|
||||
// literals
|
||||
Expr::char.value(Token::SomeOther),
|
||||
Expr::str.value(Token::SomeOther),
|
||||
Expr::num.value(Token::SomeOther),
|
||||
// keywords + (raw) identifiers + raw strings
|
||||
identifier_or_prefixed_string.value(Token::SomeOther),
|
||||
// lifetimes
|
||||
('\'', identifier, not(peek('\''))).value(Token::SomeOther),
|
||||
// punctuations
|
||||
@ -788,51 +787,96 @@ impl<'a> Suffix<'a> {
|
||||
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> {
|
||||
fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
||||
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str-raw.syntax>
|
||||
|
||||
let prefix = identifier.parse_next(i)?;
|
||||
if opt('#').parse_next(i)?.is_none() {
|
||||
// a simple identifier
|
||||
return Ok(Token::SomeOther);
|
||||
let hashes: usize = repeat(.., '#').parse_next(i)?;
|
||||
if hashes >= 256 {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"a maximum of 255 hashes `#` are allowed with raw strings",
|
||||
prefix,
|
||||
)));
|
||||
}
|
||||
|
||||
match prefix {
|
||||
let str_kind = match prefix {
|
||||
// raw cstring or byte slice
|
||||
"cr" | "br" => {}
|
||||
"br" => Some(StrPrefix::Binary),
|
||||
"cr" => Some(StrPrefix::CLike),
|
||||
// raw string string or identifier
|
||||
"r" => {
|
||||
if opt(identifier).parse_next(i)?.is_some() {
|
||||
return Ok(Token::SomeOther);
|
||||
}
|
||||
}
|
||||
"r" => None,
|
||||
// a simple identifier
|
||||
_ if hashes == 0 => return Ok(()),
|
||||
// reserved prefix: reject
|
||||
_ => {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!(
|
||||
"reserved prefix `{}#`, only `r#` is allowed",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
format!("reserved prefix `{}#`", 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)
|
||||
if opt('"').parse_next(i)?.is_some() {
|
||||
// got a raw string
|
||||
|
||||
let Some((inner, j)) = i.split_once(&format!("\"{:#<hashes$}", "")) else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unterminated raw string",
|
||||
prefix,
|
||||
)));
|
||||
};
|
||||
*i = j;
|
||||
|
||||
let msg = match str_kind {
|
||||
Some(StrPrefix::Binary) => inner
|
||||
.bytes()
|
||||
.any(|b| !b.is_ascii())
|
||||
.then_some("binary string literals must not contain non-ASCII characters"),
|
||||
Some(StrPrefix::CLike) => inner
|
||||
.bytes()
|
||||
.any(|b| b == 0)
|
||||
.then_some("cstring literals must not contain NUL characters"),
|
||||
None => None,
|
||||
};
|
||||
if let Some(msg) = msg {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, prefix)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else if hashes == 0 {
|
||||
// a simple identifier
|
||||
Ok(())
|
||||
} else if opt(identifier).parse_next(i)?.is_some() {
|
||||
// got a raw identifier
|
||||
|
||||
if str_kind.is_some() {
|
||||
// an invalid raw identifier like `cr#async`
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!(
|
||||
"reserved prefix `{}#`, only `r#` is allowed with raw identifiers",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
prefix,
|
||||
)))
|
||||
} else if hashes > 1 {
|
||||
// an invalid raw identifier like `r##async`
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"only one `#` is allowed in raw identifier delimitation",
|
||||
prefix,
|
||||
)))
|
||||
} else {
|
||||
// a raw identifier like `r#async`
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!(
|
||||
"prefix `{}#` is only allowed with raw identifiers and raw strings",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
prefix,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn hash<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||
@ -850,16 +894,36 @@ impl<'a> Suffix<'a> {
|
||||
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((
|
||||
|
||||
const ONE_CHAR: &[u8] = b"+-*/%^!&|=><@_.,;:$?~";
|
||||
const TWO_CHARS: &[&str] = &[
|
||||
"&&", "||", "<<", ">>", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "==", "!=",
|
||||
">=", "<=", "..", "::", "->", "=>", "<-",
|
||||
));
|
||||
let three = alt(("<<=", ">>=", "...", "..="));
|
||||
alt((three.value(()), two.value(()), one.value(()))).parse_next(i)
|
||||
];
|
||||
const THREE_CHARS: &[&str] = &["<<=", ">>=", "...", "..="];
|
||||
|
||||
// need to check long to short
|
||||
if let Some((head, tail)) = i.split_at_checked(3) {
|
||||
if THREE_CHARS.contains(&head) {
|
||||
*i = tail;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some((head, tail)) = i.split_at_checked(2) {
|
||||
if TWO_CHARS.contains(&head) {
|
||||
*i = tail;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some((head, tail)) = i.split_at_checked(1) {
|
||||
if let [head] = head.as_bytes() {
|
||||
if ONE_CHAR.contains(head) {
|
||||
*i = tail;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
fail(i)
|
||||
}
|
||||
|
||||
fn open<'a>(i: &mut &'a str) -> ParseResult<'a, Group> {
|
||||
|
@ -19,10 +19,12 @@ use std::sync::Arc;
|
||||
use std::{fmt, str};
|
||||
|
||||
use winnow::ascii::take_escaped;
|
||||
use winnow::combinator::{alt, cut_err, delimited, fail, not, opt, peek, preceded, repeat};
|
||||
use winnow::combinator::{
|
||||
alt, cut_err, delimited, fail, not, opt, peek, preceded, repeat, terminated,
|
||||
};
|
||||
use winnow::error::FromExternalError;
|
||||
use winnow::stream::Stream as _;
|
||||
use winnow::token::{any, one_of, take_till, take_while};
|
||||
use winnow::stream::{AsChar, Stream as _};
|
||||
use winnow::token::{any, none_of, one_of, take_till, take_while};
|
||||
use winnow::{ModalParser, Parser};
|
||||
|
||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||
@ -544,28 +546,166 @@ impl fmt::Display for StrPrefix {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct StrLit<'a> {
|
||||
pub prefix: Option<StrPrefix>,
|
||||
/// the unparsed (but validated) content
|
||||
pub content: &'a str,
|
||||
}
|
||||
|
||||
fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||
let s = delimited(
|
||||
'"',
|
||||
opt(take_escaped(take_till(1.., ['\\', '"']), '\\', any)),
|
||||
'"',
|
||||
)
|
||||
.parse_next(i)?;
|
||||
Ok(s.unwrap_or_default())
|
||||
/// whether the string literal is unprefixed, a cstring or binary slice
|
||||
pub prefix: Option<StrPrefix>,
|
||||
/// contains a NUL character, either escaped `'\0'` or the very characters;
|
||||
/// not allowed in cstring literals
|
||||
pub contains_null: bool,
|
||||
/// contains a non-ASCII character, either as `\u{123456}` or as an unescaped character;
|
||||
/// not allowed in binary slices
|
||||
pub contains_unicode_character: bool,
|
||||
/// contains unicode escape sequences like `\u{12}` (regardless of its range);
|
||||
/// not allowed in binary slices
|
||||
pub contains_unicode_escape: bool,
|
||||
/// contains a non-ASCII range escape sequence like `\x80`;
|
||||
/// not allowed in unprefix strings
|
||||
pub contains_high_ascii: bool,
|
||||
}
|
||||
|
||||
fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
let (prefix, content) = (opt(alt(('b', 'c'))), str_lit_without_prefix).parse_next(i)?;
|
||||
let prefix = match prefix {
|
||||
Some('b') => Some(StrPrefix::Binary),
|
||||
Some('c') => Some(StrPrefix::CLike),
|
||||
_ => None,
|
||||
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str.syntax>
|
||||
|
||||
fn inner<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Sequence<'a> {
|
||||
Text(&'a str),
|
||||
Close,
|
||||
Escape,
|
||||
}
|
||||
|
||||
let start = *i;
|
||||
let mut contains_null = false;
|
||||
let mut contains_unicode_character = false;
|
||||
let mut contains_unicode_escape = false;
|
||||
let mut contains_high_ascii = false;
|
||||
|
||||
while !i.is_empty() {
|
||||
let seq = alt((
|
||||
repeat::<_, _, (), _, _>(1.., none_of(['\\', '"']))
|
||||
.take()
|
||||
.map(Sequence::Text),
|
||||
'\\'.value(Sequence::Escape),
|
||||
peek('"').value(Sequence::Close),
|
||||
))
|
||||
.parse_next(i)?;
|
||||
|
||||
match seq {
|
||||
Sequence::Text(s) => {
|
||||
contains_unicode_character =
|
||||
contains_unicode_character || s.bytes().any(|c: u8| !c.is_ascii());
|
||||
contains_null = contains_null || s.bytes().any(|c: u8| c == 0);
|
||||
continue;
|
||||
}
|
||||
Sequence::Close => break,
|
||||
Sequence::Escape => {}
|
||||
}
|
||||
|
||||
match any.parse_next(i)? {
|
||||
'\'' | '"' | 'n' | 'r' | 't' | '\\' => continue,
|
||||
'0' => {
|
||||
contains_null = true;
|
||||
continue;
|
||||
}
|
||||
'x' => {
|
||||
let code = take_while(2, AsChar::is_hex_digit).parse_next(i)?;
|
||||
match u8::from_str_radix(code, 16).unwrap() {
|
||||
0 => contains_null = true,
|
||||
128.. => contains_high_ascii = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
'u' => {
|
||||
contains_unicode_escape = true;
|
||||
let code = delimited('{', take_while(1..6, AsChar::is_hex_digit), '}')
|
||||
.parse_next(i)?;
|
||||
match u32::from_str_radix(code, 16).unwrap() {
|
||||
0 => contains_null = true,
|
||||
0xd800..0xe000 => {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unicode escape must not be a surrogate",
|
||||
start,
|
||||
)));
|
||||
}
|
||||
128.. => contains_unicode_character = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => return fail(i),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StrLit {
|
||||
content: "",
|
||||
prefix: None,
|
||||
contains_null,
|
||||
contains_unicode_character,
|
||||
contains_unicode_escape,
|
||||
contains_high_ascii,
|
||||
})
|
||||
}
|
||||
|
||||
let start = *i;
|
||||
|
||||
let prefix = terminated(
|
||||
opt(alt((
|
||||
'b'.value(StrPrefix::Binary),
|
||||
'c'.value(StrPrefix::CLike),
|
||||
))),
|
||||
'"',
|
||||
)
|
||||
.parse_next(i)?;
|
||||
|
||||
let lit = opt(terminated(inner.with_taken(), '"')).parse_next(i)?;
|
||||
let Some((mut lit, content)) = lit else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unclosed or broken string",
|
||||
start,
|
||||
)));
|
||||
};
|
||||
Ok(StrLit { prefix, content })
|
||||
lit.content = content;
|
||||
lit.prefix = prefix;
|
||||
|
||||
let msg = match prefix {
|
||||
Some(StrPrefix::Binary) => {
|
||||
if lit.contains_unicode_character {
|
||||
Some("non-ASCII character in byte string literal")
|
||||
} else if lit.contains_unicode_escape {
|
||||
Some("unicode escape in byte string")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Some(StrPrefix::CLike) => lit
|
||||
.contains_null
|
||||
.then_some("null characters in C string literals are not supported"),
|
||||
None => lit.contains_high_ascii.then_some("out of range hex escape"),
|
||||
};
|
||||
if let Some(msg) = msg {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, start)));
|
||||
}
|
||||
|
||||
Ok(lit)
|
||||
}
|
||||
|
||||
fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||
let start = *i;
|
||||
let lit = str_lit.parse_next(i)?;
|
||||
|
||||
let kind = match lit.prefix {
|
||||
Some(StrPrefix::Binary) => Some("binary slice"),
|
||||
Some(StrPrefix::CLike) => Some("cstring"),
|
||||
None => None,
|
||||
};
|
||||
if let Some(kind) = kind {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("expected an unprefixed normal string, not a {kind}"),
|
||||
start,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(lit.content)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -1478,7 +1618,11 @@ mod test {
|
||||
"",
|
||||
StrLit {
|
||||
prefix: Some(StrPrefix::Binary),
|
||||
content: "hello"
|
||||
content: "hello",
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -1488,7 +1632,11 @@ mod test {
|
||||
"",
|
||||
StrLit {
|
||||
prefix: Some(StrPrefix::CLike),
|
||||
content: "hello"
|
||||
content: "hello",
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -237,6 +237,10 @@ fn test_parse_var_call() {
|
||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||
content: "123",
|
||||
prefix: None,
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
})),
|
||||
WithSpan::no_span(int_lit("3"))
|
||||
],
|
||||
@ -283,6 +287,10 @@ fn test_parse_path_call() {
|
||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||
content: "123",
|
||||
prefix: None,
|
||||
contains_null: false,
|
||||
contains_unicode_character: false,
|
||||
contains_unicode_escape: false,
|
||||
contains_high_ascii: false,
|
||||
})),
|
||||
WithSpan::no_span(int_lit("3"))
|
||||
],
|
||||
|
Binary file not shown.
1
testing/templates/macro-call-raw-string-many-hashes.html
Normal file
1
testing/templates/macro-call-raw-string-many-hashes.html
Normal file
@ -0,0 +1 @@
|
||||
{{ z!(hello################################################################################################################################################################################################################################################################world) }}
|
@ -42,7 +42,7 @@ struct ThreeTimesOk2 {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
ext = "",
|
||||
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
)]
|
||||
struct Regression {}
|
||||
|
||||
|
@ -32,8 +32,8 @@ error: comparison operators cannot be chained; consider using explicit parenthes
|
||||
|
||||
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ < _) < _`
|
||||
--> <source attribute>:1:52
|
||||
"<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
"<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
--> tests/ui/comparator-chaining.rs:45:14
|
||||
|
|
||||
45 | source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
45 | source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
105
testing/tests/ui/illegal-string-literals.rs
Normal file
105
testing/tests/ui/illegal-string-literals.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use askama::Template;
|
||||
|
||||
// Strings must be closed
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello world }}"#)]
|
||||
struct Unclosed1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello world\" }}"#)]
|
||||
struct Unclosed2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ b"hello world }}"#)]
|
||||
struct Unclosed3;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ b"hello world\" }}"#)]
|
||||
struct Unclosed4;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ c"hello world }}"#)]
|
||||
struct Unclosed5;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ c"hello world\" }}"#)]
|
||||
struct Unclosed6;
|
||||
|
||||
// Unprefix string literals must not contain hex escapes sequences higher than `\x7f`
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \x80 world" }}"#)]
|
||||
struct HighAscii1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \xff world" }}"#)]
|
||||
struct HighAscii2;
|
||||
|
||||
// Cstring literals must not contain null characters
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \0 world" }}"#)]
|
||||
struct NulEscapeSequence1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \x00 world" }}"#)]
|
||||
struct NulEscapeSequence2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \u{0} world" }}"#)]
|
||||
struct NulEscapeSequence3;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = "{{ \"hello \0 world\" }}")]
|
||||
struct NulCharacter;
|
||||
|
||||
// Binary slice literals must not contain non-ASCII characters
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello 😉 world" }}"#)]
|
||||
struct UnicodeCharacter;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \u{128521} world" }}"#)]
|
||||
struct UnicodeEscapeSequence;
|
||||
|
||||
// Surrogate characters (even if paired) are not allowed at all
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \u{d83d}\u{de09} world" }}"#)]
|
||||
struct SurrogatePaired1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \u{d83d} world" }}"#)]
|
||||
struct SurrogateLow1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ "hello \u{de09} world" }}"#)]
|
||||
struct SurrogateHigh1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ b"hello \u{d83d}\u{de09} world" }}"#)]
|
||||
struct SurrogatePaired2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ b"hello \u{d83d} world" }}"#)]
|
||||
struct SurrogateLow2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ b"hello \u{de09} world" }}"#)]
|
||||
struct SurrogateHigh2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ c"hello \u{d83d}\u{de09} world" }}"#)]
|
||||
struct SurrogatePaired3;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ c"hello \u{d83d} world" }}"#)]
|
||||
struct SurrogateLow3;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
|
||||
struct SurrogateHigh3;
|
||||
|
||||
fn main() {}
|
143
testing/tests/ui/illegal-string-literals.stderr
Normal file
143
testing/tests/ui/illegal-string-literals.stderr
Normal file
@ -0,0 +1,143 @@
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"\"hello world }}"
|
||||
--> tests/ui/illegal-string-literals.rs:6:34
|
||||
|
|
||||
6 | #[template(ext = "txt", source = r#"{{ "hello world }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"\"hello world\\\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:10:34
|
||||
|
|
||||
10 | #[template(ext = "txt", source = r#"{{ "hello world\" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"b\"hello world }}"
|
||||
--> tests/ui/illegal-string-literals.rs:14:34
|
||||
|
|
||||
14 | #[template(ext = "txt", source = r#"{{ b"hello world }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"b\"hello world\\\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:18:34
|
||||
|
|
||||
18 | #[template(ext = "txt", source = r#"{{ b"hello world\" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"c\"hello world }}"
|
||||
--> tests/ui/illegal-string-literals.rs:22:34
|
||||
|
|
||||
22 | #[template(ext = "txt", source = r#"{{ c"hello world }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"c\"hello world\\\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:26:34
|
||||
|
|
||||
26 | #[template(ext = "txt", source = r#"{{ c"hello world\" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: out of range hex escape
|
||||
--> <source attribute>:1:3
|
||||
"\"hello \\x80 world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:32:34
|
||||
|
|
||||
32 | #[template(ext = "txt", source = r#"{{ "hello \x80 world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: out of range hex escape
|
||||
--> <source attribute>:1:3
|
||||
"\"hello \\xff world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:36:34
|
||||
|
|
||||
36 | #[template(ext = "txt", source = r#"{{ "hello \xff world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unclosed or broken string
|
||||
--> <source attribute>:1:3
|
||||
"\"hello \\u{128521} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:64:34
|
||||
|
|
||||
64 | #[template(ext = "txt", source = r#"{{ "hello \u{128521} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:4
|
||||
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:70:34
|
||||
|
|
||||
70 | #[template(ext = "txt", source = r#"{{ "hello \u{d83d}\u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:4
|
||||
"hello \\u{d83d} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:74:34
|
||||
|
|
||||
74 | #[template(ext = "txt", source = r#"{{ "hello \u{d83d} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:4
|
||||
"hello \\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:78:34
|
||||
|
|
||||
78 | #[template(ext = "txt", source = r#"{{ "hello \u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:82:34
|
||||
|
|
||||
82 | #[template(ext = "txt", source = r#"{{ b"hello \u{d83d}\u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{d83d} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:86:34
|
||||
|
|
||||
86 | #[template(ext = "txt", source = r#"{{ b"hello \u{d83d} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:90:34
|
||||
|
|
||||
90 | #[template(ext = "txt", source = r#"{{ b"hello \u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:94:34
|
||||
|
|
||||
94 | #[template(ext = "txt", source = r#"{{ c"hello \u{d83d}\u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{d83d} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:98:34
|
||||
|
|
||||
98 | #[template(ext = "txt", source = r#"{{ c"hello \u{d83d} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unicode escape must not be a surrogate
|
||||
--> <source attribute>:1:5
|
||||
"hello \\u{de09} world\" }}"
|
||||
--> tests/ui/illegal-string-literals.rs:102:34
|
||||
|
|
||||
102 | #[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
108
testing/tests/ui/raw-prefix.rs
Normal file
108
testing/tests/ui/raw-prefix.rs
Normal file
@ -0,0 +1,108 @@
|
||||
// Regression test for <https://github.com/askama-rs/askama/issues/478>.
|
||||
|
||||
use askama::Template;
|
||||
|
||||
// Cstring literals must not contain NULs.
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(cr#\"\0\"#) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawCstring1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(cr##\"\0\"##) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawCstring2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(cr###\"\0\"###) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawCstring3;
|
||||
|
||||
// Binary string literals must not contain NULs.
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(br#\"😶🌫️\"#) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawBinaryString1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(br##\"😶🌫️\"##) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawBinaryString2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(br###\"😶🌫️\"###) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallRawBinaryString3;
|
||||
|
||||
// Only `r#` is allowed as prefix idenfiers
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(br#async) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallIllegalPrefix1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(cr#async) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallIllegalPrefix2;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(r##async) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallIllegalPrefix3;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(br##async) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallIllegalPrefix4;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(cr##async) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallIllegalPrefix5;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(hello#world) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallReservedPrefix1;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{{ z!(hello##world) }}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct MacroCallReservedPrefix2;
|
||||
|
||||
// No more than 255 hashes
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "macro-call-raw-string-many-hashes.html")]
|
||||
struct MacroCallManyHashes;
|
||||
|
||||
fn main() {}
|
111
testing/tests/ui/raw-prefix.stderr
Normal file
111
testing/tests/ui/raw-prefix.stderr
Normal file
@ -0,0 +1,111 @@
|
||||
error: cstring literals must not contain NUL characters
|
||||
--> <source attribute>:1:6
|
||||
"cr#\"\0\"#) }}"
|
||||
--> tests/ui/raw-prefix.rs:9:14
|
||||
|
|
||||
9 | source = "{{ z!(cr#\"\0\"#) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: cstring literals must not contain NUL characters
|
||||
--> <source attribute>:1:6
|
||||
"cr##\"\0\"##) }}"
|
||||
--> tests/ui/raw-prefix.rs:16:14
|
||||
|
|
||||
16 | source = "{{ z!(cr##\"\0\"##) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: cstring literals must not contain NUL characters
|
||||
--> <source attribute>:1:6
|
||||
"cr###\"\0\"###) }}"
|
||||
--> tests/ui/raw-prefix.rs:23:14
|
||||
|
|
||||
23 | source = "{{ z!(cr###\"\0\"###) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: binary string literals must not contain non-ASCII characters
|
||||
--> <source attribute>:1:6
|
||||
"br#\"😶\u{200d}🌫\u{fe0f}\"#) }}"
|
||||
--> tests/ui/raw-prefix.rs:32:14
|
||||
|
|
||||
32 | source = "{{ z!(br#\"😶🌫️\"#) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: binary string literals must not contain non-ASCII characters
|
||||
--> <source attribute>:1:6
|
||||
"br##\"😶\u{200d}🌫\u{fe0f}\"##) }}"
|
||||
--> tests/ui/raw-prefix.rs:39:14
|
||||
|
|
||||
39 | source = "{{ z!(br##\"😶🌫️\"##) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: binary string literals must not contain non-ASCII characters
|
||||
--> <source attribute>:1:6
|
||||
"br###\"😶\u{200d}🌫\u{fe0f}\"###) }}"
|
||||
--> tests/ui/raw-prefix.rs:46:14
|
||||
|
|
||||
46 | source = "{{ z!(br###\"😶🌫️\"###) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `br#`, only `r#` is allowed with raw identifiers
|
||||
--> <source attribute>:1:6
|
||||
"br#async) }}"
|
||||
--> tests/ui/raw-prefix.rs:55:14
|
||||
|
|
||||
55 | source = "{{ z!(br#async) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `cr#`, only `r#` is allowed with raw identifiers
|
||||
--> <source attribute>:1:6
|
||||
"cr#async) }}"
|
||||
--> tests/ui/raw-prefix.rs:62:14
|
||||
|
|
||||
62 | source = "{{ z!(cr#async) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: only one `#` is allowed in raw identifier delimitation
|
||||
--> <source attribute>:1:6
|
||||
"r##async) }}"
|
||||
--> tests/ui/raw-prefix.rs:69:14
|
||||
|
|
||||
69 | source = "{{ z!(r##async) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `br#`, only `r#` is allowed with raw identifiers
|
||||
--> <source attribute>:1:6
|
||||
"br##async) }}"
|
||||
--> tests/ui/raw-prefix.rs:76:14
|
||||
|
|
||||
76 | source = "{{ z!(br##async) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `cr#`, only `r#` is allowed with raw identifiers
|
||||
--> <source attribute>:1:6
|
||||
"cr##async) }}"
|
||||
--> tests/ui/raw-prefix.rs:83:14
|
||||
|
|
||||
83 | source = "{{ z!(cr##async) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `hello#`
|
||||
--> <source attribute>:1:6
|
||||
"hello#world) }}"
|
||||
--> tests/ui/raw-prefix.rs:90:14
|
||||
|
|
||||
90 | source = "{{ z!(hello#world) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: reserved prefix `hello#`
|
||||
--> <source attribute>:1:6
|
||||
"hello##world) }}"
|
||||
--> tests/ui/raw-prefix.rs:97:14
|
||||
|
|
||||
97 | source = "{{ z!(hello##world) }}",
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: a maximum of 255 hashes `#` are allowed with raw strings
|
||||
--> testing/templates/macro-call-raw-string-many-hashes.html:1:6
|
||||
"hello###########################################################################"...
|
||||
--> tests/ui/raw-prefix.rs:105:19
|
||||
|
|
||||
105 | #[template(path = "macro-call-raw-string-many-hashes.html")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Loading…
x
Reference in New Issue
Block a user