diff --git a/rinja_parser/src/expr.rs b/rinja_parser/src/expr.rs index 2dd1b257..ced11f36 100644 --- a/rinja_parser/src/expr.rs +++ b/rinja_parser/src/expr.rs @@ -4,7 +4,7 @@ use std::str; use nom::branch::alt; use nom::bytes::complete::{tag, take_till}; use nom::character::complete::char; -use nom::combinator::{cut, map, not, opt, peek, recognize, value}; +use nom::combinator::{cut, fail, map, not, opt, peek, recognize, value}; use nom::error::ErrorKind; use nom::error_position; use nom::multi::{fold_many0, many0, separated_list0}; @@ -127,7 +127,7 @@ impl<'a> Expr<'a> { if !is_template_macro { // If this is not a template macro, we don't want to parse named arguments so // we instead return an error which will allow to continue the parsing. - return Err(nom::Err::Error(error_position!(i, ErrorKind::Alt))); + return fail(i); } let (_, level) = level.nest(i)?; @@ -486,10 +486,7 @@ impl<'a> Suffix<'a> { if nested == 0 { Ok((&input[last..], ())) } else { - Err(nom::Err::Error(error_position!( - input, - ErrorKind::SeparatedNonEmptyList - ))) + fail(input) } } diff --git a/rinja_parser/src/lib.rs b/rinja_parser/src/lib.rs index a4534b97..2fbc20f8 100644 --- a/rinja_parser/src/lib.rs +++ b/rinja_parser/src/lib.rs @@ -12,11 +12,11 @@ use std::{fmt, str}; use nom::branch::alt; use nom::bytes::complete::{escaped, is_not, tag, take_till, take_while_m_n}; use nom::character::complete::{anychar, char, one_of, satisfy}; -use nom::combinator::{complete, cut, eof, map, not, opt, recognize}; -use nom::error::{Error, ErrorKind, FromExternalError}; +use nom::combinator::{complete, cut, eof, fail, map, not, opt, recognize}; +use nom::error::{ErrorKind, FromExternalError}; use nom::multi::{many0_count, many1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use nom::{error_position, AsChar, InputTakeAtPosition}; +use nom::{AsChar, InputTakeAtPosition}; pub mod expr; pub use expr::{Expr, Filter}; @@ -240,20 +240,6 @@ impl<'a> ErrorContext<'a> { message: Some(message.into()), } } - - pub(crate) fn from_err(error: nom::Err>) -> nom::Err { - match error { - nom::Err::Incomplete(i) => nom::Err::Incomplete(i), - nom::Err::Failure(Error { input, .. }) => nom::Err::Failure(Self { - input, - message: None, - }), - nom::Err::Error(Error { input, .. }) => nom::Err::Error(Self { - input, - message: None, - }), - } - } } impl<'a> nom::error::ParseError<&'a str> for ErrorContext<'a> { @@ -323,11 +309,7 @@ fn skip_till<'a, 'b, O>( fn keyword<'a>(k: &'a str) -> impl FnMut(&'a str) -> ParseResult<'_> { move |i: &'a str| -> ParseResult<'a> { let (j, v) = identifier(i)?; - if k == v { - Ok((j, v)) - } else { - Err(nom::Err::Error(error_position!(i, ErrorKind::Tag))) - } + if k == v { Ok((j, v)) } else { fail(i) } } } @@ -366,7 +348,7 @@ fn num_lit(i: &str) -> ParseResult<'_> { Ok((i, suffix)) } else if ignore.contains(&suffix) { // no need for a message, this case only occures in an `opt(…)` - Err(nom::Err::Error(ErrorContext::new("", i))) + fail(i) } else { Err(nom::Err::Failure(ErrorContext::new( format!("unknown {kind} suffix `{suffix}`"), diff --git a/rinja_parser/src/node.rs b/rinja_parser/src/node.rs index 5a822bb2..ef16a8b4 100644 --- a/rinja_parser/src/node.rs +++ b/rinja_parser/src/node.rs @@ -3,9 +3,7 @@ use std::str; use nom::branch::alt; use nom::bytes::complete::{tag, take_till}; use nom::character::complete::char; -use nom::combinator::{complete, consumed, cut, eof, map, not, opt, peek, recognize, value}; -use nom::error::ErrorKind; -use nom::error_position; +use nom::combinator::{complete, consumed, cut, eof, fail, map, not, opt, peek, recognize, value}; use nom::multi::{many0, many1, separated_list0}; use nom::sequence::{delimited, pair, preceded, tuple}; @@ -79,12 +77,7 @@ impl<'a> Node<'a> { "break" => |i, s| Self::r#break(i, s), "continue" => |i, s| Self::r#continue(i, s), "filter" => |i, s| wrap(Self::FilterBlock, FilterBlock::parse(i, s)), - _ => { - return Err(ErrorContext::from_err(nom::Err::Error(error_position!( - i, - ErrorKind::Tag - )))); - } + _ => return fail(i), }; let (i, node) = s.nest(j, |i| func(i, s))?; @@ -377,7 +370,7 @@ impl<'a> Loop<'a> { |i| s.tag_block_start(i), opt(Whitespace::parse), opt(else_block), - ws(keyword("endfor")), + end_node("for", "endfor"), opt(Whitespace::parse), ))), ))), @@ -449,7 +442,7 @@ impl<'a> Macro<'a> { cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("endmacro")), + end_node("macro", "endmacro"), cut(preceded( opt(|before| { let (after, end_name) = ws(identifier)(before)?; @@ -525,7 +518,7 @@ impl<'a> FilterBlock<'a> { cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("endfilter")), + end_node("filter", "endfilter"), opt(Whitespace::parse), ))), ))); @@ -645,7 +638,7 @@ impl<'a> Match<'a> { cut(tuple(( ws(|i| s.tag_block_start(i)), opt(Whitespace::parse), - ws(keyword("endmatch")), + end_node("match", "endmatch"), opt(Whitespace::parse), ))), ))), @@ -699,7 +692,7 @@ impl<'a> BlockDef<'a> { cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("endblock")), + end_node("block", "endblock"), cut(tuple(( opt(|before| { let (after, end_name) = ws(identifier)(before)?; @@ -773,7 +766,7 @@ impl<'a> Lit<'a> { let (i, content) = match content { Some("") => { // {block,comment,expr}_start follows immediately. - return Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil))); + return fail(i); } Some(content) => (i, content), None => ("", i), // there is no {block,comment,expr}_start: take everything @@ -806,7 +799,7 @@ impl<'a> Raw<'a> { let endraw = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("endraw")), + ws(keyword("endraw")), // sic: ignore `{% end %}` in raw blocks opt(Whitespace::parse), peek(|i| s.tag_block_end(i)), )); @@ -888,7 +881,7 @@ impl<'a> If<'a> { cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), - ws(keyword("endif")), + end_node("if", "endif"), opt(Whitespace::parse), ))), ))), @@ -1057,6 +1050,25 @@ impl<'a> Comment<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub struct Ws(pub Option, pub Option); +fn end_node<'a, 'g: 'a>( + node: &'g str, + expected: &'g str, +) -> impl Fn(&'a str) -> ParseResult<'a> + 'g { + move |start| { + let (i, actual) = ws(identifier)(start)?; + if actual == expected { + Ok((i, actual)) + } else if actual.starts_with("end") { + Err(nom::Err::Failure(ErrorContext::new( + format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"), + start, + ))) + } else { + fail(start) + } + } +} + #[doc(hidden)] pub const MAX_KW_LEN: usize = 8; const MAX_REPL_LEN: usize = MAX_KW_LEN + 2; diff --git a/testing/tests/ui/typo_in_keyword.stderr b/testing/tests/ui/typo_in_keyword.stderr index b18eccee..bd9daecd 100644 --- a/testing/tests/ui/typo_in_keyword.stderr +++ b/testing/tests/ui/typo_in_keyword.stderr @@ -1,4 +1,4 @@ -error: failed to parse template source +error: expected `endfor` to terminate `for` node, found `endfo` --> :1:26 "endfo%}\n1234567890123456789012345678901234567890" --> tests/ui/typo_in_keyword.rs:5:14 diff --git a/testing/tests/ui/wrong-end.rs b/testing/tests/ui/wrong-end.rs new file mode 100644 index 00000000..0e43f5f3 --- /dev/null +++ b/testing/tests/ui/wrong-end.rs @@ -0,0 +1,31 @@ +use rinja::Template; + +#[derive(Template)] +#[template(source = "{% for _ in 1..=10 %}{% end %}", ext = "txt")] +struct For; + +#[derive(Template)] +#[template(source = "{% macro test() %}{% end %}", ext = "txt")] +struct Macro; + +#[derive(Template)] +#[template(source = "{% filter upper %}{% end %}", ext = "txt")] +struct Filter; + +#[derive(Template)] +#[template(source = "{% match () %}{% when () %}{% end %}", ext = "txt")] +struct Match; + +#[derive(Template)] +#[template(source = "{% block body %}{% end %}", ext = "txt")] +struct Block; + +#[derive(Template)] +#[template(source = "{% if true %}{% end %}", ext = "txt")] +struct If; + +#[derive(Template)] +#[template(source = "{% if true %}{% endfor %}", ext = "txt")] +struct IfFor; + +fn main() {} diff --git a/testing/tests/ui/wrong-end.stderr b/testing/tests/ui/wrong-end.stderr new file mode 100644 index 00000000..eb61b1f6 --- /dev/null +++ b/testing/tests/ui/wrong-end.stderr @@ -0,0 +1,55 @@ +error: expected `endfor` to terminate `for` node, found `end` + --> :1:23 + " end %}" + --> tests/ui/wrong-end.rs:4:21 + | +4 | #[template(source = "{% for _ in 1..=10 %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endmacro` to terminate `macro` node, found `end` + --> :1:20 + " end %}" + --> tests/ui/wrong-end.rs:8:21 + | +8 | #[template(source = "{% macro test() %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endfilter` to terminate `filter` node, found `end` + --> :1:20 + " end %}" + --> tests/ui/wrong-end.rs:12:21 + | +12 | #[template(source = "{% filter upper %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endmatch` to terminate `match` node, found `end` + --> :1:30 + "end %}" + --> tests/ui/wrong-end.rs:16:21 + | +16 | #[template(source = "{% match () %}{% when () %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endblock` to terminate `block` node, found `end` + --> :1:18 + " end %}" + --> tests/ui/wrong-end.rs:20:21 + | +20 | #[template(source = "{% block body %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endif` to terminate `if` node, found `end` + --> :1:15 + " end %}" + --> tests/ui/wrong-end.rs:24:21 + | +24 | #[template(source = "{% if true %}{% end %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `endif` to terminate `if` node, found `endfor` + --> :1:15 + " endfor %}" + --> tests/ui/wrong-end.rs:28:21 + | +28 | #[template(source = "{% if true %}{% endfor %}", ext = "txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^