Compare commits

...
3 Commits
Author SHA1 Message Date
danhanson aceae012fa improved scope rules 2026-09-08 19:00:19 -05:00
danhanson ca01dd5f95 add clauses to support autocomplete better 2026-09-08 14:27:57 -05:00
danhanson 06aa8706c8 fix for and add reversed argument 2026-09-08 02:42:50 -05:00
24 changed files with 49283 additions and 97907 deletions

No files matched your search

+48 -1
View File
@@ -72,6 +72,45 @@ Consequences worth knowing before editing a rule:
part boundary is `SEP` instead of the meaningful token, so LR(1) loses its discriminator. That is why
`predicate` and `[$.library, $.load]` are listed there.
### A tag group is a sequence of clauses
Every group is split so that each tag that opens a body owns a node of its own:
```js
if_clause: ($) => seq(block("if", part($.predicate)), optional($.template)),
if_group: ($) =>
seq($.if_clause, repeat($.elif_clause), optional($.else_clause), block("endif")),
```
**A clause is complete without the closing tag, and that is the point.** While a template is being edited
the closer usually is not there yet, and a group rule cannot be reduced without it — so `{% if a %}{% x %}`
used to produce a top-level `ERROR` with the body's `template` parented to nothing that says which tag
opened it. A clause reduces anyway, so it survives error recovery and tooling can walk up from the cursor
to find the enclosing tag. That is what makes completion of `{% elif %}`, `{% else %}`, `{% empty %}` and
the end tags possible.
The names themselves need no hand-maintained list either: in the generated `src/node-types.json`, a group's
`children` are its clause types and its own `tag:` field holds the end tag, so the candidates for a group
are its `tag:` plus the `tag:` of each clause it can hold. For `if_group` that is `endif` from the group and
`if`, `elif`, `else` from its three clauses. Going the other way — from a clause the cursor sits in to the
groups that can hold it — is the same table read backwards, which is what an unterminated tag needs, since
the group node does not exist yet.
Do not reach for lookahead to solve that instead: `ts_language_next_state` follows only the shift on `{%`,
which lands in the "a tag starts here" state no matter how the tree is shaped.
`else_clause` is shared by `if_group` and `ifchanged_group`. `comment_group` and `verbatim_group` have no
clause — their bodies are raw scanner text that admits no tags — and neither does `blocktranslate_group`,
whose body is `_translate_body`.
**Every clause needs a `conflicts` entry.** The generator offers a left associativity instead; taking it is
what broke `{% for %}` in `06aa870`, where `prec.left` resolved the shift/reduce on `{%` statically and
silently discarded the parse in which the body continues, so every tag inside a loop became an `ERROR`.
Sharing the clauses also made the table markedly smaller — 7,200 states and a 515KB library became 5,150
and 383KB — for the same reason hoisting `blocktranslate`'s pieces into hidden rules did: the automaton
stops duplicating the body states in every group's inline context.
### Filter pipes differ by context
Django parses everything between `{{` and `}}` — and the entire remainder of `{% filter %}` — as one
@@ -183,7 +222,11 @@ captured through its field instead (`(filter name: (identifier))`, `(custom_tag
`queries/libraries.scm` is not a query editors run themselves; it is data for a linter, and every tag or
filter added from a library belongs in it. `queries/locals.scm` relies on the `variable:` field to tell a
binding from a reference — another reason to keep `asVariable` uniform.
binding from a reference — another reason to keep `asVariable` uniform. `{% blocktranslate %}`'s `asvar`
target carries an `asvar:` field instead, because it is written inside the tag but assigned outside it:
the scope on `blocktranslate_group` confines the `with` and `count` bindings, and would swallow `asvar`
too if it were spelled the same way. Defining it in the surrounding scope is left to the tool, since a
locals query cannot let a definition out of the scope that contains it.
## Testing
@@ -242,6 +285,10 @@ documentation or memory. Both checks are worth repeating whenever tags, filters
`{% cache 500 sidebar.name %}` names a fragment there, `{% cache 500 using="c" %}` names a fragment
called `using="c"` rather than choosing a cache, and `{% cache 500 sidebar using= %}` takes an empty
name. All three are errors here.
- `do_for` reads `reversed` off the end of the tag before it looks for `in`, so `{% for x in reversed %}`
is an error there and a loop over a variable named `reversed` here. Writing the flag as well
(`{% for x in reversed reversed %}`) is accepted by both, which is why the name is not simply reserved
in that position.
- `do_translate` refuses `as` and `noop` as the value of its `context` option, so
`{% trans "hi" context noop %}` is an error there and a context named by the variable `noop` here.
- `partial`/`partialdef` are Django builtins as of Django 6; `elif`/`else`/`empty` are modelled as parts of
+66 -58
View File
@@ -291,6 +291,23 @@ const django = grammar({
// after it settles
conflicts: ($) => [
[$.template],
[$.autoescape_clause],
[$.block_clause],
[$.cache_clause],
[$.elif_clause],
[$.else_clause],
[$.empty_clause],
[$.filter_clause],
[$.for_clause],
[$.if_clause],
[$.ifchanged_clause],
[$.language_clause],
[$.localize_clause],
[$.localtime_clause],
[$.partialdef_clause],
[$.spaceless_clause],
[$.timezone_clause],
[$.with_clause],
[$.predicate],
[$.library, $.load],
[$._translate_option],
@@ -481,19 +498,13 @@ const django = grammar({
optional(seq(":", field("argument", $.value))),
),
),
autoescape_group: ($) =>
seq(
block("autoescape", part(choice("on", "off"))),
optional($.template),
block("endautoescape"),
),
autoescape_clause: ($) =>
seq(block("autoescape", part(choice("on", "off"))), optional($.template)),
autoescape_group: ($) => seq($.autoescape_clause, block("endautoescape")),
// the scanner takes the whitespace before a name it has to match itself
block_group: ($) =>
seq(
block("block", field("name", $.push_block)),
optional($.template),
block("endblock", $.pop_block),
),
block_clause: ($) =>
seq(block("block", field("name", $.push_block)), optional($.template)),
block_group: ($) => seq($.block_clause, block("endblock", $.pop_block)),
blocktranslate_group: ($) =>
choice(
...["blocktrans", "blocktranslate"].map((name) =>
@@ -531,7 +542,10 @@ const django = grammar({
seq(part("with"), $._tag_kwargs),
part("context", $.filtered_value),
part("trimmed"),
part("asvar", field("variable", $.identifier)),
// asvar names where the rendered text is stored, which is read
// after the block rather than inside it, so it is not a binding of
// the block the way "with" and "count" are
part("asvar", field("asvar", $.identifier)),
),
),
_translate_count: ($) =>
@@ -560,7 +574,7 @@ const django = grammar({
),
),
),
cache_group: ($) =>
cache_clause: ($) =>
seq(
block(
"cache",
@@ -570,8 +584,8 @@ const django = grammar({
optional(part(seq("using=", $.filtered_value))),
),
optional($.template),
block("endcache"),
),
cache_group: ($) => seq($.cache_clause, block("endcache")),
comment_group: ($) =>
seq(
block("comment", optional(part($.string))),
@@ -602,7 +616,7 @@ const django = grammar({
),
debug: ($) => block("debug"),
extends: ($) => block("extends", part($.filtered_value)),
filter_group: ($) =>
filter_clause: ($) =>
seq(
// the filter tag hands Django the rest of its text as one expression
// rather than as split arguments, so pipes here take whitespace just
@@ -612,16 +626,15 @@ const django = grammar({
part(alias($._filter_expression_spaced, $.filter_expression)),
),
optional($.template),
block("endfilter"),
),
filter_group: ($) => seq($.filter_clause, block("endfilter")),
firstof: ($) =>
block(
"firstof",
repeat1(part($.filtered_value)),
optional(asVariable($)),
),
for_scope: ($) =>
prec.left(
for_clause: ($) =>
seq(
block(
"for",
@@ -636,16 +649,15 @@ const django = grammar({
),
part("in"),
part($.filtered_value),
// do_for reads this off the end and takes the sequence from the bit
// before it, so it comes last or not at all
optional(part("reversed")),
),
optional($.template),
),
),
empty_clause: ($) => seq(block("empty"), optional($.template)),
for_group: ($) =>
seq(
$.for_scope,
optional(seq(block("empty"), optional($.template))),
block("endfor"),
),
seq($.for_clause, optional($.empty_clause), block("endfor")),
/**
* django.templatetags.i18n. Each of these requires exactly "as <name>",
* or "for <expression> as <name>", and rejects anything further.
@@ -672,21 +684,25 @@ const django = grammar({
get_media_prefix: ($) => block("get_media_prefix", optional(asVariable($))),
get_static_prefix: ($) =>
block("get_static_prefix", optional(asVariable($))),
if_clause: ($) => seq(block("if", part($.predicate)), optional($.template)),
elif_clause: ($) =>
seq(block("elif", part($.predicate)), optional($.template)),
/** Shared by if_group and ifchanged_group, which spell it the same way. */
else_clause: ($) => seq(block("else"), optional($.template)),
if_group: ($) =>
seq(
block("if", part($.predicate)),
optional($.template),
repeat(seq(block("elif", part($.predicate)), optional($.template))),
optional(seq(block("else"), optional($.template))),
$.if_clause,
repeat($.elif_clause),
optional($.else_clause),
block("endif"),
),
ifchanged_group: ($) =>
ifchanged_clause: ($) =>
seq(
block("ifchanged", repeat(part($.filtered_value))),
optional($.template),
optional(seq(block("else"), optional($.template))),
block("endifchanged"),
),
ifchanged_group: ($) =>
seq($.ifchanged_clause, optional($.else_clause), block("endifchanged")),
/**
* do_include takes "with" and "only" in either order and refuses one it has
* already been given, and "with" needs at least one argument. It calls
@@ -703,12 +719,9 @@ const django = grammar({
),
),
/** django.templatetags.i18n. The language tag takes the one argument. */
language_group: ($) =>
seq(
block("language", part($.filtered_value)),
optional($.template),
block("endlanguage"),
),
language_clause: ($) =>
seq(block("language", part($.filtered_value)), optional($.template)),
language_group: ($) => seq($.language_clause, block("endlanguage")),
library: ($) => seq($.identifier, optional(seq(".", $.identifier))),
load: ($) =>
block(
@@ -722,19 +735,19 @@ const django = grammar({
* django.templatetags.l10n. The argument is optional and localize_tag
* rejects anything but "on" or "off", including a second word.
*/
localize_group: ($) =>
localize_clause: ($) =>
seq(
block("localize", optional(part(choice("on", "off")))),
optional($.template),
block("endlocalize"),
),
localize_group: ($) => seq($.localize_clause, block("endlocalize")),
/** django.templatetags.tz, taking "on" or "off" as localize does. */
localtime_group: ($) =>
localtime_clause: ($) =>
seq(
block("localtime", optional(part(choice("on", "off")))),
optional($.template),
block("endlocaltime"),
),
localtime_group: ($) => seq($.localtime_clause, block("endlocaltime")),
lorem: ($) =>
block(
"lorem",
@@ -744,12 +757,13 @@ const django = grammar({
),
now: ($) => block("now", part($.string), optional(asVariable($))),
partial: ($) => block("partial", part($.identifier)),
partialdef_group: ($) =>
partialdef_clause: ($) =>
seq(
block("partialdef", $.push_partial, optional(part("inline"))),
optional($.template),
block("endpartialdef", $.pop_partial),
),
partialdef_group: ($) =>
seq($.partialdef_clause, block("endpartialdef", $.pop_partial)),
// querystring(context, *args, **kwargs)
query_string: ($) => simpleTag($, "querystring"),
regroup: ($) =>
@@ -761,8 +775,8 @@ const django = grammar({
optional(asVariable($)),
),
reset_cycle: ($) => block("resetcycle", optional(part($.identifier))),
spaceless_group: ($) =>
seq(block("spaceless"), optional($.template), block("endspaceless")),
spaceless_clause: ($) => seq(block("spaceless"), optional($.template)),
spaceless_group: ($) => seq($.spaceless_clause, block("endspaceless")),
static: ($) =>
block("static", part($.filtered_value), optional(asVariable($))),
template_tag_block: ($) =>
@@ -782,12 +796,9 @@ const django = grammar({
),
),
/** django.templatetags.tz. timezone_tag takes the one argument. */
timezone_group: ($) =>
seq(
block("timezone", part($.filtered_value)),
optional($.template),
block("endtimezone"),
),
timezone_clause: ($) =>
seq(block("timezone", part($.filtered_value)), optional($.template)),
timezone_group: ($) => seq($.timezone_clause, block("endtimezone")),
/**
* django.templatetags.i18n, registered under both spellings. do_translate
* pops its options off one at a time and refuses one it has already seen,
@@ -831,12 +842,9 @@ const django = grammar({
part($.filtered_value),
optional(asVariable($)),
),
with_group: ($) =>
seq(
block("with", $._tag_open, $._tag_kwargs),
optional($.template),
block("endwith"),
),
with_clause: ($) =>
seq(block("with", $._tag_open, $._tag_kwargs), optional($.template)),
with_group: ($) => seq($.with_clause, block("endwith")),
},
});
+2 -2
View File
@@ -4,10 +4,10 @@
variable: (identifier) @variable.parameter
(block_group
(block_clause
(push_block) @type)
(partialdef_group
(partialdef_clause
(push_partial) @type)
(verbatim_group
+5 -5
View File
@@ -34,7 +34,7 @@
("trans")
("translate")] @library.i18n)
(language_group
(language_clause
tag: ("language") @library.i18n)
(get_available_languages
@@ -59,10 +59,10 @@
("language_name_local")
("language_name_translated")] @library.i18n)
(cache_group
(cache_clause
tag: ("cache") @library.cache)
(localize_group
(localize_clause
tag: ("localize") @library.l10n)
(filter
@@ -70,10 +70,10 @@
("localize")
("unlocalize")] @library.l10n)
(localtime_group
(localtime_clause
tag: ("localtime") @library.tz)
(timezone_group
(timezone_clause
tag: ("timezone") @library.tz)
(get_current_timezone
+14 -2
View File
@@ -3,5 +3,17 @@
variable: (identifier) @local.definition
(with_group) @local.scope
(for_scope) @local.scope
; The tags whose Node pushes a context, so that what they bind is not visible
; after them. Verified by rendering: "{% block b %}{% now "Y" as n %}{% endblock %}{{ n }}"
; is empty, while the same around an "{% if %}" is not, because if does not push.
(with_clause) @local.scope
(for_clause) @local.scope
(block_clause) @local.scope
(partialdef_clause) @local.scope
; blocktranslate pushes a context for its "with" and "count" bindings. Its
; asvar target is not one of them: it is written in the same tag but assigned
; outside it, and carries an "asvar" field rather than "variable" so that this
; scope does not swallow it. A tool that wants it must define it in the scope
; around the block, which a locals query has no way to say.
(blocktranslate_group) @local.scope
+7 -5
View File
@@ -1,15 +1,17 @@
(load
(identifier) @name) @definition.function
(partialdef_group (push_partial) @name) @definition.method
(partialdef_clause (push_partial) @name) @definition.method
(partial (identifier) @name) @reference.call
; every tag that opens a group names itself in a clause of its own, and every
; tag that does not is a group member directly
(template_block_groups
[
(_
tag: _ @name) @reference.call
(for_group
(for_scope
tag: "for" @reference.call))])
tag: _ @name)
(_
(_
tag: _ @name))] @reference.call)
(filter) @reference.call
+232 -93
View File
@@ -1097,7 +1097,7 @@
}
]
},
"autoescape_group": {
"autoescape_clause": {
"type": "SEQ",
"members": [
{
@@ -1178,6 +1178,15 @@
"type": "BLANK"
}
]
}
]
},
"autoescape_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "autoescape_clause"
},
{
"type": "SEQ",
@@ -1226,7 +1235,7 @@
}
]
},
"block_group": {
"block_clause": {
"type": "SEQ",
"members": [
{
@@ -1293,6 +1302,15 @@
"type": "BLANK"
}
]
}
]
},
"block_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "block_clause"
},
{
"type": "SEQ",
@@ -2043,7 +2061,7 @@
},
{
"type": "FIELD",
"name": "variable",
"name": "asvar",
"content": {
"type": "SYMBOL",
"name": "identifier"
@@ -2162,7 +2180,7 @@
]
}
},
"cache_group": {
"cache_clause": {
"type": "SEQ",
"members": [
{
@@ -2302,6 +2320,15 @@
"type": "BLANK"
}
]
}
]
},
"cache_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "cache_clause"
},
{
"type": "SEQ",
@@ -3037,7 +3064,7 @@
}
]
},
"filter_group": {
"filter_clause": {
"type": "SEQ",
"members": [
{
@@ -3114,6 +3141,15 @@
"type": "BLANK"
}
]
}
]
},
"filter_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "filter_clause"
},
{
"type": "SEQ",
@@ -3261,10 +3297,7 @@
}
]
},
"for_scope": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"for_clause": {
"type": "SEQ",
"members": [
{
@@ -3381,6 +3414,27 @@
}
]
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "STRING",
"value": "reversed"
}
]
},
{
"type": "BLANK"
}
]
},
{
"type": "CHOICE",
"members": [
@@ -3412,19 +3466,8 @@
]
}
]
}
},
"for_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "for_scope"
},
{
"type": "CHOICE",
"members": [
{
"empty_clause": {
"type": "SEQ",
"members": [
{
@@ -3486,6 +3529,20 @@
}
]
},
"for_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "for_clause"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "empty_clause"
},
{
"type": "BLANK"
}
@@ -4206,7 +4263,7 @@
}
]
},
"if_group": {
"if_clause": {
"type": "SEQ",
"members": [
{
@@ -4278,10 +4335,10 @@
"type": "BLANK"
}
]
}
]
},
{
"type": "REPEAT",
"content": {
"elif_clause": {
"type": "SEQ",
"members": [
{
@@ -4355,12 +4412,8 @@
]
}
]
}
},
{
"type": "CHOICE",
"members": [
{
"else_clause": {
"type": "SEQ",
"members": [
{
@@ -4422,6 +4475,27 @@
}
]
},
"if_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "if_clause"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "elif_clause"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "else_clause"
},
{
"type": "BLANK"
}
@@ -4474,7 +4548,7 @@
}
]
},
"ifchanged_group": {
"ifchanged_clause": {
"type": "SEQ",
"members": [
{
@@ -4549,71 +4623,22 @@
"type": "BLANK"
}
]
}
]
},
{
"type": "CHOICE",
"members": [
{
"ifchanged_group": {
"type": "SEQ",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{%"
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "FIELD",
"name": "tag",
"content": {
"type": "STRING",
"value": "else"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "PATTERN",
"value": "[ \\t\\r\\n]+"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "%}"
}
]
"type": "SYMBOL",
"name": "ifchanged_clause"
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "template"
},
{
"type": "BLANK"
}
]
}
]
"name": "else_clause"
},
{
"type": "BLANK"
@@ -4857,7 +4882,7 @@
}
]
},
"language_group": {
"language_clause": {
"type": "SEQ",
"members": [
{
@@ -4929,6 +4954,15 @@
"type": "BLANK"
}
]
}
]
},
"language_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "language_clause"
},
{
"type": "SEQ",
@@ -5120,7 +5154,7 @@
}
]
},
"localize_group": {
"localize_clause": {
"type": "SEQ",
"members": [
{
@@ -5209,6 +5243,15 @@
"type": "BLANK"
}
]
}
]
},
"localize_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "localize_clause"
},
{
"type": "SEQ",
@@ -5257,7 +5300,7 @@
}
]
},
"localtime_group": {
"localtime_clause": {
"type": "SEQ",
"members": [
{
@@ -5346,6 +5389,15 @@
"type": "BLANK"
}
]
}
]
},
"localtime_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "localtime_clause"
},
{
"type": "SEQ",
@@ -5653,7 +5705,7 @@
}
]
},
"partialdef_group": {
"partialdef_clause": {
"type": "SEQ",
"members": [
{
@@ -5737,6 +5789,15 @@
"type": "BLANK"
}
]
}
]
},
"partialdef_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "partialdef_clause"
},
{
"type": "SEQ",
@@ -6113,7 +6174,7 @@
}
]
},
"spaceless_group": {
"spaceless_clause": {
"type": "SEQ",
"members": [
{
@@ -6172,6 +6233,15 @@
"type": "BLANK"
}
]
}
]
},
"spaceless_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "spaceless_clause"
},
{
"type": "SEQ",
@@ -6407,7 +6477,7 @@
}
]
},
"timezone_group": {
"timezone_clause": {
"type": "SEQ",
"members": [
{
@@ -6479,6 +6549,15 @@
"type": "BLANK"
}
]
}
]
},
"timezone_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "timezone_clause"
},
{
"type": "SEQ",
@@ -7826,7 +7905,7 @@
}
]
},
"with_group": {
"with_clause": {
"type": "SEQ",
"members": [
{
@@ -7893,6 +7972,15 @@
"type": "BLANK"
}
]
}
]
},
"with_group": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "with_clause"
},
{
"type": "SEQ",
@@ -7947,6 +8035,57 @@
[
"template"
],
[
"autoescape_clause"
],
[
"block_clause"
],
[
"cache_clause"
],
[
"elif_clause"
],
[
"else_clause"
],
[
"empty_clause"
],
[
"filter_clause"
],
[
"for_clause"
],
[
"if_clause"
],
[
"ifchanged_clause"
],
[
"language_clause"
],
[
"localize_clause"
],
[
"localtime_clause"
],
[
"partialdef_clause"
],
[
"spaceless_clause"
],
[
"timezone_clause"
],
[
"with_clause"
],
[
"predicate"
],
+712 -334
View File
File diff suppressed because it is too large. Load diff
+47693 -97298
View File
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -17,6 +17,7 @@ Autoescape
(template
(content)
(autoescape_group
(autoescape_clause
(template
(content)))
(content))))
(content))
+25 -34
View File
@@ -17,9 +17,10 @@ Block
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content))
(content)))
(pop_block))
(content))
@@ -42,9 +43,10 @@ Block Matching End Tag
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content))
(content)))
(pop_block))
(content))
@@ -69,32 +71,17 @@ Nested Blocks
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content))
(content)))
(pop_block))
(content))
(pop_block))
(content))
---
(template
(content)
(block_group
(push_block)
(template
(content)
(block_group
(push_block)
(template
(content))
(pop_block))
(content))
(content)))
(pop_block))
(content))
@@ -119,15 +106,17 @@ Nested Blocks End Tag
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content))
(content)))
(pop_block))
(content))
(content)))
(pop_block))
(content))
@@ -152,14 +141,16 @@ Nested Blocks Mixed Tags
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content)
(block_group
(push_block)
(block_clause
name: (push_block)
(template
(content))
(pop_block))
(content))
(content)))
(pop_block))
(content)))
(pop_block))
(content))
+3 -3
View File
@@ -165,7 +165,7 @@ Blocktrans With Asvar
(template
(content)
(blocktranslate_group
variable: (identifier)
asvar: (identifier)
(content))
(content))
@@ -214,7 +214,7 @@ Blocktrans With Every Option
(value
(literal
(string))))
variable: (identifier)
asvar: (identifier)
(content)
(content))
(content))
@@ -230,7 +230,7 @@ Blocktrans With Its Options Reordered
(template
(content)
(blocktranslate_group
variable: (identifier)
asvar: (identifier)
(content))
(content))
+16 -8
View File
@@ -9,13 +9,14 @@ Cache
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
(number))))
(identifier)
(template
(content)))
(content))))
(content))
==================
@@ -29,13 +30,14 @@ Cache With A Variable Timeout
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(variable_attribute
(identifier))))
(identifier)
(template
(content)))
(content))))
(content))
==================
@@ -49,6 +51,7 @@ Cache With A Filtered Timeout
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
@@ -60,7 +63,7 @@ Cache With A Filtered Timeout
(number))))))
(identifier)
(template
(content)))
(content))))
(content))
==================
@@ -74,13 +77,14 @@ Cache With A Quoted Fragment Name
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
(number))))
(string)
(template
(content)))
(content))))
(content))
==================
@@ -94,6 +98,7 @@ Cache Varying On Arguments
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
@@ -108,7 +113,7 @@ Cache Varying On Arguments
(variable_attribute
(identifier))))
(template
(content)))
(content))))
(content))
==================
@@ -122,6 +127,7 @@ Cache Varying On A Filtered Argument
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
@@ -134,7 +140,7 @@ Cache Varying On A Filtered Argument
(filter_expression
(filter)))
(template
(content)))
(content))))
(content))
==================
@@ -148,6 +154,7 @@ Cache Using A Named Cache
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
@@ -158,7 +165,7 @@ Cache Using A Named Cache
(literal
(string))))
(template
(content)))
(content))))
(content))
==================
@@ -172,6 +179,7 @@ Cache Using An Unquoted Cache Name
(template
(content)
(cache_group
(cache_clause
(filtered_value
(value
(literal
@@ -182,7 +190,7 @@ Cache Using An Unquoted Cache Name
(variable_attribute
(identifier))))
(template
(content)))
(content))))
(content))
==================
+6 -4
View File
@@ -13,10 +13,11 @@ hello
(template
(content)
(filter_group
(filter_clause
(filter_expression
(filter))
(template
(content)))
(content))))
(content))
==================
@@ -34,11 +35,12 @@ HELLO WORLD
(template
(content)
(filter_group
(filter_clause
(filter_expression
(filter)
(filter))
(template
(content)))
(content))))
(content))
==================
@@ -57,10 +59,10 @@ HELLO WORLD
(template
(content)
(filter_group
(filter_clause
(filter_expression
(filter)
(filter))
(template
(content)))
(content))))
(content))
+363 -3
View File
@@ -13,7 +13,7 @@ For Basic
(template
(content)
(for_group
(for_scope
(for_clause
variable: (identifier)
(filtered_value
(value
@@ -44,7 +44,7 @@ For With Multiple Variables
(template
(content)
(for_group
(for_scope
(for_clause
variable: (identifier)
variable: (identifier)
(filtered_value
@@ -78,7 +78,7 @@ For With Empty
(template
(content)
(for_group
(for_scope
(for_clause
variable: (identifier)
(filtered_value
(value
@@ -92,6 +92,366 @@ For With Empty
(variable_attribute
(identifier)))))
(content)))
(empty_clause
(template
(content))))
(content))
==================
For With A Tag In Its Body
==================
{% for x in y %}
{% if a %}b{% endif %}
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content))))
(content))))
(content))
==================
For With A Nested For
==================
{% for x in y %}
{% for z in x %}a{% endfor %}
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content))))
(content))))
(content))
==================
For With Tags In Both Branches
==================
{% for x in y %}
{% if a %}b{% endif %}
{% empty %}
{% with c=1 %}d{% endwith %}
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content))))
(content)))
(empty_clause
(template
(content)
(with_group
(with_clause
variable: (identifier)
(filtered_value
(value
(literal
(number))))
(template
(content))))
(content))))
(content))
==================
For With A Named Block In Its Body
==================
{% for x in y %}
{% block b %}c{% endblock b %}
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)
(block_group
(block_clause
name: (push_block)
(template
(content)))
(pop_block))
(content))))
(content))
==================
For Nested In A Tag
==================
{% if a %}
{% for x in y %}{% if b %}c{% endif %}{% endfor %}
{% endif %}
---
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))))))
(content))))
(content))
==================
For With An Empty Body
==================
{% for x in y %}{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))))
(content))
==================
For Reversed
==================
{% for x in y reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content))))
(content))
==================
For With Multiple Variables Reversed
==================
{% for k,v in y reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content))))
(content))
==================
For With A Filtered Sequence Reversed
==================
{% for x in y|slice:":2" reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter
argument: (value
(literal
(string))))))
(template
(content))))
(content))
==================
For Reversed With Empty
==================
{% for x in y reversed %}
a
{% empty %}
b
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)))
(empty_clause
(template
(content))))
(content))
==================
For Reversed With A Tag In Its Body
==================
{% for x in y reversed %}
{% if a %}b{% endif %}
{% endfor %}
---
(template
(content)
(for_group
(for_clause
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content))))
(content))))
(content))
==================
For With Reversed Twice
:error
==================
{% for x in y reversed reversed %}a{% endfor %}
---
==================
For With Reversed Before In
:error
==================
{% for x reversed in y %}a{% endfor %}
---
+4 -2
View File
@@ -355,12 +355,13 @@ Language
(template
(content)
(language_group
(language_clause
(filtered_value
(value
(literal
(string))))
(template
(content)))
(content))))
(content))
==================
@@ -374,12 +375,13 @@ Language With A Variable
(template
(content)
(language_group
(language_clause
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)))
(content))))
(content))
==================
+23 -19
View File
@@ -11,6 +11,7 @@ Hi
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
@@ -18,7 +19,7 @@ Hi
(identifier)
(attribute)))))
(template
(content)))
(content))))
(content))
==================
@@ -34,13 +35,14 @@ working
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(content))))
(content))
==================
@@ -60,6 +62,7 @@ other
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
@@ -70,22 +73,24 @@ other
(value
(literal
(number)))))
(template
(content))
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number)))))
(template
(content))
(template
(content)))
(elif_clause
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(else_clause
(template
(content))))
(content))
==================
@@ -101,6 +106,7 @@ yes
(template
(content)
(if_group
(if_clause
(predicate
(filtered_value
(value
@@ -139,7 +145,7 @@ yes
(literal
(number)))))
(template
(content)))
(content))))
(content))
==================
@@ -150,5 +156,3 @@ If Operator Adjacent To Value
{% if 1and 2 %}yes{% endif %}
---
(ERROR)
+22 -10
View File
@@ -11,8 +11,9 @@ content
(template
(content)
(ifchanged_group
(ifchanged_clause
(template
(content)))
(content))))
(content))
==================
@@ -28,6 +29,7 @@ content
(template
(content)
(ifchanged_group
(ifchanged_clause
(filtered_value
(value
(variable_attribute
@@ -37,7 +39,7 @@ content
(variable_attribute
(identifier))))
(template
(content)))
(content))))
(content))
==================
@@ -55,10 +57,12 @@ unchanged
(template
(content)
(ifchanged_group
(template
(content))
(ifchanged_clause
(template
(content)))
(else_clause
(template
(content))))
(content))
==================
@@ -76,6 +80,7 @@ unchanged
(template
(content)
(ifchanged_group
(ifchanged_clause
(filtered_value
(value
(variable_attribute
@@ -86,10 +91,11 @@ unchanged
(identifier)))
(filter_expression
(filter)))
(template
(content))
(template
(content)))
(else_clause
(template
(content))))
(content))
==================
@@ -105,8 +111,10 @@ content
(template
(content)
(ifchanged_group
(ifchanged_clause
(template
(content)))
(else_clause))
(content))
==================
@@ -128,16 +136,20 @@ c
(template
(content)
(ifchanged_group
(ifchanged_clause
(template
(content))
(content)))
(else_clause
(template
(content)
(ifchanged_group
(template
(content))
(ifchanged_clause
(template
(content)))
(content)))
(else_clause
(template
(content))))
(content))))
(content))
==================
+11 -5
View File
@@ -9,8 +9,9 @@ Localize
(template
(content)
(localize_group
(localize_clause
(template
(content)))
(content))))
(content))
==================
@@ -24,8 +25,9 @@ Localize On
(template
(content)
(localize_group
(localize_clause
(template
(content)))
(content))))
(content))
==================
@@ -39,8 +41,9 @@ Localize Off
(template
(content)
(localize_group
(localize_clause
(template
(content)))
(content))))
(content))
==================
@@ -54,8 +57,9 @@ Localize Hugging Its Delimiters
(template
(content)
(localize_group
(localize_clause
(template
(content)))
(content))))
(content))
==================
@@ -69,10 +73,12 @@ Localize Nested
(template
(content)
(localize_group
(localize_clause
(template
(localize_group
(localize_clause
(template
(content)))))
(content)))))))
(content))
==================
+4 -2
View File
@@ -13,9 +13,10 @@ Partialdef Basic
(template
(content)
(partialdef_group
(partialdef_clause
(push_partial)
(template
(content))
(content)))
(pop_partial))
(content))
@@ -34,8 +35,9 @@ Partialdef Inline
(template
(content)
(partialdef_group
(partialdef_clause
(push_partial)
(template
(content))
(content)))
(pop_partial))
(content))
+2 -1
View File
@@ -13,6 +13,7 @@ Spaceless Basic
(template
(content)
(spaceless_group
(spaceless_clause
(template
(content)))
(content))))
(content))
+3 -7
View File
@@ -174,7 +174,7 @@ Template Variable With Filter Argument
(identifier)))
(filter_expression
(filter
(value
argument: (value
(literal
(string)))))))
(content))
@@ -196,7 +196,7 @@ Template Variable Number With Filter Argument
(number)))
(filter_expression
(filter
(value
argument: (value
(literal
(number)))))))
(content))
@@ -210,7 +210,6 @@ Template Variable Literal Adjacent To Name
---
(ERROR)
==================
Template Variable With Escaped String
@@ -272,7 +271,6 @@ Template Variable Attribute Beginning With Underscore
---
(ERROR)
==================
Template Variable Translated String
@@ -321,7 +319,6 @@ Template Variable Translated String With Spaces
---
(ERROR)
==================
Template Variable Spanning Lines
@@ -341,7 +338,7 @@ Template Variable Spanning Lines
(identifier)))
(filter_expression
(filter
(value
argument: (value
(literal
(number)))))))
(content))
@@ -364,4 +361,3 @@ Template Variable With Spaced Filter
(filter_expression
(filter))))
(content))
+12 -6
View File
@@ -9,8 +9,9 @@ Localtime
(template
(content)
(localtime_group
(localtime_clause
(template
(content)))
(content))))
(content))
==================
@@ -24,8 +25,9 @@ Localtime On
(template
(content)
(localtime_group
(localtime_clause
(template
(content)))
(content))))
(content))
==================
@@ -39,8 +41,9 @@ Localtime Off
(template
(content)
(localtime_group
(localtime_clause
(template
(content)))
(content))))
(content))
==================
@@ -74,12 +77,13 @@ Timezone
(template
(content)
(timezone_group
(timezone_clause
(filtered_value
(value
(literal
(string))))
(template
(content)))
(content))))
(content))
==================
@@ -93,12 +97,13 @@ Timezone With A Variable
(template
(content)
(timezone_group
(timezone_clause
(filtered_value
(value
(variable_attribute
(identifier))))
(template
(content)))
(content))))
(content))
==================
@@ -112,6 +117,7 @@ Timezone With A Filtered Argument
(template
(content)
(timezone_group
(timezone_clause
(filtered_value
(value
(variable_attribute
@@ -119,7 +125,7 @@ Timezone With A Filtered Argument
(filter_expression
(filter)))
(template
(content)))
(content))))
(content))
==================
+8 -4
View File
@@ -11,6 +11,7 @@ With Basic
(template
(content)
(with_group
(with_clause
variable: (identifier)
(filtered_value
(value
@@ -24,7 +25,7 @@ With Basic
(value
(variable_attribute
(identifier)))))
(content)))
(content))))
(content))
==================
@@ -40,6 +41,7 @@ With Multiple Variables
(template
(content)
(with_group
(with_clause
variable: (identifier)
(filtered_value
(value
@@ -57,7 +59,7 @@ With Multiple Variables
(value
(variable_attribute
(identifier)))))
(content)))
(content))))
(content))
==================
@@ -91,19 +93,21 @@ With Repeating A Binding Of The Tag Before It
(template
(content)
(with_group
(with_clause
variable: (identifier)
(filtered_value
(value
(literal
(number))))
(template
(content)))
(content))))
(with_group
(with_clause
variable: (identifier)
(filtered_value
(value
(literal
(number))))
(template
(content)))
(content))))
(content))