update specs

This commit is contained in:
Josh Thomas 2025-01-06 19:52:31 -06:00
parent c8c547a9db
commit 70b9958891
2 changed files with 40 additions and 251 deletions

View file

@ -35,17 +35,17 @@ 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
content: String,
span: Span,
},
Comment {
content: String, // The comment content
span: Span, // The position of the comment in the template
content: String,
span: Span,
},
Variable {
bits: Vec<String>, // Components of the variable path
filters: Vec<DjangoFilter>, // Filters applied to the variable
span: Span, // The position of the variable in the template
bits: Vec<String>,
filters: Vec<DjangoFilter>,
span: Span,
},
Block(Block),
}
@ -113,8 +113,8 @@ pub enum Block {
Block {
tag: Tag,
nodes: Vec<Node>,
closing: Option<Box<Block>>, // Contains Block::Closing if present
assignments: Option<Vec<Assignment>>, // Assignments declared within the tag (e.g., `{% with var=value %}`)
closing: Option<Box<Block>>,
assignments: Option<Vec<Assignment>>,
},
Branch {
tag: Tag,
@ -270,9 +270,8 @@ Tag Specifications (TagSpecs) define how tags are parsed and understood. They al
```toml
[package.module.path.tag_name] # Path where tag is registered, e.g., django.template.defaulttags
type = "block" | "tag" | "inclusion" | "variable"
type = "block" | "inclusion" | "tag" | "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
[[package.module.path.tag_name.args]]
@ -280,6 +279,35 @@ name = "argument_name"
required = true | false
```
### Tag Types
- `block`: Tags that wrap content and require a closing tag
```django
{% if condition %}content{% endif %}
{% for item in items %}content{% endfor %}
```
- `inclusion`: Tags that include or extend templates.
```django
{% extends "base.html" %}
{% include "partial.html" %}
```
- `tag`: Single tags that don't wrap content
```django
{% csrf_token %}
```
- `variable`: Tags that output a value directly
```django
{% cycle 'odd' 'even' %}
{% firstof var1 var2 var3 %}
```
### Configuration
- **Built-in TagSpecs**: The parser includes TagSpecs for Django's built-in tags and popular third-party tags.
@ -293,7 +321,6 @@ required = true | false
[django.template.defaulttags.if]
type = "block"
closing = "endif"
supports_assignment = false
branches = ["elif", "else"]
[[django.template.defaulttags.if.args]]
@ -306,174 +333,19 @@ required = true
```toml
[django.template.defaulttags.includes]
type = "inclusion"
supports_assignment = true
[[django.template.defaulttags.includes.args]]
name = "template_name"
required = true
```
#### Custom Tag Example
#### Custom Tag
```toml
[my_module.templatetags.my_tags.my_custom_tag]
type = "tag"
supports_assignment = true
{[my_module.templatetags.my_tags.my_custom_tag.args]]
name = "arg1"
required = false
```
### 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.

View file

@ -1,83 +0,0 @@
# djls-template-ast Tag Specifications
Configuration files defining template tag behavior for the Django Language Server Protocol.
## Schema
```toml
[package.module.path.tag_name] # Path where tag is registered, e.g., django.template.defaulttags
type = "block" | "tag" | "assignment" | "variable" # Required: Type of template tag
closing = "endtag" # Optional: Name of closing tag for block tags
intermediates = ["else", "elif"] # Optional: Allowed intermediate tags
[[package.module.path.tag_name.args]] # Optional: Arguments specification
name = "arg_name" # Name of the argument
required = true | false # Whether the argument is required
```
## Tag Types
- `block`: Tags that wrap content and require a closing tag
```django
{% if condition %}content{% endif %}
{% for item in items %}content{% endfor %}
```
- `tag`: Single tags that don't wrap content
```django
{% csrf_token %}
{% include "template.html" %}
```
- `assignment`: Tags that assign their output to a variable
```django
{% url 'view-name' as url_var %}
{% with total=business.employees.count %}
```
- `variable`: Tags that output a value directly
```django
{% cycle 'odd' 'even' %}
{% firstof var1 var2 var3 %}
```
## Argument Specification
Arguments can be either:
- Literal values that must match exactly (e.g., "in")
- Placeholders for variables (wrapped in curly braces, e.g., "{item}")
## Examples
```toml
[django.template.defaulttags.if]
type = "block"
closing = "endif"
intermediates = ["else", "elif"]
[[django.template.defaulttags.if.args]]
name = "condition"
required = true
[django.template.defaulttags.for]
type = "block"
closing = "endfor"
intermediates = ["empty"]
[[django.template.defaulttags.for.args]]
name = "{item}"
required = true
[[django.template.defaulttags.for.args]]
name = "in"
required = true
[[django.template.defaulttags.for.args]]
name = "{iterable}"
required = true
```