contain template variable errors

This commit is contained in:
danhanson committed 2026-09-14 21:50:16 -05:00
1 parent ccb80972b6
commit 7979890227
5 files changed
+2304 -2201

No files matched your search

+3 -1
View File
@@ -206,7 +206,9 @@ before its scanner token, so `{% else x %}` stays the `else` of its group, `{% e
its `if` (it used to leave the whole file an `ERROR`), and `{% static "a.css" "b.css" %}` is a `static_tag`. its `if` (it used to leave the whole file an `ERROR`), and `{% static "a.css" "b.css" %}` is a `static_tag`.
The node is a run of hidden `_unexpected_word`s, and three details make it work: The node is a run of hidden `_unexpected_word`s, and three details make it work:
- **Lexical precedence -1.** A loose word is `token(prec(-1, /[^\s%]+/))`. Tree-sitter weighs lexical - **Lexical precedence -1.** A loose word is `token(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/))`, which never holds `}}`:
lexer states are shared between parse states, so the token is tried inside `{{ }}` too, and after
`{{ x| ` it once swallowed the closing braces and left recovery nothing to close the variable with. Tree-sitter weighs lexical
precedence before match length, so a token an argument really takes wins wherever one is valid precedence before match length, so a token an argument really takes wins wherever one is valid
(`b|upper` stays a filtered value) and a loose word only matches where nothing else can. (`b|upper` stays a filtered value) and a loose word only matches where nothing else can.
- **Identifiers are words too.** Wherever a keyword is valid — after `{% for a in b`, where `reversed` may - **Identifiers are words too.** Wherever a keyword is valid — after `{% for a in b`, where `reversed` may
+7 -3
View File
@@ -1251,6 +1251,10 @@ const django = grammar({
* as an identifier to check for the keyword, so an identifier is a word * as an identifier to check for the keyword, so an identifier is a word
* here too, followed by whatever touches it ("a=2", "x.y|upper"). It is * here too, followed by whatever touches it ("a=2", "x.y|upper"). It is
* aliased so that queries/locals.scm does not take it for a reference. * aliased so that queries/locals.scm does not take it for a reference.
*
* A word never holds "}}": tree-sitter shares lexer states between parse
* states, so the token is tried inside {{ }} as well, and after "{{ x|" it
* would swallow the closing braces and leave recovery nothing to close with.
*/ */
_unexpected_word: ($) => _unexpected_word: ($) =>
prec.dynamic( prec.dynamic(
@@ -1258,10 +1262,10 @@ const django = grammar({
seq( seq(
SEP, SEP,
choice( choice(
token(prec(-1, /[^\s%]+/)), token(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/)),
seq( seq(
alias($.identifier, "unexpected_word"), alias($.identifier, "unexpected_word"),
optional(token.immediate(prec(-1, /[^\s%]+/))), optional(token.immediate(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/))),
), ),
), ),
), ),
@@ -1277,7 +1281,7 @@ const django = grammar({
-1, -1,
seq( seq(
alias($.identifier, "unexpected_word"), alias($.identifier, "unexpected_word"),
token.immediate(prec(-1, /[^\s%]+/)), token.immediate(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/)),
), ),
), ),
url_tag: ($) => url_tag: ($) =>
+3 -3
View File
@@ -7454,7 +7454,7 @@
"value": -1, "value": -1,
"content": { "content": {
"type": "PATTERN", "type": "PATTERN",
"value": "[^\\s%]+" "value": "(?:[^\\s%}]|\\}[^\\s%}])+"
} }
} }
}, },
@@ -7480,7 +7480,7 @@
"value": -1, "value": -1,
"content": { "content": {
"type": "PATTERN", "type": "PATTERN",
"value": "[^\\s%]+" "value": "(?:[^\\s%}]|\\}[^\\s%}])+"
} }
} }
}, },
@@ -7518,7 +7518,7 @@
"value": -1, "value": -1,
"content": { "content": {
"type": "PATTERN", "type": "PATTERN",
"value": "[^\\s%]+" "value": "(?:[^\\s%}]|\\}[^\\s%}])+"
} }
} }
} }
+2253 -2194
View File
File diff suppressed because it is too large. Load diff
+38
View File
@@ -608,3 +608,41 @@ a{{{ x }}
--- ---
==================
A Filter Pipe With No Filter Stays In Its Variable
==================
{{ oops| }}
{% if x == 1 %}
hello
{% endif %}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier))))
(ERROR))
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number))))))
(template
(content)))
(endif_tag))
(content))