Skip to content

Commit 4232e40

Browse files
add ErrorNode
1 parent 6c77d91 commit 4232e40

File tree

4 files changed

+76
-3
lines changed

4 files changed

+76
-3
lines changed

crates/djls-templates/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ use djls_source::File;
5858
use djls_source::FileKind;
5959
pub use error::TemplateError;
6060
pub use lexer::Lexer;
61+
pub use nodelist::ErrorNode;
6162
pub use nodelist::NodeList;
6263
pub use parser::ParseError;
6364
pub use parser::Parser;

crates/djls-templates/src/nodelist.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use djls_source::Span;
22

33
use crate::db::Db as TemplateDb;
4+
use crate::parser::ParseError;
45

56
#[salsa::tracked(debug)]
67
pub struct NodeList<'db> {
@@ -28,6 +29,9 @@ pub enum Node<'db> {
2829
filters: Vec<FilterName<'db>>,
2930
span: Span,
3031
},
32+
Error {
33+
node: ErrorNode,
34+
},
3135
}
3236

3337
impl<'db> Node<'db> {
@@ -38,19 +42,23 @@ impl<'db> Node<'db> {
3842
| Node::Variable { span, .. }
3943
| Node::Comment { span, .. }
4044
| Node::Text { span } => *span,
45+
Node::Error { node, .. } => node.span,
4146
}
4247
}
4348

4449
#[must_use]
4550
pub fn full_span(&self) -> Span {
4651
match self {
4752
// account for delimiters
48-
Node::Variable { span, .. } | Node::Comment { span, .. } | Node::Tag { span, .. } => {
53+
Node::Variable { span, .. }
54+
| Node::Comment { span, .. }
55+
| Node::Tag { span, .. } => {
4956
Span {
5057
start: span.start.saturating_sub(3),
5158
length: span.length + 6,
5259
}
5360
}
61+
Node::Error { node } => node.span,
5462
Node::Text { span } => *span,
5563
}
5664
}
@@ -73,11 +81,18 @@ impl<'db> Node<'db> {
7381
length: u32::try_from(var_len).unwrap_or(0),
7482
})
7583
}
76-
Node::Comment { .. } | Node::Text { .. } => None,
84+
Node::Comment { .. } | Node::Text { .. } | Node::Error { .. } => None,
7785
}
7886
}
7987
}
8088

89+
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
90+
pub struct ErrorNode {
91+
pub content: String,
92+
pub span: Span,
93+
pub error: ParseError,
94+
}
95+
8196
#[salsa::interned(debug)]
8297
pub struct TagName<'db> {
8398
pub text: String,

crates/djls-templates/src/parser.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use thiserror::Error;
66
use crate::db::Db as TemplateDb;
77
use crate::db::TemplateErrorAccumulator;
88
use crate::error::TemplateError;
9+
use crate::nodelist::ErrorNode;
910
use crate::nodelist::FilterName;
1011
use crate::nodelist::Node;
1112
use crate::nodelist::NodeList;
@@ -41,7 +42,9 @@ impl<'db> Parser<'db> {
4142
nodelist.push(node);
4243
}
4344
Err(err) => {
44-
self.report_error(&err);
45+
let error_node = self.build_error_node(err);
46+
nodelist.push(error_node);
47+
4548
if !self.is_at_end() {
4649
self.synchronize()?;
4750
}
@@ -239,6 +242,26 @@ impl<'db> Parser<'db> {
239242
fn report_error(&self, error: &ParseError) {
240243
TemplateErrorAccumulator(TemplateError::Parser(error.to_string())).accumulate(self.db);
241244
}
245+
246+
fn build_error_node(&self, error: ParseError) -> Node<'db> {
247+
let token = self
248+
.peek_previous()
249+
.ok()
250+
.or_else(|| self.peek().ok())
251+
.map(|token| (span_from_token(token, self.db), token.lexeme(self.db)));
252+
253+
let (span, content) = token.unwrap_or_else(|| (Span::new(0, 0), String::new()));
254+
255+
self.report_error(&error);
256+
257+
Node::Error {
258+
node: ErrorNode {
259+
content,
260+
span,
261+
error,
262+
},
263+
}
264+
}
242265
}
243266

244267
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
@@ -375,6 +398,11 @@ mod tests {
375398
filters: Vec<String>,
376399
span: (u32, u32),
377400
},
401+
Error {
402+
content: String,
403+
span: (u32, u32),
404+
error: ParseError,
405+
},
378406
}
379407

380408
impl TestNode {
@@ -397,6 +425,11 @@ mod tests {
397425
filters: filters.iter().map(|f| f.text(db).to_string()).collect(),
398426
span: (span.start, span.length),
399427
},
428+
Node::Error { node } => TestNode::Error {
429+
content: node.content.clone(),
430+
span: (node.span.start, node.span.length),
431+
error: node.error.clone(),
432+
},
400433
}
401434
}
402435
}
@@ -711,6 +744,16 @@ mod tests {
711744
insta::assert_yaml_snapshot!(test_nodelist);
712745
}
713746

747+
#[test]
748+
fn test_parse_unclosed_variable_token() {
749+
let db = TestDatabase::new();
750+
let source = "{{ user".to_string();
751+
let template = TestTemplate::new(&db, source);
752+
let nodelist = parse_test_template(&db, template);
753+
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
754+
insta::assert_yaml_snapshot!(test_nodelist);
755+
}
756+
714757
// TODO: fix this so we can test against errors returned by parsing
715758
// #[test]
716759
// fn test_parse_error_recovery() {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: crates/djls-templates/src/parser.rs
3+
expression: test_nodelist
4+
---
5+
nodelist:
6+
- type: Error
7+
content: user
8+
span:
9+
- 3
10+
- 4
11+
error:
12+
MalformedConstruct:
13+
position: 3
14+
content: user

0 commit comments

Comments
 (0)