Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parser: tell user proper keyword to end node #118

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions rinja_parser/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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)
}
}

Expand Down
28 changes: 5 additions & 23 deletions rinja_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -240,20 +240,6 @@ impl<'a> ErrorContext<'a> {
message: Some(message.into()),
}
}

pub(crate) fn from_err(error: nom::Err<Error<&'a str>>) -> nom::Err<Self> {
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> {
Expand Down Expand Up @@ -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) }
}
}

Expand Down Expand Up @@ -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}`"),
Expand Down
46 changes: 29 additions & 17 deletions rinja_parser/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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))?;
Expand Down Expand Up @@ -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),
))),
))),
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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),
))),
)));
Expand Down Expand Up @@ -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),
))),
))),
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)),
));
Expand Down Expand Up @@ -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),
))),
))),
Expand Down Expand Up @@ -1057,6 +1050,25 @@ impl<'a> Comment<'a> {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Ws(pub Option<Whitespace>, pub Option<Whitespace>);

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;
Expand Down
2 changes: 1 addition & 1 deletion testing/tests/ui/typo_in_keyword.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: failed to parse template source
error: expected `endfor` to terminate `for` node, found `endfo`
--> <source attribute>:1:26
"endfo%}\n1234567890123456789012345678901234567890"
--> tests/ui/typo_in_keyword.rs:5:14
Expand Down
31 changes: 31 additions & 0 deletions testing/tests/ui/wrong-end.rs
Original file line number Diff line number Diff line change
@@ -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() {}
55 changes: 55 additions & 0 deletions testing/tests/ui/wrong-end.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
error: expected `endfor` to terminate `for` node, found `end`
--> <source attribute>: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`
--> <source attribute>: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`
--> <source attribute>: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`
--> <source attribute>: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`
--> <source attribute>: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`
--> <source attribute>: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`
--> <source attribute>:1:15
" endfor %}"
--> tests/ui/wrong-end.rs:28:21
|
28 | #[template(source = "{% if true %}{% endfor %}", ext = "txt")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^