mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-12-23 08:47:53 +00:00
fix template analysis after semantic crate refactor (#225)
Some checks are pending
lint / cargo-check (push) Waiting to run
release / test (push) Waiting to run
test / generate-matrix (push) Waiting to run
lint / pre-commit (push) Waiting to run
lint / rustfmt (push) Waiting to run
lint / clippy (push) Waiting to run
release / release (push) Blocked by required conditions
release / build (push) Waiting to run
test / Python , Django () (push) Blocked by required conditions
test / tests (push) Blocked by required conditions
zizmor 🌈 / zizmor latest via PyPI (push) Waiting to run
Some checks are pending
lint / cargo-check (push) Waiting to run
release / test (push) Waiting to run
test / generate-matrix (push) Waiting to run
lint / pre-commit (push) Waiting to run
lint / rustfmt (push) Waiting to run
lint / clippy (push) Waiting to run
release / release (push) Blocked by required conditions
release / build (push) Waiting to run
test / Python , Django () (push) Blocked by required conditions
test / tests (push) Blocked by required conditions
zizmor 🌈 / zizmor latest via PyPI (push) Waiting to run
This commit is contained in:
parent
6919f2e7a7
commit
1ceac4732f
11 changed files with 252 additions and 193 deletions
|
|
@ -2,12 +2,27 @@ use std::sync::Arc;
|
|||
|
||||
use djls_templates::Db as TemplateDb;
|
||||
use djls_workspace::Db as WorkspaceDb;
|
||||
use tower_lsp_server::lsp_types;
|
||||
|
||||
use crate::specs::TagSpecs;
|
||||
|
||||
/// Semantic database trait extending the template and workspace databases
|
||||
#[salsa::db]
|
||||
pub trait SemanticDb: TemplateDb + WorkspaceDb {
|
||||
/// Get the Django tag specifications for semantic analysis
|
||||
fn tag_specs(&self) -> Arc<TagSpecs>;
|
||||
}
|
||||
|
||||
#[salsa::accumulator]
|
||||
pub struct SemanticDiagnostic(pub lsp_types::Diagnostic);
|
||||
|
||||
impl From<SemanticDiagnostic> for lsp_types::Diagnostic {
|
||||
fn from(diagnostic: SemanticDiagnostic) -> Self {
|
||||
diagnostic.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SemanticDiagnostic> for lsp_types::Diagnostic {
|
||||
fn from(diagnostic: &SemanticDiagnostic) -> Self {
|
||||
diagnostic.0.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ pub mod validation;
|
|||
|
||||
pub use builtins::django_builtin_specs;
|
||||
pub use db::SemanticDb;
|
||||
pub use db::SemanticDiagnostic;
|
||||
use salsa::Accumulator;
|
||||
pub use snippets::generate_partial_snippet;
|
||||
pub use snippets::generate_snippet_for_tag;
|
||||
pub use snippets::generate_snippet_for_tag_with_end;
|
||||
|
|
@ -17,6 +19,7 @@ pub use specs::SimpleArgType;
|
|||
pub use specs::TagArg;
|
||||
pub use specs::TagSpec;
|
||||
pub use specs::TagSpecs;
|
||||
use tower_lsp_server::lsp_types;
|
||||
pub use validation::TagValidator;
|
||||
|
||||
pub enum TagType {
|
||||
|
|
@ -40,3 +43,46 @@ impl TagType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a Django template node list and return validation errors.
|
||||
///
|
||||
/// This function runs the TagValidator on the parsed node list to check for:
|
||||
/// - Unclosed block tags
|
||||
/// - Mismatched tag pairs
|
||||
/// - Orphaned intermediate tags
|
||||
/// - Invalid argument counts
|
||||
/// - Unmatched block names
|
||||
#[salsa::tracked]
|
||||
pub fn validate_nodelist(db: &dyn SemanticDb, nodelist: djls_templates::NodeList<'_>) {
|
||||
if nodelist.nodelist(db).is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let validation_errors = TagValidator::new(db, nodelist).validate();
|
||||
|
||||
let line_offsets = nodelist.line_offsets(db);
|
||||
for error in validation_errors {
|
||||
let code = error.diagnostic_code();
|
||||
let range = error
|
||||
.span()
|
||||
.map(|(start, length)| {
|
||||
let span = djls_templates::nodelist::Span::new(start, length);
|
||||
span.to_lsp_range(line_offsets)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let diagnostic = lsp_types::Diagnostic {
|
||||
range,
|
||||
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
|
||||
code: Some(lsp_types::NumberOrString::String(code.to_string())),
|
||||
code_description: None,
|
||||
source: Some("Django Language Server".to_string()),
|
||||
message: error.to_string(),
|
||||
related_information: None,
|
||||
tags: None,
|
||||
data: None,
|
||||
};
|
||||
|
||||
SemanticDiagnostic(diagnostic).accumulate(db);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@
|
|||
//! ## Architecture
|
||||
//!
|
||||
//! The `TagValidator` follows the same pattern as the Parser and Lexer,
|
||||
//! maintaining minimal state and walking through the AST to accumulate errors.
|
||||
//! maintaining minimal state and walking through the node list to accumulate errors.
|
||||
|
||||
use djls_templates::ast::Node;
|
||||
use djls_templates::ast::NodeListError;
|
||||
use djls_templates::ast::Span;
|
||||
use djls_templates::ast::TagBit;
|
||||
use djls_templates::ast::TagName;
|
||||
use djls_templates::nodelist::Node;
|
||||
use djls_templates::nodelist::NodeListError;
|
||||
use djls_templates::nodelist::Span;
|
||||
use djls_templates::nodelist::TagBit;
|
||||
use djls_templates::nodelist::TagName;
|
||||
use djls_templates::NodeList;
|
||||
|
||||
use crate::db::SemanticDb;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ use std::future::Future;
|
|||
use std::sync::Arc;
|
||||
|
||||
use djls_project::Db as ProjectDb;
|
||||
use djls_templates::analyze_template;
|
||||
use djls_semantic::validate_nodelist;
|
||||
use djls_semantic::SemanticDiagnostic;
|
||||
use djls_templates::parse_template;
|
||||
use djls_templates::TemplateDiagnostic;
|
||||
use djls_workspace::paths;
|
||||
use djls_workspace::FileKind;
|
||||
|
|
@ -96,14 +98,26 @@ impl DjangoLanguageServer {
|
|||
let file = session.get_or_create_file(&path);
|
||||
|
||||
session.with_db(|db| {
|
||||
// Parse and validate the template (triggers accumulation)
|
||||
// This should be a cheap call since salsa should cache the function
|
||||
// call, but we may need to revisit if that assumption is incorrect
|
||||
let _ast = analyze_template(db, file);
|
||||
let Some(nodelist) = parse_template(db, file) else {
|
||||
// If parsing failed completely, just return syntax errors
|
||||
return parse_template::accumulated::<TemplateDiagnostic>(db, file)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
};
|
||||
|
||||
let diagnostics = analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
validate_nodelist(db, nodelist);
|
||||
|
||||
diagnostics.into_iter().map(Into::into).collect()
|
||||
let syntax_diagnostics =
|
||||
parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
let semantic_diagnostics =
|
||||
validate_nodelist::accumulated::<SemanticDiagnostic>(db, nodelist);
|
||||
|
||||
syntax_diagnostics
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.chain(semantic_diagnostics.into_iter().map(Into::into))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
|
@ -240,7 +254,6 @@ impl LanguageServer for DjangoLanguageServer {
|
|||
})
|
||||
.await;
|
||||
|
||||
// Publish diagnostics for template files
|
||||
if let Some((url, version)) = url_version {
|
||||
self.publish_diagnostics(&url, Some(version)).await;
|
||||
}
|
||||
|
|
@ -262,7 +275,6 @@ impl LanguageServer for DjangoLanguageServer {
|
|||
})
|
||||
.await;
|
||||
|
||||
// Publish diagnostics for template files
|
||||
if let Some((url, version)) = url_version {
|
||||
self.publish_diagnostics(&url, version).await;
|
||||
}
|
||||
|
|
@ -426,14 +438,25 @@ impl LanguageServer for DjangoLanguageServer {
|
|||
return vec![];
|
||||
};
|
||||
|
||||
// Parse and validate the template (triggers accumulation)
|
||||
let _ast = analyze_template(db, file);
|
||||
let Some(nodelist) = parse_template(db, file) else {
|
||||
return parse_template::accumulated::<TemplateDiagnostic>(db, file)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
};
|
||||
|
||||
// Get accumulated diagnostics directly - they're already LSP diagnostics!
|
||||
let diagnostics = analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
validate_nodelist(db, nodelist);
|
||||
|
||||
// Convert from TemplateDiagnostic wrapper to lsp_types::Diagnostic
|
||||
diagnostics.into_iter().map(Into::into).collect()
|
||||
let syntax_diagnostics =
|
||||
parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
let semantic_diagnostics =
|
||||
validate_nodelist::accumulated::<SemanticDiagnostic>(db, nodelist);
|
||||
|
||||
syntax_diagnostics
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.chain(semantic_diagnostics.into_iter().map(Into::into))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -14,30 +14,30 @@
|
|||
//! ## Key Components
|
||||
//!
|
||||
//! - [`Db`]: Database trait extending the workspace database
|
||||
//! - [`analyze_template`]: Main entry point for template analysis
|
||||
//! - [`parse_template`]: Main entry point for template parsing
|
||||
//! - [`TemplateDiagnostic`]: Accumulator for collecting LSP diagnostics
|
||||
//!
|
||||
//! ## Incremental Computation
|
||||
//!
|
||||
//! When a template file changes:
|
||||
//! 1. Salsa invalidates the cached AST for that file
|
||||
//! 2. Next access to `analyze_template` triggers reparse
|
||||
//! 2. Next access to `parse_template` triggers reparse
|
||||
//! 3. Diagnostics are accumulated during parse/validation
|
||||
//! 4. Other files remain cached unless they also changed
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```ignore
|
||||
//! // Analyze a template and get its AST
|
||||
//! let ast = analyze_template(db, file);
|
||||
//! // Parse a template and get its AST
|
||||
//! let nodelist = parse_template(db, file);
|
||||
//!
|
||||
//! // Retrieve accumulated diagnostics
|
||||
//! let diagnostics = analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//! let diagnostics = parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//!
|
||||
//! // Get diagnostics for all workspace files
|
||||
//! for file in workspace.files() {
|
||||
//! let _ = analyze_template(db, file); // Trigger analysis
|
||||
//! let diags = analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//! let _ = parse_template(db, file); // Trigger parsing
|
||||
//! let diags = parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//! // Process diagnostics...
|
||||
//! }
|
||||
//! ```
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::NodeListError;
|
||||
use crate::nodelist::NodeListError;
|
||||
use crate::parser::ParserError;
|
||||
|
||||
#[derive(Clone, Debug, Error, PartialEq, Eq, Serialize)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::ast::LineOffsets;
|
||||
use crate::db::Db as TemplateDb;
|
||||
use crate::nodelist::LineOffsets;
|
||||
use crate::tokens::Token;
|
||||
use crate::tokens::TokenContent;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! This crate provides comprehensive support for Django template files including:
|
||||
//! - Lexical analysis and tokenization
|
||||
//! - Parsing into an Abstract Syntax Tree (AST)
|
||||
//! - Parsing into a flat node list
|
||||
//! - Validation using configurable tag specifications
|
||||
//! - LSP diagnostic generation with Salsa integration
|
||||
//!
|
||||
|
|
@ -11,13 +11,13 @@
|
|||
//! The system uses a multi-stage pipeline:
|
||||
//!
|
||||
//! 1. **Lexing**: Template text is tokenized into Django constructs (tags, variables, text)
|
||||
//! 2. **Parsing**: Tokens are parsed into a structured AST
|
||||
//! 3. **Validation**: The AST is validated using the visitor pattern
|
||||
//! 2. **Parsing**: Tokens are parsed into a flat node list
|
||||
//! 3. **Validation**: The node list is validated using the visitor pattern
|
||||
//! 4. **Diagnostics**: Errors are converted to LSP diagnostics via Salsa accumulators
|
||||
//!
|
||||
//! ## Key Components
|
||||
//!
|
||||
//! - [`ast`]: AST node definitions and visitor pattern implementation
|
||||
//! - [`nodelist`]: Node list definitions and structure
|
||||
//! - [`db`]: Salsa database integration for incremental computation
|
||||
//! - [`validation`]: Validation rules using the visitor pattern
|
||||
//! - [`tagspecs`]: Django tag specifications for validation
|
||||
|
|
@ -32,43 +32,40 @@
|
|||
//!
|
||||
//! ```ignore
|
||||
//! // For LSP integration with Salsa (primary usage):
|
||||
//! use djls_templates::db::{analyze_template, TemplateDiagnostic};
|
||||
//! use djls_templates::{parse_template, TemplateDiagnostic};
|
||||
//!
|
||||
//! let ast = analyze_template(db, file);
|
||||
//! let diagnostics = analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//! let nodelist = parse_template(db, file);
|
||||
//! let diagnostics = parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
//!
|
||||
//! // For direct parsing (testing/debugging):
|
||||
//! use djls_templates::{Lexer, Parser};
|
||||
//!
|
||||
//! let tokens = Lexer::new(source).tokenize()?;
|
||||
//! let mut parser = Parser::new(tokens);
|
||||
//! let (ast, errors) = parser.parse()?;
|
||||
//! let (nodelist, errors) = parser.parse()?;
|
||||
//! ```
|
||||
|
||||
pub mod ast;
|
||||
pub mod db;
|
||||
mod error;
|
||||
mod lexer;
|
||||
pub mod nodelist;
|
||||
mod parser;
|
||||
mod tokens;
|
||||
|
||||
use ast::LineOffsets;
|
||||
pub use ast::NodeList;
|
||||
pub use db::Db;
|
||||
pub use db::TemplateDiagnostic;
|
||||
use djls_workspace::db::SourceFile;
|
||||
use djls_workspace::FileKind;
|
||||
pub use error::TemplateError;
|
||||
pub use lexer::Lexer;
|
||||
use nodelist::LineOffsets;
|
||||
pub use nodelist::NodeList;
|
||||
pub use parser::Parser;
|
||||
pub use parser::ParserError;
|
||||
use salsa::Accumulator;
|
||||
use tokens::TokenStream;
|
||||
|
||||
/// Lex a template file into tokens.
|
||||
///
|
||||
/// This is the first phase of template processing. It tokenizes the source text
|
||||
/// into Django-specific tokens (tags, variables, text, etc.).
|
||||
#[salsa::tracked]
|
||||
fn lex_template(db: &dyn Db, file: SourceFile) -> TokenStream<'_> {
|
||||
if file.kind(db) != FileKind::Template {
|
||||
|
|
@ -82,53 +79,55 @@ fn lex_template(db: &dyn Db, file: SourceFile) -> TokenStream<'_> {
|
|||
TokenStream::new(db, tokens, line_offsets)
|
||||
}
|
||||
|
||||
/// Parse tokens into an AST.
|
||||
/// Parse a Django template file and accumulate diagnostics.
|
||||
///
|
||||
/// This is the second phase of template processing. It takes the token stream
|
||||
/// from lexing and builds an Abstract Syntax Tree.
|
||||
/// Diagnostics can be retrieved using:
|
||||
/// ```ignore
|
||||
/// let diagnostics =
|
||||
/// parse_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
/// ```
|
||||
#[salsa::tracked]
|
||||
fn parse_template(db: &dyn Db, file: SourceFile) -> NodeList<'_> {
|
||||
let token_stream = lex_template(db, file);
|
||||
|
||||
// Check if lexing produced no tokens (likely due to an error)
|
||||
if token_stream.stream(db).is_empty() {
|
||||
// Return empty AST for error recovery
|
||||
let empty_nodelist = Vec::new();
|
||||
let empty_offsets = LineOffsets::default();
|
||||
return NodeList::new(db, empty_nodelist, empty_offsets);
|
||||
pub fn parse_template(db: &dyn Db, file: SourceFile) -> Option<NodeList<'_>> {
|
||||
if file.kind(db) != FileKind::Template {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Parser needs the TokenStream<'db>
|
||||
match Parser::new(db, token_stream).parse() {
|
||||
Ok((ast, errors)) => {
|
||||
// Accumulate parser errors
|
||||
let token_stream = lex_template(db, file);
|
||||
|
||||
if token_stream.stream(db).is_empty() {
|
||||
let empty_nodelist = Vec::new();
|
||||
let empty_offsets = LineOffsets::default();
|
||||
return Some(NodeList::new(db, empty_nodelist, empty_offsets));
|
||||
}
|
||||
|
||||
let nodelist = match Parser::new(db, token_stream).parse() {
|
||||
Ok((nodelist, errors)) => {
|
||||
for error in errors {
|
||||
let template_error = TemplateError::Parser(error.to_string());
|
||||
accumulate_error(db, &template_error, ast.line_offsets(db));
|
||||
accumulate_error(db, &template_error, nodelist.line_offsets(db));
|
||||
}
|
||||
ast
|
||||
nodelist
|
||||
}
|
||||
Err(err) => {
|
||||
// Critical parser error
|
||||
let template_error = TemplateError::Parser(err.to_string());
|
||||
let empty_offsets = LineOffsets::default();
|
||||
accumulate_error(db, &template_error, &empty_offsets);
|
||||
|
||||
// Return empty AST
|
||||
let empty_nodelist = Vec::new();
|
||||
let empty_offsets = LineOffsets::default();
|
||||
NodeList::new(db, empty_nodelist, empty_offsets)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some(nodelist)
|
||||
}
|
||||
|
||||
/// Helper function to convert errors to LSP diagnostics and accumulate
|
||||
fn accumulate_error(db: &dyn Db, error: &TemplateError, line_offsets: &LineOffsets) {
|
||||
let code = error.diagnostic_code();
|
||||
let range = error
|
||||
.span()
|
||||
.map(|(start, length)| {
|
||||
let span = crate::ast::Span::new(start, length);
|
||||
let span = crate::nodelist::Span::new(start, length);
|
||||
span.to_lsp_range(line_offsets)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
|
@ -152,30 +151,3 @@ fn accumulate_error(db: &dyn Db, error: &TemplateError, line_offsets: &LineOffse
|
|||
|
||||
TemplateDiagnostic(diagnostic).accumulate(db);
|
||||
}
|
||||
|
||||
/// Analyze a Django template file - parse and accumulate diagnostics.
|
||||
///
|
||||
/// This is the PRIMARY function for template processing. It's a Salsa tracked function
|
||||
/// that orchestrates the parsing phases of template processing:
|
||||
/// 1. Lexing (tokenization)
|
||||
/// 2. Parsing (AST construction)
|
||||
///
|
||||
/// Validation has been moved to the djls-semantic crate for semantic analysis.
|
||||
///
|
||||
/// Each phase is independently cached by Salsa, allowing for fine-grained
|
||||
/// incremental computation.
|
||||
///
|
||||
/// The function returns the parsed AST (or None for non-template files).
|
||||
///
|
||||
/// Diagnostics can be retrieved using:
|
||||
/// ```ignore
|
||||
/// let diagnostics =
|
||||
/// analyze_template::accumulated::<TemplateDiagnostic>(db, file);
|
||||
/// ```
|
||||
#[salsa::tracked]
|
||||
pub fn analyze_template(db: &dyn Db, file: SourceFile) -> Option<NodeList<'_>> {
|
||||
if file.kind(db) != FileKind::Template {
|
||||
return None;
|
||||
}
|
||||
Some(parse_template(db, file))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::FilterName;
|
||||
use crate::ast::LineOffsets;
|
||||
use crate::ast::Node;
|
||||
use crate::ast::NodeList;
|
||||
use crate::ast::NodeListError;
|
||||
use crate::ast::Span;
|
||||
use crate::ast::TagBit;
|
||||
use crate::ast::TagName;
|
||||
use crate::ast::VariableName;
|
||||
use crate::db::Db as TemplateDb;
|
||||
use crate::nodelist::FilterName;
|
||||
use crate::nodelist::LineOffsets;
|
||||
use crate::nodelist::Node;
|
||||
use crate::nodelist::NodeList;
|
||||
use crate::nodelist::NodeListError;
|
||||
use crate::nodelist::Span;
|
||||
use crate::nodelist::TagBit;
|
||||
use crate::nodelist::TagName;
|
||||
use crate::nodelist::VariableName;
|
||||
use crate::tokens::Token;
|
||||
use crate::tokens::TokenStream;
|
||||
|
||||
|
|
@ -51,9 +51,9 @@ impl<'db> Parser<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
let ast = NodeList::new(self.db, nodelist, self.line_offsets.clone());
|
||||
let nodelist = NodeList::new(self.db, nodelist, self.line_offsets.clone());
|
||||
|
||||
Ok((ast, std::mem::take(&mut self.errors)))
|
||||
Ok((nodelist, std::mem::take(&mut self.errors)))
|
||||
}
|
||||
|
||||
fn next_node(&mut self) -> Result<Node<'db>, ParserError> {
|
||||
|
|
@ -287,7 +287,7 @@ pub enum ParseError {
|
|||
#[error("Stream error: {kind:?}")]
|
||||
StreamError { kind: StreamError },
|
||||
|
||||
#[error("AST error: {0}")]
|
||||
#[error("Node list error: {0}")]
|
||||
NodeList(#[from] NodeListError),
|
||||
}
|
||||
|
||||
|
|
@ -357,12 +357,12 @@ mod tests {
|
|||
let (tokens, line_offsets) = Lexer::new(db, source).tokenize();
|
||||
let token_stream = TokenStream::new(db, tokens, line_offsets);
|
||||
let mut parser = Parser::new(db, token_stream);
|
||||
let (ast, _) = parser.parse().unwrap();
|
||||
ast
|
||||
let (nodelist, _) = parser.parse().unwrap();
|
||||
nodelist
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
struct TestAst {
|
||||
struct TestNodeList {
|
||||
nodelist: Vec<TestNode>,
|
||||
line_offsets: Vec<u32>,
|
||||
}
|
||||
|
|
@ -413,10 +413,13 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_ast_for_testing(ast: NodeList<'_>, db: &dyn crate::db::Db) -> TestAst {
|
||||
TestAst {
|
||||
nodelist: convert_nodelist_for_testing(ast.nodelist(db), db),
|
||||
line_offsets: ast.line_offsets(db).0.clone(),
|
||||
fn convert_nodelist_for_testing_wrapper(
|
||||
nodelist: NodeList<'_>,
|
||||
db: &dyn crate::db::Db,
|
||||
) -> TestNodeList {
|
||||
TestNodeList {
|
||||
nodelist: convert_nodelist_for_testing(nodelist.nodelist(db), db),
|
||||
line_offsets: nodelist.line_offsets(db).0.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -432,9 +435,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<!DOCTYPE html>".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -442,9 +445,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<div class=\"container\">Hello</div>".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -452,9 +455,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<input type=\"text\" />".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -466,9 +469,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{{ user.name }}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -476,9 +479,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{{ user.name|title }}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -486,9 +489,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{{ value|default:'nothing'|title|upper }}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -496,9 +499,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{% if user.is_authenticated %}Welcome{% endif %}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -507,9 +510,9 @@ mod tests {
|
|||
let source =
|
||||
"{% for item in items %}{{ item }}{% empty %}No items{% endfor %}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -518,9 +521,9 @@ mod tests {
|
|||
let source = "{% if x > 0 %}Positive{% elif x < 0 %}Negative{% else %}Zero{% endif %}"
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -528,9 +531,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{% url 'view-name' as view %}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -540,9 +543,9 @@ mod tests {
|
|||
"{% for item in items %}{% if item.active %}{{ item.name }}{% endif %}{% endfor %}"
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -563,9 +566,9 @@ mod tests {
|
|||
{% endif %}!"
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -584,9 +587,9 @@ mod tests {
|
|||
</script>"#
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -604,9 +607,9 @@ mod tests {
|
|||
</style>"#
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -618,9 +621,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<!-- HTML comment -->{# Django comment #}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -632,9 +635,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = " hello".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -642,9 +645,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "\n hello".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -652,9 +655,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "hello ".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -662,9 +665,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "hello \n".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -676,9 +679,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<div>".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -686,9 +689,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{% if user.is_authenticated %}Welcome".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -696,9 +699,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "{% for item in items %}{{ item.name }}".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -706,9 +709,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<script>console.log('test');".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -716,9 +719,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "<style>body { color: blue; ".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
|
||||
// TODO: fix this so we can test against errors returned by parsing
|
||||
|
|
@ -738,7 +741,7 @@ mod tests {
|
|||
// </div>"#;
|
||||
// let tokens = Lexer::new(source).tokenize().unwrap();
|
||||
// let mut parser = create_test_parser(tokens);
|
||||
// let (ast, errors) = parser.parse().unwrap();
|
||||
// let (nodelist, errors) = parser.parse().unwrap();
|
||||
// let nodelist = convert_nodelist_for_testing(ast.nodelist(parser.db), parser.db);
|
||||
// insta::assert_yaml_snapshot!(nodelist);
|
||||
// assert_eq!(errors.len(), 1);
|
||||
|
|
@ -784,9 +787,9 @@ mod tests {
|
|||
</html>"#
|
||||
.to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let test_ast = convert_ast_for_testing(ast, &db);
|
||||
insta::assert_yaml_snapshot!(test_ast);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
let test_nodelist = convert_nodelist_for_testing_wrapper(nodelist, &db);
|
||||
insta::assert_yaml_snapshot!(test_nodelist);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -798,9 +801,9 @@ mod tests {
|
|||
let db = TestDatabase::new();
|
||||
let source = "line1\nline2".to_string();
|
||||
let template = TestTemplate::new(&db, source);
|
||||
let ast = parse_test_template(&db, template);
|
||||
let nodelist = parse_test_template(&db, template);
|
||||
|
||||
let offsets = ast.line_offsets(&db);
|
||||
let offsets = nodelist.line_offsets(&db);
|
||||
assert_eq!(offsets.position_to_line_col(0), (1, 0)); // Start of line 1
|
||||
assert_eq!(offsets.position_to_line_col(6), (2, 0)); // Start of line 2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::ast::LineOffsets;
|
||||
use crate::db::Db as TemplateDb;
|
||||
use crate::nodelist::LineOffsets;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, salsa::Update)]
|
||||
pub enum Token<'db> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue