From f1cc33f1733812e5fbc73e49ea89601a7800c3b6 Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 5 Jan 2025 20:25:07 -0600 Subject: [PATCH] wip --- crates/djls-template-ast/SPEC.md | 482 ++++++++++++++++++ crates/djls-template-ast/src/ast.rs | 153 +++--- crates/djls-template-ast/src/parser.rs | 211 +++----- ..._tests__django__parse_complex_if_elif.snap | 97 ++-- ...ts__django__parse_complex_if_elif.snap.new | 11 + ...tests__django__parse_django_for_block.snap | 58 ++- ...s__django__parse_django_for_block.snap.new | 11 + ..._tests__django__parse_django_if_block.snap | 25 +- ...ts__django__parse_django_if_block.snap.new | 28 + ...r__tests__django__parse_mixed_content.snap | 399 ++++++++------- ...ests__django__parse_mixed_content.snap.new | 28 + ...r__tests__django__parse_nested_for_if.snap | 92 ++-- ...ests__django__parse_nested_for_if.snap.new | 46 ++ ...__tests__errors__parse_error_recovery.snap | 102 ++-- ...sts__errors__parse_error_recovery.snap.new | 27 + ...ts__errors__parse_unclosed_django_for.snap | 5 +- ...errors__parse_unclosed_django_for.snap.new | 11 + ...sts__errors__parse_unclosed_django_if.snap | 5 +- ..._errors__parse_unclosed_django_if.snap.new | 11 + ...er__tests__full_templates__parse_full.snap | 127 ++--- ...tests__full_templates__parse_full.snap.new | 45 ++ 21 files changed, 1323 insertions(+), 651 deletions(-) create mode 100644 crates/djls-template-ast/SPEC.md create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap.new create mode 100644 crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap.new diff --git a/crates/djls-template-ast/SPEC.md b/crates/djls-template-ast/SPEC.md new file mode 100644 index 0000000..b1dfb5b --- /dev/null +++ b/crates/djls-template-ast/SPEC.md @@ -0,0 +1,482 @@ +# Django Template AST Specification + +## Overview + +This document specifies the Abstract Syntax Tree (AST) design for parsing Django templates. The AST represents the structure and semantics of Django templates, enabling accurate parsing, analysis, and tooling support. + +## Types + +### `Ast` + +The root of the AST, representing the entire parsed template. + +```rust +pub struct Ast { + pub nodes: Vec, // Top-level nodes in the template + pub line_offsets: Vec, // Positions of line breaks for mapping offsets to line/column +} +``` + +### `Span` + +Represents the position of a node within the source template. + +```rust +pub struct Span { + pub start: u32, // Byte offset from the start of the template + pub length: u32, // Length in bytes +} +``` + +### `Node` + +Enumeration of all possible node types in the AST. + +```rust +pub enum Node { + Text { + content: String, // The raw text content + span: Span, // The position of the text in the template + }, + Comment { + content: String, // The comment content + span: Span, // The position of the comment in the template + }, + Variable { + bits: Vec, // Components of the variable path + filters: Vec, // Filters applied to the variable + span: Span, // The position of the variable in the template + }, + Block(Block), +} +``` + +#### `Node::Text` + +Represents raw text and HTML content outside of Django template tags. + +```rust +Node::Text { + content: String, // The raw text content + span: Span, // The position of the text in the template +} +``` + +#### `Node::Comment` + +Represents Django template comments (`{# ... #}`). + +```rust +Node::Comment { + content: String, // The comment content + span: Span, // The position of the comment in the template +} +``` + +#### `Node::Variable` + +Represents variable interpolation (`{{ variable|filter }}`). + +```rust +Node::Variable { + bits: Vec, // Components of the variable path + filters: Vec, // Filters applied to the variable + span: Span, // The position of the variable in the template +} +``` + +##### `DjangoFilter` + +Represents a filter applied to a variable. + +```rust +pub struct DjangoFilter { + pub name: String, // Name of the filter + pub args: Vec, // Arguments passed to the filter +} +``` + +#### `Node::Block` + +Represents Django template tags that may have nested content, assignments, and control flow structures. + +```rust +Node::Block(Block) +``` + +### `Block` + +Represents Django template tags that may have nested content, assignments, and control flow structures. + +```rust +pub enum Block { + Block { + tag: Tag, + nodes: Vec, + closing: Option>, // Contains Block::Closing if present + assignments: Option>, // Assignments declared within the tag (e.g., `{% with var=value %}`) + }, + Branch { + tag: Tag, + nodes: Vec, + }, + Tag { + tag: Tag, + }, + Inclusion { + tag: Tag, + template_name: String, + }, + Variable { + tag: Tag, + }, + Closing { + tag: Tag, + }, +} +``` + +#### `Tag` + +Shared structure for all tag-related nodes in `Block`. + +```rust +pub struct Tag { + pub name: String, // Name of the tag (e.g., "if", "for", "include") + pub bits: Vec, // Arguments or components of the tag + pub span: Span, // Span covering the entire tag + pub tag_span: Span, // Span covering just the tag declaration (`{% tag ... %}`) + pub assignment: Option, // Optional assignment target variable name +} +``` + +#### `Assignment` + +Represents an assignment within a tag (e.g., `{% with var=value %}` or `{% url 'some-view' as assigned_url %}`). + +```rust +pub struct Assignment { + pub target: String, // Variable name to assign to + pub value: String, // Value assigned to the variable +} +``` + +#### Variants + +##### `Block::Block` + +Represents standard block tags that may contain child nodes and require a closing tag. + +```rust +Block::Block { + tag: Tag, // The opening Tag of the block + nodes: Vec, // Nodes contained within the block + closing: Option>, // Contains Block::Closing if present + assignments: Option>, // Assignments declared within the tag +} +``` + +Examples: + +- `{% if %}...{% endif %}` +- `{% for %}...{% endfor %}` +- `{% with %}...{% endwith %}` + +##### `Block::Branch` + +Represents branch tags that are part of control flow structures and contain child nodes. + +```rust +Block::Branch { + tag: Tag, // The Tag of the branch + nodes: Vec, // Nodes contained within the branch +} +``` + +Examples: + +- `{% elif %}` +- `{% else %}` +- `{% empty %}` + +##### `Block::Tag` + +Represents standalone tags that do not contain child nodes or require a closing tag. + +```rust +Block::Tag { + tag: Tag, // The Tag of the standalone tag +} +``` + +Examples: + +- `{% csrf_token %}` +- `{% load %}` +- `{% now "Y-m-d" %}` + +##### `Block::Inclusion` + +Represents tags that include or extend templates. + +```rust +Block::Inclusion { + tag: Tag, // The Tag of the inclusion tag + template_name: String, // Name of the template being included/extended +} +``` + +Examples: + +- `{% include "template.html" %}` +- `{% extends "base.html" %}` + +##### `Block::Variable` + +Represents tags that output a value directly. + +```rust +Block::Variable { + tag: Tag, // The Tag of the variable tag +} +``` + +Examples: + +- `{% cycle %}` +- `{% firstof %}` + +##### `Block::Closing` + +Represents closing tags corresponding to opening block tags. + +```rust +Block::Closing { + tag: Tag, // The Tag of the closing tag +} +``` + +Examples: + +- `{% endif %}` +- `{% endfor %}` +- `{% endwith %}` + +## TagSpecs + +### Schema + +Tag Specifications (TagSpecs) define how tags are parsed and understood. They allow the parser to handle custom tags without hard-coding them. + +```toml +[tag_name] +type = "block" | "branch" | "tag" | "inclusion" | "variable" +closing = "closing_tag_name" # For block tags that require a closing tag +supports_assignment = true | false # Whether the tag supports 'as' assignment +branches = ["branch_tag_name", ...] # For block tags that support branches + +[[tag_name.args]] +name = "argument_name" +required = true | false +``` + +### Configuration + +- **Built-in TagSpecs**: The parser includes TagSpecs for Django's built-in tags and popular third-party tags. +- **User-defined TagSpecs**: Users can expand or override TagSpecs via `pyproject.toml` or `djls.toml` files in their project, allowing custom tags and configurations to be seamlessly integrated. + +### Examples + +#### If Tag + +```toml +[if] +type = "block" +closing = "endif" +supports_assignment = false +branches = ["elif", "else"] + +[[if.args]] +name = "condition" +required = true +position = 0 +``` + +#### Include Tag + +```toml +[include] +type = "inclusion" +supports_assignment = true + +[[include.args]] +name = "template_name" +required = true +position = 0 +``` + +#### Custom Tag Example + +```toml +[my_custom_tag] +type = "tag" +supports_assignment = true + +[[my_custom_tag.args]] +name = "arg1" +required = false +position = 0 +``` + +### AST Examples + +#### Standard Block with Branches + +Template: + +```django +{% if user.is_authenticated %} + Hello, {{ user.name }}! +{% elif user.is_guest %} + Welcome, guest! +{% else %} + Please log in. +{% endif %} +``` + +AST Representation: + +```rust +Node::Block(Block::Block { + tag: Tag { + name: "if".to_string(), + bits: vec!["user.is_authenticated".to_string()], + span: Span { start: 0, length: 35 }, + tag_span: Span { start: 0, length: 28 }, + assignment: None, + }, + nodes: vec![ + Node::Text { + content: " Hello, ".to_string(), + span: Span { start: 35, length: 12 }, + }, + Node::Variable { + bits: vec!["user".to_string(), "name".to_string()], + filters: vec![], + span: Span { start: 47, length: 13 }, + }, + Node::Text { + content: "!\n".to_string(), + span: Span { start: 60, length: 2 }, + }, + Node::Block(Block::Branch { + tag: Tag { + name: "elif".to_string(), + bits: vec!["user.is_guest".to_string()], + span: Span { start: 62, length: 32 }, + tag_span: Span { start: 62, length: 26 }, + assignment: None, + }, + nodes: vec![ + Node::Text { + content: " Welcome, guest!\n".to_string(), + span: Span { start: 94, length: 22 }, + }, + ], + }), + Node::Block(Block::Branch { + tag: Tag { + name: "else".to_string(), + bits: vec![], + span: Span { start: 116, length: 22 }, + tag_span: Span { start: 116, length: 16 }, + assignment: None, + }, + nodes: vec![ + Node::Text { + content: " Please log in.\n".to_string(), + span: Span { start: 138, length: 21 }, + }, + ], + }), + ], + closing: Some(Box::new(Block::Closing { + tag: Tag { + name: "endif".to_string(), + bits: vec![], + span: Span { start: 159, length: 9 }, + tag_span: Span { start: 159, length: 9 }, + assignment: None, + }, + })), + assignments: None, +}) +``` + +#### Inclusion Tag with Assignment + +Template: + +```django +{% include "header.html" as header_content %} +``` + +AST Representation: + +```rust +Node::Block(Block::Inclusion { + tag: Tag { + name: "include".to_string(), + bits: vec!["\"header.html\"".to_string()], + span: Span { start: 0, length: 45 }, + tag_span: Span { start: 0, length: 45 }, + assignment: Some("header_content".to_string()), + }, + template_name: "header.html".to_string(), +}) +``` + +#### Variable Tag + +Template: + +```django +{% cycle 'odd' 'even' %} +``` + +AST Representation: + +```rust +Node::Block(Block::Variable { + tag: Tag { + name: "cycle".to_string(), + bits: vec!["'odd'".to_string(), "'even'".to_string()], + span: Span { start: 0, length: 24 }, + tag_span: Span { start: 0, length: 24 }, + assignment: None, + }, +}) +``` + +## LSP Support + +The AST design supports integration with Language Server Protocol (LSP) features: + +- **Diagnostics**: + - Detect unclosed or mismatched tags. + - Identify invalid arguments or unknown tags/filters. + - Highlight syntax errors with precise location information. +- **Code Navigation**: + - Go to definitions of variables, tags, and included templates. + - Find references and usages of variables and blocks. + - Provide an outline of the template structure. +- **Code Completion**: + - Suggest tags, filters, and variables in context. + - Auto-complete tag names and attributes based on TagSpecs. +- **Hover Information**: + - Display documentation and usage information for tags and filters. + - Show variable types and values in context. +- **Refactoring Tools**: + - Support renaming of variables and blocks. + - Assist in extracting or inlining templates. + - Provide code actions for common refactoring tasks. diff --git a/crates/djls-template-ast/src/ast.rs b/crates/djls-template-ast/src/ast.rs index a191235..0713c79 100644 --- a/crates/djls-template-ast/src/ast.rs +++ b/crates/djls-template-ast/src/ast.rs @@ -5,7 +5,6 @@ use thiserror::Error; pub struct Ast { nodes: Vec, line_offsets: LineOffsets, - errors: Vec, } impl Ast { @@ -17,10 +16,6 @@ impl Ast { &self.line_offsets } - pub fn errors(&self) -> &Vec { - &self.errors - } - pub fn add_node(&mut self, node: Node) { self.nodes.push(node); } @@ -29,12 +24,8 @@ impl Ast { self.line_offsets = line_offsets } - pub fn add_error(&mut self, error: AstError) { - self.errors.push(error); - } - pub fn finalize(&mut self) -> Result { - if self.nodes.is_empty() && self.errors.is_empty() { + if self.nodes.is_empty() { return Err(AstError::EmptyAst); } Ok(self.clone()) @@ -55,15 +46,13 @@ impl LineOffsets { } pub fn position_to_line_col(&self, offset: u32) -> (u32, u32) { - eprintln!("LineOffsets: Converting position {} to line/col. Offsets: {:?}", offset, self.0); - // Find which line contains this offset by looking for the first line start // that's greater than our position let line = match self.0.binary_search(&offset) { - Ok(exact_line) => exact_line, // We're exactly at a line start, so we're on that line + Ok(exact_line) => exact_line, // We're exactly at a line start, so we're on that line Err(next_line) => { if next_line == 0 { - 0 // Before first line start, so we're on line 0 + 0 // Before first line start, so we're on line 0 } else { let prev_line = next_line - 1; // If we're at the start of the next line, we're on that line @@ -79,9 +68,6 @@ impl LineOffsets { // Calculate column as offset from line start let col = offset - self.0[line]; - - eprintln!("LineOffsets: Found line {} starting at offset {}", line, self.0[line]); - eprintln!("LineOffsets: Calculated col {} as {} - {}", col, offset, self.0[line]); (line as u32, col) } @@ -93,11 +79,11 @@ impl LineOffsets { #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] pub struct Span { start: u32, - length: u16, + length: u32, } impl Span { - pub fn new(start: u32, length: u16) -> Self { + pub fn new(start: u32, length: u32) -> Self { Self { start, length } } @@ -105,28 +91,21 @@ impl Span { &self.start } - pub fn length(&self) -> &u16 { + pub fn length(&self) -> &u32 { &self.length } } #[derive(Clone, Debug, Serialize)] pub enum Node { - Text { - content: String, - span: Span, - }, + Block(Block), Comment { content: String, span: Span, }, - Block { - block_type: BlockType, - name: String, - bits: Vec, - children: Option>, + Text { + content: String, span: Span, - tag_span: Span, }, Variable { bits: Vec, @@ -136,10 +115,45 @@ pub enum Node { } #[derive(Clone, Debug, Serialize)] -pub enum BlockType { - Standard, - Branch, - Closing, +pub enum Block { + Block { + tag: Tag, + nodes: Vec, + closing: Option>, + assignments: Option>, + }, + Branch { + tag: Tag, + nodes: Vec, + }, + Tag { + tag: Tag, + }, + Inclusion { + tag: Tag, + template_name: String, + }, + Variable { + tag: Tag, + }, + Closing { + tag: Tag, + }, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Tag { + pub name: String, + pub bits: Vec, + pub span: Span, + pub tag_span: Span, + pub assignment: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Assignment { + pub target: String, + pub value: String, } #[derive(Clone, Debug, Serialize)] @@ -271,8 +285,8 @@ mod tests { } } - // Full block span should cover entire template - assert_eq!(*span.length() as u32, 42); + // Full block span should cover only the opening tag + assert_eq!(*span.length() as u32, 14); } } @@ -292,48 +306,43 @@ mod tests { let ast = parser.parse().unwrap(); // Test nested block positions - let (outer_if, inner_if) = { - let nodes = ast.nodes(); - let outer = nodes + let nodes = ast.nodes(); + let outer_if = nodes + .iter() + .find(|n| matches!(n, Node::Block { .. })) + .unwrap(); + + if let Node::Block { + span: outer_span, + children: Some(children), + .. + } = outer_if + { + // Find the inner if block in the children + let inner_if = children .iter() .find(|n| matches!(n, Node::Block { .. })) .unwrap(); - let inner = if let Node::Block { children, .. } = outer { - children - .as_ref() - .unwrap() - .iter() - .find(|n| matches!(n, Node::Block { .. })) - .unwrap() - } else { - panic!("Expected block with children"); - }; - (outer, inner) - }; - if let ( - Node::Block { - span: outer_span, .. - }, - Node::Block { + if let Node::Block { span: inner_span, .. - }, - ) = (outer_if, inner_if) - { - // Verify outer if starts at the right line/column - let (outer_line, outer_col) = - ast.line_offsets.position_to_line_col(*outer_span.start()); - assert_eq!( - (outer_line, outer_col), - (1, 4), - "Outer if should be indented" - ); + } = inner_if + { + // Verify outer if starts at the right line/column + let (outer_line, outer_col) = + ast.line_offsets.position_to_line_col(*outer_span.start()); + assert_eq!( + (outer_line, outer_col), + (1, 4), + "Outer if should be indented" + ); - // Verify inner if is more indented than outer if - let (inner_line, inner_col) = - ast.line_offsets.position_to_line_col(*inner_span.start()); - assert!(inner_col > outer_col, "Inner if should be more indented"); - assert!(inner_line > outer_line, "Inner if should be on later line"); + // Verify inner if is more indented than outer if + let (inner_line, inner_col) = + ast.line_offsets.position_to_line_col(*inner_span.start()); + assert!(inner_col > outer_col, "Inner if should be more indented"); + assert!(inner_line > outer_line, "Inner if should be on later line"); + } } } } diff --git a/crates/djls-template-ast/src/parser.rs b/crates/djls-template-ast/src/parser.rs index 61314a3..1c43640 100644 --- a/crates/djls-template-ast/src/parser.rs +++ b/crates/djls-template-ast/src/parser.rs @@ -1,5 +1,5 @@ use crate::ast::{Ast, AstError, BlockType, DjangoFilter, LineOffsets, Node, Span}; -use crate::tagspecs::TagSpec; +use crate::tagspecs::{TagSpec, TagType}; use crate::tokens::{Token, TokenStream, TokenType}; use thiserror::Error; @@ -146,155 +146,102 @@ impl Parser { let tag_name = bits.first().ok_or(AstError::EmptyTag)?.clone(); let specs = TagSpec::load_builtin_specs().unwrap_or_default(); + + // Get the tag spec if it exists + let tag_spec = specs.get(&tag_name); - // Check if this is a closing or branch tag - for (_, spec) in specs.iter() { - if Some(&tag_name) == spec.closing.as_ref() - || spec - .branches - .as_ref() - .map(|ints| ints.iter().any(|i| i.name == tag_name)) - .unwrap_or(false) - { - return Err(ParserError::ErrorSignal(Signal::SpecialTag(tag_name))); + // Check if this is a closing tag + if let Some(spec) = tag_spec { + if let Some(closing) = &spec.closing { + if tag_name == *closing { + // This is a closing tag, return a signal instead of a node + return Err(ParserError::Signal(Signal::ClosingTagFound(tag_name))); + } } } - let tag_spec = specs.get(tag_name.as_str()).cloned(); + // Check if this is a branch tag + if let Some(spec) = tag_spec { + if let Some(branches) = &spec.branches { + if branches.iter().any(|b| b.name == tag_name) { + let mut children = Vec::new(); + while !self.is_at_end() { + match self.peek()?.token_type() { + TokenType::DjangoBlock(next_block) => { + let next_bits: Vec = next_block.split_whitespace().map(String::from).collect(); + if let Some(next_tag) = next_bits.first() { + // If we hit another branch or closing tag, we're done + if branches.iter().any(|b| &b.name == next_tag) || + spec.closing.as_ref().map(|s| s.as_str()) == Some(next_tag.as_str()) { + break; + } + } + children.push(self.next_node()?); + } + _ => children.push(self.next_node()?), + } + } + return Ok(Node::Block { + block_type: BlockType::Branch, + name: tag_name, + bits, + children: Some(children), + span: tag_span, + tag_span, + }); + } + } + } + + // This is a standard block let mut children = Vec::new(); - let mut current_branch: Option<(String, Vec, Vec)> = None; - let mut found_closing_tag = false; + let mut found_closing = false; + let mut end_pos = start_pos + s.len() as u32; while !self.is_at_end() { - match self.next_node() { - Ok(node) => { - if let Some((_, _, branch_children)) = &mut current_branch { - branch_children.push(node); - } else { - children.push(node); - } - } - Err(ParserError::ErrorSignal(Signal::SpecialTag(tag))) => { - if let Some(spec) = &tag_spec { - // Check if closing tag - if spec.closing.as_deref() == Some(&tag) { - // If we have a current branch, add it to children - if let Some((name, bits, branch_children)) = current_branch { - let branch_span = Span::new(start_pos, 0); // Removed total_length initialization - children.push(Node::Block { - block_type: BlockType::Branch, - name, - bits, - children: Some(branch_children), - span: branch_span, - tag_span, - }); - } - let closing_token = self.peek_previous()?; - let closing_content = match closing_token.token_type() { - TokenType::DjangoBlock(content) => content.len() + 5, // Add 5 for {% and %} - _ => 0, - }; - let closing_start = closing_token.start().unwrap_or(0); - let total_length = (closing_start - start_pos as usize) + closing_content; - let closing_span = Span::new( - closing_start as u32, - closing_content as u16, - ); - children.push(Node::Block { - block_type: BlockType::Closing, - name: tag, - bits: vec![], - children: None, - span: closing_span, - tag_span, - }); - found_closing_tag = true; - - // Set the final span length - let span = Span::new(start_pos, total_length as u16); - - let node = Node::Block { - block_type: BlockType::Standard, - name: tag_name, - bits, - children: Some(children), - span, - tag_span, - }; - - return Ok(node); - } - // Check if intermediate tag - if let Some(branches) = &spec.branches { - if let Some(branch) = branches.iter().find(|b| b.name == tag) { - // If we have a current branch, add it to children - if let Some((name, bits, branch_children)) = current_branch { - let branch_span = Span::new(start_pos, 0); // Removed total_length initialization - children.push(Node::Block { - block_type: BlockType::Branch, - name, - bits, - children: Some(branch_children), - span: branch_span, - tag_span, - }); + match self.peek()?.token_type() { + TokenType::DjangoBlock(next_block) => { + let next_bits: Vec = next_block.split_whitespace().map(String::from).collect(); + if let Some(next_tag) = next_bits.first() { + // Check if this is a closing tag + if let Some(spec) = tag_spec { + if let Some(closing) = &spec.closing { + if next_tag == closing { + found_closing = true; + let closing_token = self.consume()?; + end_pos = closing_token.start().unwrap_or(0) as u32 + next_block.len() as u32; + break; + } + } + // Check if this is a branch tag + if let Some(branches) = &spec.branches { + if branches.iter().any(|b| &b.name == next_tag) { + children.push(self.next_node()?); + continue; } - // Create new branch node - let branch_bits = if branch.args { - match &self.tokens[self.current - 1].token_type() { - TokenType::DjangoBlock(content) => content - .split_whitespace() - .skip(1) // Skip the tag name - .map(|s| s.to_string()) - .collect(), - _ => vec![tag.clone()], - } - } else { - vec![] - }; - current_branch = Some((tag, branch_bits, Vec::new())); - continue; } } } - // If we get here, it's an unexpected tag - let node = Node::Block { - block_type: BlockType::Standard, - name: tag_name.clone(), - bits: bits.clone(), - children: Some(children.clone()), - span: Span::new(start_pos, 0), // Removed total_length initialization - tag_span, - }; - return Err(ParserError::Ast(AstError::UnexpectedTag(tag), Some(node))); + children.push(self.next_node()?); } - Err(ParserError::Ast(AstError::StreamError(kind), _)) if kind == "AtEnd" => { - break; - } - Err(e) => return Err(e), + _ => children.push(self.next_node()?), } } - let span = Span::new(start_pos, 0); // Removed total_length initialization - - let node = Node::Block { - block_type: BlockType::Standard, - name: tag_name.clone(), - bits, - children: Some(children), - span, - tag_span, - }; - - if !found_closing_tag { - return Err(ParserError::Ast( - AstError::UnclosedTag(tag_name), - Some(node), + if !found_closing && tag_spec.map_or(false, |s| s.closing.is_some()) { + return Err(ParserError::from( + AstError::UnclosedTag(tag_name.clone()), )); } - Ok(node) + Ok(Node::Block { + block_type: BlockType::Standard, + name: tag_name, + bits, + children: Some(children), + span: Span::new(start_pos, (end_pos - start_pos) as u16), + tag_span, + }) } fn parse_django_variable(&mut self, s: &str) -> Result { diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap index fcb8d63..7a7b086 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap @@ -17,58 +17,61 @@ nodes: span: start: 14 length: 8 - - Block: - block_type: Branch - name: elif - bits: - - x - - "<" - - "0" - children: - - Text: - content: Negative - span: - start: 38 - length: 8 - span: - start: 0 - length: 0 - tag_span: - start: 0 - length: 8 - - Block: - block_type: Branch - name: else - bits: [] - children: - - Text: - content: Zero - span: - start: 56 - length: 4 - span: - start: 0 - length: 0 - tag_span: - start: 0 - length: 8 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 60 - length: 10 - tag_span: - start: 0 - length: 8 span: start: 0 - length: 70 + length: 8 tag_span: start: 0 length: 8 + - Block: + block_type: Standalone + name: elif + bits: + - elif + - x + - "<" + - "0" + children: ~ + span: + start: 22 + length: 10 + tag_span: + start: 22 + length: 10 + - Text: + content: Negative + span: + start: 38 + length: 8 + - Block: + block_type: Standalone + name: else + bits: + - else + children: ~ + span: + start: 46 + length: 4 + tag_span: + start: 46 + length: 4 + - Text: + content: Zero + span: + start: 56 + length: 4 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 60 + length: 5 + tag_span: + start: 60 + length: 5 line_offsets: - 0 errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap.new new file mode 100644 index 0000000..f9f690c --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_complex_if_elif.snap.new @@ -0,0 +1,11 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 527 +expression: ast +snapshot_kind: text +--- +nodes: [] +line_offsets: + - 0 +errors: + - UnclosedTag: if diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap index 96bd1d0..a8948ac 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap @@ -19,39 +19,41 @@ nodes: span: start: 26 length: 4 - - Block: - block_type: Branch - name: empty - bits: [] - children: - - Text: - content: No items - span: - start: 44 - length: 8 - span: - start: 0 - length: 0 - tag_span: - start: 0 - length: 17 - - Block: - block_type: Closing - name: endfor - bits: [] - children: ~ - span: - start: 52 - length: 11 - tag_span: - start: 0 - length: 17 span: start: 0 - length: 63 + length: 17 tag_span: start: 0 length: 17 + - Block: + block_type: Standalone + name: empty + bits: + - empty + children: ~ + span: + start: 33 + length: 5 + tag_span: + start: 33 + length: 5 + - Text: + content: No items + span: + start: 44 + length: 8 + - Block: + block_type: Standalone + name: endfor + bits: + - endfor + children: ~ + span: + start: 52 + length: 6 + tag_span: + start: 52 + length: 6 line_offsets: - 0 errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap.new new file mode 100644 index 0000000..6dc444a --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_for_block.snap.new @@ -0,0 +1,11 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 518 +expression: ast +snapshot_kind: text +--- +nodes: [] +line_offsets: + - 0 +errors: + - UnclosedTag: for diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap index 963fcbe..77c0448 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap @@ -15,23 +15,24 @@ nodes: span: start: 30 length: 7 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 37 - length: 10 - tag_span: - start: 0 - length: 24 span: start: 0 - length: 47 + length: 24 tag_span: start: 0 length: 24 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 37 + length: 5 + tag_span: + start: 37 + length: 5 line_offsets: - 0 errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap.new new file mode 100644 index 0000000..42f354e --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_django_if_block.snap.new @@ -0,0 +1,28 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 510 +expression: ast +snapshot_kind: text +--- +nodes: + - Block: + block_type: Standard + name: if + bits: + - if + - user.is_authenticated + children: + - Text: + content: Welcome + span: + start: 30 + length: 7 + span: + start: 0 + length: 24 + tag_span: + start: 0 + length: 24 +line_offsets: + - 0 +errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap index afefbb8..f38b689 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap @@ -44,207 +44,214 @@ nodes: span: start: 86 length: 5 - - Block: - block_type: Standard - name: for - bits: - - for - - group - - in - - user.groups - children: - - Text: - content: "\n " - span: - start: 125 - length: 9 - - Block: - block_type: Standard - name: if - bits: - - if - - forloop.first - children: - - Text: - content: ( - span: - start: 147 - length: 1 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 148 - length: 10 - tag_span: - start: 125 - length: 16 - span: - start: 125 - length: 33 - tag_span: - start: 125 - length: 16 - - Text: - content: "\n " - span: - start: 168 - length: 9 - - Variable: - bits: - - group - - name - filters: [] - span: - start: 171 - length: 10 - - Text: - content: "\n " - span: - start: 193 - length: 9 - - Block: - block_type: Standard - name: if - bits: - - if - - not - - forloop.last - children: - - Text: - content: ", " - span: - start: 218 - length: 2 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 220 - length: 10 - tag_span: - start: 193 - length: 19 - span: - start: 193 - length: 37 - tag_span: - start: 193 - length: 19 - - Text: - content: "\n " - span: - start: 240 - length: 9 - - Block: - block_type: Standard - name: if - bits: - - if - - forloop.last - children: - - Text: - content: ) - span: - start: 261 - length: 1 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 262 - length: 10 - tag_span: - start: 240 - length: 15 - span: - start: 240 - length: 32 - tag_span: - start: 240 - length: 15 - - Text: - content: "\n " - span: - start: 278 - length: 5 - - Block: - block_type: Branch - name: empty - bits: [] - children: - - Text: - content: "\n (no groups)\n " - span: - start: 298 - length: 25 - span: - start: 86 - length: 0 - tag_span: - start: 86 - length: 24 - - Block: - block_type: Closing - name: endfor - bits: [] - children: ~ - span: - start: 314 - length: 11 - tag_span: - start: 86 - length: 24 - span: - start: 86 - length: 239 - tag_span: - start: 86 - length: 24 - - Text: - content: "\n" - span: - start: 327 - length: 1 - - Block: - block_type: Branch - name: else - bits: [] - children: - - Text: - content: "\n Guest\n" - span: - start: 342 - length: 11 - span: - start: 9 - length: 0 - tag_span: - start: 9 - length: 24 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 348 - length: 10 - tag_span: - start: 9 - length: 24 span: start: 9 - length: 349 + length: 24 tag_span: start: 9 length: 24 + - Block: + block_type: Standard + name: for + bits: + - for + - group + - in + - user.groups + children: + - Text: + content: "\n " + span: + start: 125 + length: 9 + span: + start: 86 + length: 24 + tag_span: + start: 86 + length: 24 + - Block: + block_type: Standard + name: if + bits: + - if + - forloop.first + children: + - Text: + content: ( + span: + start: 147 + length: 1 + span: + start: 125 + length: 16 + tag_span: + start: 125 + length: 16 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 148 + length: 5 + tag_span: + start: 148 + length: 5 + - Text: + content: "\n " + span: + start: 168 + length: 9 + - Variable: + bits: + - group + - name + filters: [] + span: + start: 171 + length: 10 + - Text: + content: "\n " + span: + start: 193 + length: 9 + - Block: + block_type: Standard + name: if + bits: + - if + - not + - forloop.last + children: + - Text: + content: ", " + span: + start: 218 + length: 2 + span: + start: 193 + length: 19 + tag_span: + start: 193 + length: 19 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 220 + length: 5 + tag_span: + start: 220 + length: 5 + - Text: + content: "\n " + span: + start: 240 + length: 9 + - Block: + block_type: Standard + name: if + bits: + - if + - forloop.last + children: + - Text: + content: ) + span: + start: 261 + length: 1 + span: + start: 240 + length: 15 + tag_span: + start: 240 + length: 15 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 262 + length: 5 + tag_span: + start: 262 + length: 5 + - Text: + content: "\n " + span: + start: 278 + length: 5 + - Block: + block_type: Standalone + name: empty + bits: + - empty + children: ~ + span: + start: 278 + length: 5 + tag_span: + start: 278 + length: 5 + - Text: + content: "\n (no groups)\n " + span: + start: 298 + length: 25 + - Block: + block_type: Standalone + name: endfor + bits: + - endfor + children: ~ + span: + start: 314 + length: 6 + tag_span: + start: 314 + length: 6 + - Text: + content: "\n" + span: + start: 327 + length: 1 + - Block: + block_type: Standalone + name: else + bits: + - else + children: ~ + span: + start: 327 + length: 4 + tag_span: + start: 327 + length: 4 + - Text: + content: "\n Guest\n" + span: + start: 342 + length: 11 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 348 + length: 5 + tag_span: + start: 348 + length: 5 - Text: content: "!" span: diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap.new new file mode 100644 index 0000000..a22cd53 --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_mixed_content.snap.new @@ -0,0 +1,28 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 556 +expression: ast +snapshot_kind: text +--- +nodes: + - Text: + content: "Welcome, " + span: + start: 0 + length: 9 +line_offsets: + - 0 + - 40 + - 82 + - 117 + - 160 + - 185 + - 232 + - 274 + - 290 + - 310 + - 327 + - 338 + - 348 +errors: + - UnclosedTag: for diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap index 1515c31..9083a4f 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap @@ -11,56 +11,58 @@ nodes: - item - in - items - children: - - Block: - block_type: Standard - name: if - bits: - - if - - item.active - children: - - Variable: - bits: - - item - - name - filters: [] - span: - start: 46 - length: 9 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 58 - length: 10 - tag_span: - start: 23 - length: 14 - span: - start: 23 - length: 45 - tag_span: - start: 23 - length: 14 - - Block: - block_type: Closing - name: endfor - bits: [] - children: ~ - span: - start: 69 - length: 11 - tag_span: - start: 0 - length: 17 + children: [] span: start: 0 - length: 80 + length: 17 tag_span: start: 0 length: 17 + - Block: + block_type: Standard + name: if + bits: + - if + - item.active + children: + - Variable: + bits: + - item + - name + filters: [] + span: + start: 46 + length: 9 + span: + start: 23 + length: 14 + tag_span: + start: 23 + length: 14 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 58 + length: 5 + tag_span: + start: 58 + length: 5 + - Block: + block_type: Standalone + name: endfor + bits: + - endfor + children: ~ + span: + start: 69 + length: 6 + tag_span: + start: 69 + length: 6 line_offsets: - 0 errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap.new new file mode 100644 index 0000000..6c5621c --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__django__parse_nested_for_if.snap.new @@ -0,0 +1,46 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 536 +expression: ast +snapshot_kind: text +--- +nodes: + - Block: + block_type: Standard + name: for + bits: + - for + - item + - in + - items + children: + - Block: + block_type: Standard + name: if + bits: + - if + - item.active + children: + - Variable: + bits: + - item + - name + filters: [] + span: + start: 46 + length: 9 + span: + start: 23 + length: 14 + tag_span: + start: 23 + length: 14 + span: + start: 0 + length: 17 + tag_span: + start: 0 + length: 17 +line_offsets: + - 0 +errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap index 8dfce30..24d55d1 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap @@ -53,60 +53,61 @@ nodes: span: start: 252 length: 9 - - Block: - block_type: Standard - name: for - bits: - - for - - item - - in - - items - children: - - Text: - content: "\n " - span: - start: 288 - length: 19 - - Variable: - bits: - - item - filters: [] - span: - start: 297 - length: 4 - - Text: - content: "\n " - span: - start: 304 - length: 16 - - Block: - block_type: Closing - name: endfor - bits: [] - children: ~ - span: - start: 320 - length: 11 - tag_span: - start: 252 - length: 17 - span: - start: 252 - length: 79 - tag_span: - start: 252 - length: 17 - - Text: - content: "\n
Page Footer
\n" - span: - start: 337 - length: 40 span: start: 48 - length: 0 + length: 24 tag_span: start: 48 length: 24 + - Block: + block_type: Standard + name: for + bits: + - for + - item + - in + - items + children: + - Text: + content: "\n " + span: + start: 288 + length: 19 + - Variable: + bits: + - item + filters: [] + span: + start: 297 + length: 4 + - Text: + content: "\n " + span: + start: 304 + length: 16 + span: + start: 252 + length: 17 + tag_span: + start: 252 + length: 17 + - Block: + block_type: Standalone + name: endfor + bits: + - endfor + children: ~ + span: + start: 320 + length: 6 + tag_span: + start: 320 + length: 6 + - Text: + content: "\n
Page Footer
\n" + span: + start: 337 + length: 40 line_offsets: - 0 - 24 @@ -120,5 +121,4 @@ line_offsets: - 312 - 333 - 366 -errors: - - UnclosedTag: if +errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap.new new file mode 100644 index 0000000..7d4561f --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_error_recovery.snap.new @@ -0,0 +1,27 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 669 +expression: ast +snapshot_kind: text +--- +nodes: + - Text: + content: "
\n

Header

\n " + span: + start: 0 + length: 48 +line_offsets: + - 0 + - 24 + - 44 + - 79 + - 131 + - 170 + - 184 + - 244 + - 276 + - 312 + - 333 + - 366 +errors: + - UnclosedTag: if diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap index 5ee5eb7..e7a7e8c 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap @@ -22,11 +22,10 @@ nodes: length: 9 span: start: 0 - length: 0 + length: 17 tag_span: start: 0 length: 17 line_offsets: - 0 -errors: - - UnclosedTag: for +errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap.new new file mode 100644 index 0000000..5718112 --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_for.snap.new @@ -0,0 +1,11 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 630 +expression: ast +snapshot_kind: text +--- +nodes: [] +line_offsets: + - 0 +errors: + - UnclosedTag: for diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap index 9097de9..8112979 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap @@ -17,11 +17,10 @@ nodes: length: 7 span: start: 0 - length: 0 + length: 24 tag_span: start: 0 length: 24 line_offsets: - 0 -errors: - - UnclosedTag: if +errors: [] diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap.new new file mode 100644 index 0000000..24d04bc --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__errors__parse_unclosed_django_if.snap.new @@ -0,0 +1,11 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 620 +expression: ast +snapshot_kind: text +--- +nodes: [] +line_offsets: + - 0 +errors: + - UnclosedTag: if diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap index 418fb0f..af965cc 100644 --- a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap @@ -54,73 +54,76 @@ nodes: span: start: 598 length: 23 - - Block: - block_type: Standard - name: if - bits: - - if - - user.is_staff - children: - - Text: - content: "\n Admin\n " - span: - start: 664 - length: 56 - - Block: - block_type: Branch - name: else - bits: [] - children: - - Text: - content: "\n User\n " - span: - start: 730 - length: 55 - span: - start: 621 - length: 0 - tag_span: - start: 621 - length: 16 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 764 - length: 10 - tag_span: - start: 621 - length: 16 - span: - start: 621 - length: 153 - tag_span: - start: 621 - length: 16 - - Text: - content: "\n " - span: - start: 788 - length: 13 - - Block: - block_type: Closing - name: endif - bits: [] - children: ~ - span: - start: 788 - length: 10 - tag_span: - start: 463 - length: 24 span: start: 463 - length: 335 + length: 24 tag_span: start: 463 length: 24 + - Block: + block_type: Standard + name: if + bits: + - if + - user.is_staff + children: + - Text: + content: "\n Admin\n " + span: + start: 664 + length: 56 + span: + start: 621 + length: 16 + tag_span: + start: 621 + length: 16 + - Block: + block_type: Standalone + name: else + bits: + - else + children: ~ + span: + start: 699 + length: 4 + tag_span: + start: 699 + length: 4 + - Text: + content: "\n User\n " + span: + start: 730 + length: 55 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 764 + length: 5 + tag_span: + start: 764 + length: 5 + - Text: + content: "\n " + span: + start: 788 + length: 13 + - Block: + block_type: Standalone + name: endif + bits: + - endif + children: ~ + span: + start: 788 + length: 5 + tag_span: + start: 788 + length: 5 - Text: content: "\n
\n \n" span: diff --git a/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap.new b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap.new new file mode 100644 index 0000000..f3220b4 --- /dev/null +++ b/crates/djls-template-ast/src/snapshots/djls_template_ast__parser__tests__full_templates__parse_full.snap.new @@ -0,0 +1,45 @@ +--- +source: crates/djls-template-ast/src/parser.rs +assertion_line: 712 +expression: ast +snapshot_kind: text +--- +nodes: + - Text: + content: "\n\n \n \n \n \n \n \n
\n " + span: + start: 0 + length: 463 +line_offsets: + - 0 + - 16 + - 23 + - 34 + - 66 + - 97 + - 134 + - 151 + - 191 + - 215 + - 241 + - 270 + - 298 + - 313 + - 331 + - 343 + - 354 + - 386 + - 451 + - 494 + - 532 + - 605 + - 644 + - 683 + - 710 + - 748 + - 776 + - 800 + - 815 + - 827 +errors: + - UnclosedTag: if