Compare commits

...
4 Commits
Author SHA1 Message Date
danhanson ccb80972b6 handle content edge cases 2026-09-14 21:40:26 -05:00
danhanson fa0d880e68 fixed content regex 2026-09-14 21:29:27 -05:00
danhanson 831acd3516 add unexpected argument 2026-09-14 18:32:37 -05:00
danhanson f17fdd2d74 add unexpected argument error 2026-09-14 17:51:45 -05:00
23 changed files with 54212 additions and 33731 deletions

No files matched your search

+48 -8
View File
@@ -197,13 +197,46 @@ group can hold it — `{% endif %}` with nothing open, `{% else %}` in a `{% for
and error recovery placed it badly: every tag starts with `{%` and the read token, which the parser has and error recovery placed it badly: every tag starts with `{%` and the read token, which the parser has
already accepted when the name turns out to be wrong, so recovery rewinds to just after them and folds the already accepted when the name turns out to be wrong, so recovery rewinds to just after them and folds the
stray tag into whichever tag comes next, which then holds the `ERROR`. `unexpected_tag` takes any of stray tag into whichever tag comes next, which then holds the `ERROR`. `unexpected_tag` takes any of
`NAMES_INSIDE_A_TAG_GROUP` with loose arguments, so there is nothing to recover; `prec.dynamic(-1)` keeps it `NAMES_INSIDE_A_TAG_GROUP` with loose arguments, so there is nothing to recover; `prec.dynamic(-2)`, or `-4`, keeps it
from displacing a tag the group does take, and `queries/errors.scm` reports it as from displacing a tag the group does take, and `queries/errors.scm` reports it as
`@error.unexpected_tag`. It added 31 states and no `conflicts` entry. `@error.unexpected_tag`. It added 31 states and no `conflicts` entry.
**Words a tag does not take are an `unexpected_argument` inside it.** Every close utility accepts one
before its scanner token, so `{% else x %}` stays the `else` of its group, `{% endif junk %}` still closes
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:
- **Lexical precedence -1.** A loose word is `token(prec(-1, /[^\s%]+/))`. Tree-sitter weighs lexical
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.
- **Identifiers are words too.** Wherever a keyword is valid — after `{% for a in b`, where `reversed` may
follow, or after `as`, `only`, `noop`, `silent` — tree-sitter lexes every word as an `identifier` to
check for the keyword, and the loose-word token never gets a turn. So a word may also be an
`identifier`, aliased to an anonymous `unexpected_word` so `queries/locals.scm` does not count it as a
reference, with whatever touches it (`a=2`, `x.y|upper`, `"z"|lower:"q"`) as an immediate tail.
- **-1 of dynamic precedence per word**, not per node, so a reading that flags more words than it must
always loses: `{% cycle a b as x silent zqx %}` flags `zqx`, not everything after `a`. `unexpected_tag`
costs -2 or -4 so that a misused tag its group does take beats reading it as a stray.
A tag with a known keyword signature (`simpleTag` given names, so far only `csp_nonce_attr`) needs one more
piece: the scanner's `_kwarg_name` accepts any name followed by `=` and commits the parser to a keyword
argument before the name is checked, so `{% csp_nonce_attr other="b" %}` would be an `ERROR`. `simpleTag`
offers `_unexpected_keyword` — an identifier and its tail, aliased to `unexpected_argument` — beside the
names it knows.
A separator after any argument can now also start stray words, so this needs `conflicts` entries for
`unexpected_argument`, `predicate`, `_translate_option`, and `_unexpected_word` with `variable_attribute`,
`resetcycle_tag` and `library`. It took the table from 3,363 states to 4,913 and the Wasm build from 251KB to
323KB. Two consequences: a repeat the scanner refuses — `{% with a=1 a=2 %}`, `{% blocktrans trimmed trimmed %}`
— is reported as an unexpected argument rather than an `ERROR`; and a stray **reserved** word is still an
`ERROR`, since `not`, `if`, `in`, `is`, `as`, `for` and `from` can never be an identifier:
`{% trans "hi" as x as y %}` errors on the second `as`. A tag missing a required argument, such as
`{% with a = 1 %}`, is an `ERROR` too; `unexpected_argument` only takes words after a tag that is otherwise
complete.
Where several tags share a name the group holds only once — `{% if %}{% else %}{% else %}{% else %}` — reading Where several tags share a name the group holds only once — `{% if %}{% else %}{% else %}{% else %}` — reading
any of them as the stray parses, so the ties are broken by dynamic precedence: `unexpected_tag` is -1 when any of them as the stray parses, so the ties are broken by dynamic precedence: `unexpected_tag` is -2 when
`_group_held` confirms the innermost group already holds the name and -2 otherwise, so each tag flagged `_group_held` confirms the innermost group already holds the name and -4 otherwise, so each tag flagged
before its time costs one more, and the first still opens the block however many follow. before its time costs one more, and the first still opens the block however many follow.
**Builtin names must stay keyword-extractable, or the fallbacks swallow them.** `word: $.identifier` turns **Builtin names must stay keyword-extractable, or the fallbacks swallow them.** `word: $.identifier` turns
@@ -249,7 +282,7 @@ README nothing checks at all. The library-contents differential below, run again
### External scanner (`src/scanner.c`) ### External scanner (`src/scanner.c`)
Eighteen external tokens, for the constraints a context-free grammar cannot express: Nineteen external tokens, for the constraints a context-free grammar cannot express:
- `push_block`/`push_partial`/`push_verbatim` and the matching `pop_*` — the names in - `push_block`/`push_partial`/`push_verbatim` and the matching `pop_*` — the names in
`{% block a %}…{% endblock a %}`. A push token reads the name into `pending_name`, which the group takes `{% block a %}…{% endblock a %}`. A push token reads the name into `pending_name`, which the group takes
@@ -305,6 +338,12 @@ Eighteen external tokens, for the constraints a context-free grammar cannot expr
`{% if %}…{% else %}{% if %}…{% else %}`, because the outer group, having held an `else`, claimed the `{% if %}…{% else %}{% if %}…{% else %}`, because the outer group, having held an `else`, claimed the
inner one's. inner one's.
- `_text_brace` — a single `{` that is text although the content regex cannot tell: before `{%` or `{#`,
and at the end of input. Django splits a template with `({%.*?%}|{{.*?}}|{#.*?#})`, so a `{` is text
unless one of those starts at it; `{{{` is not a text brace, since `{{` starts a variable at its first
brace. `content` is a right-associative run of `_text` and `_text_brace`, so it stays one node. Inside a
group's body the missing-tag check reads past `{` first, so it scans the brace itself
(`scan_text_brace_rest`) rather than give up the position.
- `matcher_error` — used by no rule; returned only if the scanner reaches an error state. - `matcher_error` — used by no rule; returned only if the scanner reaches an error state.
`check_space()` defines whitespace for the scanner and **must stay in sync with `_sep`**, because the `check_space()` defines whitespace for the scanner and **must stay in sync with `_sep`**, because the
@@ -368,7 +407,8 @@ A branch that is also a `@local.scope` confines its bindings whichever way the b
`context.push()` as the loop body, so `empty_block` is a scope alongside `for_block`. `context.push()` as the loop body, so `empty_block` is a scope alongside `for_block`.
`queries/errors.scm` collects what an editor should report: `(ERROR)` as `@error.syntax`, `(MISSING)` as `queries/errors.scm` collects what an editor should report: `(ERROR)` as `@error.syntax`, `(MISSING)` as
`@error.missing`, and every missing-tag marker as `@error.missing_tag`. A tree holding only markers has `@error.missing`, every missing-tag marker as `@error.missing_tag`, and `unexpected_tag` and
`unexpected_argument` as `@error.unexpected_tag` and `@error.unexpected_argument`. A tree holding only markers has
no `has_error`, so a tool that checks that flag alone misses them. Adding a tag group means adding its no `has_error`, so a tool that checks that flag alone misses them. Adding a tag group means adding its
marker there, since no supertype matches them all. marker there, since no supertype matches them all.
@@ -423,8 +463,8 @@ unavoidable limitation, or a case where this grammar is the more permissive one
binds an `identifier`, because the keyword is not valid where a binding name is — but `{{ True.x }}` is binds an `identifier`, because the keyword is not valid where a binding name is — but `{{ True.x }}` is
an error here and an ordinary two-part lookup in Django, which renders `string_if_invalid`. an error here and an ordinary two-part lookup in Django, which renders `string_if_invalid`.
- A `with` binding written twice (`{% with a=1 a=2 %}`, and the same in `{% include %}` and - A `with` binding written twice (`{% with a=1 a=2 %}`, and the same in `{% include %}` and
`{% blocktranslate %}`) is an error here and not in Django, where `token_kwargs` builds a dict and the `{% blocktranslate %}`) is reported here, as an `unexpected_argument`, and not in Django, where
later value wins. It has no use, so it is treated as the mistake it almost certainly is. `token_kwargs` builds a dict and the later value wins. It has no use, so it is treated as the mistake it almost certainly is.
- `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time. - `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time.
- A misspelled tag or filter cannot be caught: `{{ x|lenght }}` is indistinguishable from a filter some - A misspelled tag or filter cannot be caught: `{{ x|lenght }}` is indistinguishable from a filter some
library registered. Cross-referencing names against `{% load %}` needs the project's Python, so it library registered. Cross-referencing names against `{% load %}` needs the project's Python, so it
@@ -437,7 +477,7 @@ unavoidable limitation, or a case where this grammar is the more permissive one
redefines `length` with a different arity gets a false error here. redefines `length` with a different arity gets a false error here.
- Django's `static` tags ignore what follows their argument: `StaticNode.handle_token` looks for `as` two - Django's `static` tags ignore what follows their argument: `StaticNode.handle_token` looks for `as` two
bits from the end, so `{% static "a" junk %}` and `{% static "a" as u v %}` are accepted there and bits from the end, so `{% static "a" junk %}` and `{% static "a" as u v %}` are accepted there and
rejected here, and `PrefixNode.handle_token` raises `IndexError` rather than `TemplateSyntaxError` on reported here as an `unexpected_argument`, and `PrefixNode.handle_token` raises `IndexError` rather than `TemplateSyntaxError` on
`{% get_static_prefix as %}`. `{% get_static_prefix as %}`.
- Django's `{% cache %}` takes its fragment name as the raw word in that position and finds the cache to - Django's `{% cache %}` takes its fragment name as the raw word in that position and finds the cache to
use by testing whether the last word starts with `using=`, and only when something precedes it. So use by testing whether the last word starts with `using=`, and only when something precedes it. So
+5 -2
View File
@@ -69,7 +69,8 @@ Five of the files in `queries/` are query files an editor loads:
it. A marker is placed only where the scanner can tell: at the end of input, before an end tag that it. A marker is placed only where the scanner can tell: at the end of input, before an end tag that
belongs to an enclosing group, or before an `{% endblock %}` naming a block further out. belongs to an enclosing group, or before an `{% endblock %}` naming a block further out.
A group's tag written where no open group can hold it, such as `{% else %}` inside a `{% for %}`, is an A group's tag written where no open group can hold it, such as `{% else %}` inside a `{% for %}`, is an
`unexpected_tag`, captured as `@error.unexpected_tag`. `unexpected_tag`, captured as `@error.unexpected_tag`. Words a tag does not take, such as the `x` in
`{% else x %}`, are an `unexpected_argument` inside that tag, captured as `@error.unexpected_argument`.
The other three are data for a tool rather than queries an editor runs. Each captures the two halves of a The other three are data for a tool rather than queries an editor runs. Each captures the two halves of a
relation a tree-sitter query cannot express, and the tool does the join: relation a tree-sitter query cannot express, and the tool does the join:
@@ -224,7 +225,9 @@ Seven things to know:
`conflicts` entry, as every block in the base grammar does. The generator offers an associativity `conflicts` entry, as every block in the base grammar does. The generator offers an associativity
instead; taking it silently discards the parse in which the body continues. instead; taking it silently discards the parse in which the body continues.
- **End tags are not reserved**, a stray `{% endmap %}` parses as a `custom_tag` rather than an error — `reserved` takes no - **End tags are not reserved**, a stray `{% endmap %}` parses as a `custom_tag` rather than an error — `reserved` takes no
`previous`, so the `tag_name` list cannot be added to a name at a time. Instead you may drop the `custom_tag` rule to avoid parsing end tags as custom tags. `previous`, so the `tag_name` list cannot be added to a name at a time. Instead you may drop the `custom_tag` rule to avoid parsing end tags as custom tags. While the fallback
is there, stray words in one of your end tags (`{% endloop extra %}`) read as a `custom_tag` too, which
leaves your group with a missing-tag marker at the end of input instead of an `unexpected_argument`.
- **Your groups get missing-tag markers.** The scanner knows no group by name — a group is closed by `end` - **Your groups get missing-tag markers.** The scanner knows no group by name — a group is closed by `end`
followed by its opening tag's name — so the `alias($._missing_tag, $.missing_endloop_tag)` choice followed by its opening tag's name — so the `alias($._missing_tag, $.missing_endloop_tag)` choice
above is all it takes: an unclosed `{% loop x %}`, or one left open inside an `{% if %}` that above is all it takes: an unclosed `{% loop x %}`, or one left open inside an `{% if %}` that
+110 -19
View File
@@ -281,7 +281,14 @@ function simpleTag(name, args = true, kwargs = true) {
const named = Object.entries(kwargs).map(([name, value]) => const named = Object.entries(kwargs).map(([name, value]) =>
part(seq(sym("_kwarg_name"), name, "=", value)), part(seq(sym("_kwarg_name"), name, "=", value)),
); );
parts.push(repeat(choice(...named))); // a name the signature does not take is flagged in place
const unexpected = part(
seq(
sym("_kwarg_name"),
alias(sym("_unexpected_keyword"), sym("unexpected_argument")),
),
);
parts.push(repeat(choice(...named, unexpected)));
} }
return tag(name, ...parts, optional(asVariable)); return tag(name, ...parts, optional(asVariable));
} }
@@ -514,6 +521,12 @@ const django = grammar({
[$.timezone_block], [$.timezone_block],
[$.with_block], [$.with_block],
[$.binaryOperator], [$.binaryOperator],
[$.predicate],
[$._translate_option],
[$.unexpected_argument],
[$.variable_attribute, $._unexpected_word],
[$.resetcycle_tag, $._unexpected_word],
[$.library, $._unexpected_word],
[$.library, $.load_tag], [$.library, $.load_tag],
[$._filtered_value_spaced], [$._filtered_value_spaced],
[$._filter_expression_spaced], [$._filter_expression_spaced],
@@ -552,6 +565,7 @@ const django = grammar({
$._group_repeat, $._group_repeat,
$._group_close, $._group_close,
$._group_held, $._group_held,
$._text_brace,
], ],
reserved: { reserved: {
global: ($) => ["not", "if", "in", "is", "as", "for", "from"], global: ($) => ["not", "if", "in", "is", "as", "for", "from"],
@@ -561,16 +575,43 @@ const django = grammar({
template: ($) => repeat1(choice($.template_node, $.content)), template: ($) => repeat1(choice($.template_node, $.content)),
// the tags of a group; see startTag // the tags of a group; see startTag
simpleTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)), simpleTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
simpleTagClose: ($) => seq(optional(SEP), "%}"), simpleTagClose: ($) =>
seq(optional($.unexpected_argument), optional(SEP), "%}"),
startTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)), startTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
startTagClose: ($) => seq($._group_open_tag_push, optional(SEP), "%}"), startTagClose: ($) =>
seq(
optional($.unexpected_argument),
$._group_open_tag_push,
optional(SEP),
"%}",
),
followTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)), followTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
followTagClose: ($) => seq($._group_follow, optional(SEP), "%}"), followTagClose: ($) =>
seq(
optional($.unexpected_argument),
$._group_follow,
optional(SEP),
"%}",
),
repeatTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)), repeatTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
repeatTagClose: ($) => seq($._group_repeat, optional(SEP), "%}"), repeatTagClose: ($) =>
seq(
optional($.unexpected_argument),
$._group_repeat,
optional(SEP),
"%}",
),
endTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)), endTagOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
endTagClose: ($) => seq($._group_close, optional(SEP), "%}"), endTagClose: ($) =>
content: ($) => /(?:[^\{]|\{[^\{#%}])+/, seq(optional($.unexpected_argument), $._group_close, optional(SEP), "%}"),
content: ($) => prec.right(repeat1(choice($._text, $._text_brace))),
/**
* Template text up to a "{{", "{%" or "{#". A "{" that is text only because
* of what comes after the next character, as in "a{{% if x %}" or a "{" at
* the end of input, is _text_brace from the scanner; see
* scan_text_brace_rest.
*/
_text: ($) => /(?:[^\{]|\{[^\{#%])+/,
// the separator between the parts of a tag; see SEP // the separator between the parts of a tag; see SEP
_sep: ($) => /[ \t\r\n]+/, _sep: ($) => /[ \t\r\n]+/,
template_node: ($) => template_node: ($) =>
@@ -1165,30 +1206,80 @@ const django = grammar({
* second and a third "else", the later ones are the strays. Reading the * second and a third "else", the later ones are the strays. Reading the
* first as the stray instead parses just as well, so _group_held, which the * first as the stray instead parses just as well, so _group_held, which the
* scanner returns only for a name the innermost group already holds, puts * scanner returns only for a name the innermost group already holds, puts
* the later reading at -1 against -2 for each tag flagged before its time. * the later reading at -2 against -4 for each tag flagged before its time. Both
* cost more than an unexpected_argument, at -1, so a tag its group does take
* but with a word it does not is read as that tag rather than as a stray.
*/ */
unexpected_tag: ($) => unexpected_tag: ($) =>
choice( choice(
prec.dynamic(
-1,
seq(
$.simpleTagOpen,
field("tag_name", choice(...NAMES_INSIDE_A_TAG_GROUP)),
repeat(seq(SEP, /[^\s%]+/)),
$._group_held,
$.simpleTagClose,
),
),
prec.dynamic( prec.dynamic(
-2, -2,
seq( seq(
$.simpleTagOpen, $.simpleTagOpen,
field("tag_name", choice(...NAMES_INSIDE_A_TAG_GROUP)), field("tag_name", choice(...NAMES_INSIDE_A_TAG_GROUP)),
repeat(seq(SEP, /[^\s%]+/)), optional($.unexpected_argument),
$._group_held,
optional(SEP),
"%}",
),
),
prec.dynamic(
-4,
seq(
$.simpleTagOpen,
field("tag_name", choice(...NAMES_INSIDE_A_TAG_GROUP)),
$.simpleTagClose, $.simpleTagClose,
), ),
), ),
), ),
/**
* Words a tag does not take, kept inside the tag they were written in. Every
* close utility accepts them before its "%}", so a misused tag keeps its own
* node, "{% else x %}" is still the else of its group, and error recovery
* has nothing to repair. queries/errors.scm reports it.
*/
unexpected_argument: ($) => repeat1($._unexpected_word),
/**
* One word of an unexpected_argument, costing -1 of dynamic precedence of
* its own, so that a reading flagging more words than it must always loses:
* "{% cycle a b as x silent zqx %}" flags zqx, not everything after a.
*
* The word is a token of lexical precedence -1, so that a token an argument
* really takes wins wherever one is valid, even over a longer match:
* "b|upper" is still a filtered value. Where a keyword is valid, as after
* "{% for a in b" where "reversed" may follow, tree-sitter lexes every 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
* aliased so that queries/locals.scm does not take it for a reference.
*/
_unexpected_word: ($) =>
prec.dynamic(
-1,
seq(
SEP,
choice(
token(prec(-1, /[^\s%]+/)),
seq(
alias($.identifier, "unexpected_word"),
optional(token.immediate(prec(-1, /[^\s%]+/))),
),
),
),
),
/**
* A "name=value" argument whose name a tag with a known signature does not
* take. The scanner's _kwarg_name reads any name followed by "=", which
* commits the parser to a keyword argument before the name is known, so
* the name the signature lacks is taken here rather than after the tag.
*/
_unexpected_keyword: ($) =>
prec.dynamic(
-1,
seq(
alias($.identifier, "unexpected_word"),
token.immediate(prec(-1, /[^\s%]+/)),
),
),
url_tag: ($) => url_tag: ($) =>
tag( tag(
"url", "url",
+2
View File
@@ -41,3 +41,5 @@
(missing_plural_tag)] @error.missing_tag (missing_plural_tag)] @error.missing_tag
(unexpected_tag) @error.unexpected_tag (unexpected_tag) @error.unexpected_tag
(unexpected_argument) @error.unexpected_argument
+362 -140
View File
@@ -47,6 +47,18 @@
"simpleTagClose": { "simpleTagClose": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
@@ -93,6 +105,18 @@
"startTagClose": { "startTagClose": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_group_open_tag_push" "name": "_group_open_tag_push"
@@ -143,6 +167,18 @@
"followTagClose": { "followTagClose": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_group_follow" "name": "_group_follow"
@@ -193,6 +229,18 @@
"repeatTagClose": { "repeatTagClose": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_group_repeat" "name": "_group_repeat"
@@ -243,6 +291,18 @@
"endTagClose": { "endTagClose": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_group_close" "name": "_group_close"
@@ -266,8 +326,28 @@
] ]
}, },
"content": { "content": {
"type": "PREC_RIGHT",
"value": 0,
"content": {
"type": "REPEAT1",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "_text"
},
{
"type": "SYMBOL",
"name": "_text_brace"
}
]
}
}
},
"_text": {
"type": "PATTERN", "type": "PATTERN",
"value": "(?:[^\\{]|\\{[^\\{#%}])+" "value": "(?:[^\\{]|\\{[^\\{#%])+"
}, },
"_sep": { "_sep": {
"type": "PATTERN", "type": "PATTERN",
@@ -2833,6 +2913,33 @@
] ]
} }
] ]
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_sep"
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_kwarg_name"
},
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_unexpected_keyword"
},
"named": true,
"value": "unexpected_argument"
}
]
}
]
} }
] ]
} }
@@ -7062,140 +7169,6 @@
"unexpected_tag": { "unexpected_tag": {
"type": "CHOICE", "type": "CHOICE",
"members": [ "members": [
{
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "simpleTagOpen"
},
{
"type": "FIELD",
"name": "tag_name",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "elif"
},
{
"type": "STRING",
"value": "else"
},
{
"type": "STRING",
"value": "empty"
},
{
"type": "STRING",
"value": "endautoescape"
},
{
"type": "STRING",
"value": "endblock"
},
{
"type": "STRING",
"value": "endblocktrans"
},
{
"type": "STRING",
"value": "endblocktranslate"
},
{
"type": "STRING",
"value": "endcache"
},
{
"type": "STRING",
"value": "endcomment"
},
{
"type": "STRING",
"value": "endfilter"
},
{
"type": "STRING",
"value": "endfor"
},
{
"type": "STRING",
"value": "endif"
},
{
"type": "STRING",
"value": "endifchanged"
},
{
"type": "STRING",
"value": "endlanguage"
},
{
"type": "STRING",
"value": "endlocalize"
},
{
"type": "STRING",
"value": "endlocaltime"
},
{
"type": "STRING",
"value": "endpartialdef"
},
{
"type": "STRING",
"value": "endspaceless"
},
{
"type": "STRING",
"value": "endtimezone"
},
{
"type": "STRING",
"value": "endverbatim"
},
{
"type": "STRING",
"value": "endwith"
},
{
"type": "STRING",
"value": "plural"
}
]
}
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_sep"
},
{
"type": "PATTERN",
"value": "[^\\s%]+"
}
]
}
},
{
"type": "SYMBOL",
"name": "_group_held"
},
{
"type": "SYMBOL",
"name": "simpleTagClose"
}
]
}
},
{ {
"type": "PREC_DYNAMIC", "type": "PREC_DYNAMIC",
"value": -2, "value": -2,
@@ -7304,17 +7277,143 @@
} }
}, },
{ {
"type": "REPEAT", "type": "CHOICE",
"content": { "members": [
"type": "SEQ", {
"type": "SYMBOL",
"name": "unexpected_argument"
},
{
"type": "BLANK"
}
]
},
{
"type": "SYMBOL",
"name": "_group_held"
},
{
"type": "CHOICE",
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_sep" "name": "_sep"
}, },
{ {
"type": "PATTERN", "type": "BLANK"
"value": "[^\\s%]+" }
]
},
{
"type": "STRING",
"value": "%}"
}
]
}
},
{
"type": "PREC_DYNAMIC",
"value": -4,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "simpleTagOpen"
},
{
"type": "FIELD",
"name": "tag_name",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "elif"
},
{
"type": "STRING",
"value": "else"
},
{
"type": "STRING",
"value": "empty"
},
{
"type": "STRING",
"value": "endautoescape"
},
{
"type": "STRING",
"value": "endblock"
},
{
"type": "STRING",
"value": "endblocktrans"
},
{
"type": "STRING",
"value": "endblocktranslate"
},
{
"type": "STRING",
"value": "endcache"
},
{
"type": "STRING",
"value": "endcomment"
},
{
"type": "STRING",
"value": "endfilter"
},
{
"type": "STRING",
"value": "endfor"
},
{
"type": "STRING",
"value": "endif"
},
{
"type": "STRING",
"value": "endifchanged"
},
{
"type": "STRING",
"value": "endlanguage"
},
{
"type": "STRING",
"value": "endlocalize"
},
{
"type": "STRING",
"value": "endlocaltime"
},
{
"type": "STRING",
"value": "endpartialdef"
},
{
"type": "STRING",
"value": "endspaceless"
},
{
"type": "STRING",
"value": "endtimezone"
},
{
"type": "STRING",
"value": "endverbatim"
},
{
"type": "STRING",
"value": "endwith"
},
{
"type": "STRING",
"value": "plural"
} }
] ]
} }
@@ -7328,6 +7427,104 @@
} }
] ]
}, },
"unexpected_argument": {
"type": "REPEAT1",
"content": {
"type": "SYMBOL",
"name": "_unexpected_word"
}
},
"_unexpected_word": {
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_sep"
},
{
"type": "CHOICE",
"members": [
{
"type": "TOKEN",
"content": {
"type": "PREC",
"value": -1,
"content": {
"type": "PATTERN",
"value": "[^\\s%]+"
}
}
},
{
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "identifier"
},
"named": false,
"value": "unexpected_word"
},
{
"type": "CHOICE",
"members": [
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC",
"value": -1,
"content": {
"type": "PATTERN",
"value": "[^\\s%]+"
}
}
},
{
"type": "BLANK"
}
]
}
]
}
]
}
]
}
},
"_unexpected_keyword": {
"type": "PREC_DYNAMIC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "identifier"
},
"named": false,
"value": "unexpected_word"
},
{
"type": "IMMEDIATE_TOKEN",
"content": {
"type": "PREC",
"value": -1,
"content": {
"type": "PATTERN",
"value": "[^\\s%]+"
}
}
}
]
}
},
"url_tag": { "url_tag": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [
@@ -7821,6 +8018,27 @@
[ [
"binaryOperator" "binaryOperator"
], ],
[
"predicate"
],
[
"_translate_option"
],
[
"unexpected_argument"
],
[
"variable_attribute",
"_unexpected_word"
],
[
"resetcycle_tag",
"_unexpected_word"
],
[
"library",
"_unexpected_word"
],
[ [
"library", "library",
"load_tag" "load_tag"
@@ -7909,6 +8127,10 @@
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "_group_held" "name": "_group_held"
},
{
"type": "SYMBOL",
"name": "_text_brace"
} }
], ],
"inline": [ "inline": [
+508 -24
View File
File diff suppressed because it is too large. Load diff
+52563 -33491
View File
File diff suppressed because it is too large. Load diff
+37
View File
@@ -57,6 +57,9 @@ enum TokenType {
* the innermost group already holds a tag by that name, which makes this the * the innermost group already holds a tag by that name, which makes this the
* likelier stray of the two */ * likelier stray of the two */
GroupHeld, GroupHeld,
/* a "{" that is text although another "{" follows it, because the second
* one opens a tag or a comment, or although nothing follows it at all */
TextBrace,
}; };
/* The options blocktranslate takes, in the order of the bits recording them. /* The options blocktranslate takes, in the order of the bits recording them.
@@ -773,8 +776,33 @@ typedef enum {
/* no marker, but input was read to decide that, so nothing else may be /* no marker, but input was read to decide that, so nothing else may be
* scanned from here */ * scanned from here */
NotMissingAfterReading, NotMissingAfterReading,
/* no marker, but the "{" read to decide that was text, and was scanned */
TextBraceScanned,
} MissingResult; } MissingResult;
/* Scans TextBrace once its "{" has been read. Django's lexer takes a template
* apart with ({%.*?%}|{{.*?}}|{#.*?#}), so a "{" is text unless one of those
* starts at it: before "{%" or "{#" the tag or comment starts one character
* later, and at the end of input nothing starts at all. The content regex
* cannot look that far ahead, so these are scanned here; "{{{" is not one of
* them, since "{{" does start a variable at its first brace. */
static bool scan_text_brace_rest(TSLexer *const lexer) {
lexer->mark_end(lexer);
if (lexer->eof(lexer)) {
lexer->result_symbol = TextBrace;
return true;
}
if (lexer->lookahead != '{') {
return false;
}
lexer->advance(lexer, false);
if (lexer->lookahead == '%' || lexer->lookahead == '#') {
lexer->result_symbol = TextBrace;
return true;
}
return false;
}
/* Scans the zero-width marker for the innermost group's missing tag. The /* Scans the zero-width marker for the innermost group's missing tag. The
* marker is valid only where that group could close, which is after a body, * marker is valid only where that group could close, which is after a body,
* where nothing else of this scanner is valid but the raw-text tokens, and * where nothing else of this scanner is valid but the raw-text tokens, and
@@ -809,6 +837,10 @@ static MissingResult scan_missing_tag(
} }
lexer->advance(lexer, false); lexer->advance(lexer, false);
if (lexer->lookahead != '%') { if (lexer->lookahead != '%') {
// not a tag, but the "{" may be text the grammar still has to take
if (valid_symbols[TextBrace] && scan_text_brace_rest(lexer)) {
return TextBraceScanned;
}
return NotMissingAfterReading; return NotMissingAfterReading;
} }
lexer->advance(lexer, false); lexer->advance(lexer, false);
@@ -950,6 +982,7 @@ bool tree_sitter_django_external_scanner_scan(
if (valid_symbols[MissingTag]) { if (valid_symbols[MissingTag]) {
switch (scan_missing_tag(scanner, lexer, valid_symbols)) { switch (scan_missing_tag(scanner, lexer, valid_symbols)) {
case Missing: case Missing:
case TextBraceScanned:
return true; return true;
case NotMissingAfterReading: case NotMissingAfterReading:
return false; return false;
@@ -957,6 +990,10 @@ bool tree_sitter_django_external_scanner_scan(
break; break;
} }
} }
if (valid_symbols[TextBrace] && lexer->lookahead == '{') {
lexer->advance(lexer, false);
return scan_text_brace_rest(lexer);
}
if (valid_symbols[GroupOpenTagRead]) { if (valid_symbols[GroupOpenTagRead]) {
scan_group_open_tag_read(scanner, lexer); scan_group_open_tag_read(scanner, lexer);
return true; return true;
+129 -10
View File
@@ -380,13 +380,29 @@ Blocktrans With Plural And No Count
================== ==================
Blocktrans Counting Two Bindings Blocktrans Counting Two Bindings
:error
================== ==================
{% blocktrans count n=x c=d %}one{% plural %}many{% endblocktrans %} {% blocktrans count n=x c=d %}one{% plural %}many{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content)
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Nothing To Bind Blocktrans With Nothing To Bind
@@ -400,13 +416,21 @@ Blocktrans With Nothing To Bind
================== ==================
Blocktrans With An Unknown Option Blocktrans With An Unknown Option
:error
================== ==================
{% blocktrans bogus %}hi{% endblocktrans %} {% blocktrans bogus %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Asvar And No Name Blocktrans With Asvar And No Name
@@ -478,73 +502,155 @@ Blocktrans With Two Plurals
================== ==================
Blocktrans With An Argument On Plural Blocktrans With An Argument On Plural
:error
================== ==================
{% blocktrans count n=x %}one{% plural extra %}many{% endblocktrans %} {% blocktrans count n=x %}one{% plural extra %}many{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(plural_block
(plural_tag
(unexpected_argument))
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Trimmed Twice Blocktrans With Trimmed Twice
:error
================== ==================
{% blocktrans trimmed trimmed %}hi{% endblocktrans %} {% blocktrans trimmed trimmed %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With With Twice Blocktrans With With Twice
:error
================== ==================
{% blocktrans with a=b with c=d %}hi{% endblocktrans %} {% blocktrans with a=b with c=d %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Context Twice Blocktrans With Context Twice
:error
================== ==================
{% blocktrans context "a" context "b" %}hi{% endblocktrans %} {% blocktrans context "a" context "b" %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Asvar Twice Blocktrans With Asvar Twice
:error
================== ==================
{% blocktrans asvar x asvar y %}hi{% endblocktrans %} {% blocktrans asvar x asvar y %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Count Twice Blocktrans With Count Twice
:error
================== ==================
{% blocktrans count n=x count m=y %}a{% plural %}b{% endblocktrans %} {% blocktrans count n=x count m=y %}a{% plural %}b{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content)
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktranslate With Trimmed Twice Blocktranslate With Trimmed Twice
:error
================== ==================
{% blocktranslate trimmed trimmed %}hi{% endblocktranslate %} {% blocktranslate trimmed trimmed %}hi{% endblocktranslate %}
--- ---
(template
(content)
(blocktranslate_group
(blocktranslate_block
(blocktranslate_tag
(unexpected_argument))
(content))
(endblocktranslate_tag))
(content))
================== ==================
Blocktrans Repeating An Option Of The Tag Before It Blocktrans Repeating An Option Of The Tag Before It
@@ -570,10 +676,23 @@ Blocktrans Repeating An Option Of The Tag Before It
================== ==================
Blocktrans With A Binding Twice Blocktrans With A Binding Twice
:error
================== ==================
{% blocktrans with a=1 a=2 %}x{% endblocktrans %} {% blocktrans with a=1 a=2 %}x{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
+20 -1
View File
@@ -221,13 +221,31 @@ Cache Without A Fragment Name
================== ==================
Cache With Anything After Using Cache With Anything After Using
:error
================== ==================
{% cache 500 sidebar using="c" extra %}x{% endcache %} {% cache 500 sidebar using="c" extra %}x{% endcache %}
--- ---
(template
(content)
(cache_group
(cache_block
(cache_tag
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endcache_tag))
(content))
================== ==================
Cache With An Empty Using Cache With An Empty Using
@@ -257,3 +275,4 @@ Cache With Using As Its Fragment Name
{% cache 500 using="c" %}x{% endcache %} {% cache 500 using="c" %}x{% endcache %}
--- ---
+33 -8
View File
@@ -48,43 +48,68 @@ Csp Nonce Attr With Media Keyword
================== ==================
Csp Nonce Attr Takes One Argument Csp Nonce Attr Takes One Argument
:error
================== ==================
{% csp_nonce_attr "a" "b" %} {% csp_nonce_attr "a" "b" %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Csp Nonce Attr Rejects Unknown Keyword Csp Nonce Attr Rejects Unknown Keyword
:error
================== ==================
{% csp_nonce_attr other="b" %} {% csp_nonce_attr other="b" %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(unexpected_argument))
(content))
================== ==================
Csp Nonce Attr Rejects Repeated Keyword Csp Nonce Attr Rejects Repeated Keyword
:error
================== ==================
{% csp_nonce_attr media=1 media=2 %} {% csp_nonce_attr media=1 media=2 %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Csp Nonce Attr With Media Twice Csp Nonce Attr With Media Twice
:error
================== ==================
{% csp_nonce_attr media="a" media="b" %} {% csp_nonce_attr media="a" media="b" %}
--- ---
(template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
+11 -1
View File
@@ -294,13 +294,23 @@ A Name Reserved For A Tag Is Still An Argument
================== ==================
Custom Tag With A Keyword Argument Twice Custom Tag With A Keyword Argument Twice
:error
================== ==================
{% mytag a=1 a=2 %} {% mytag a=1 a=2 %}
--- ---
(template
(content)
(custom_tag
(identifier)
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Custom Tag With Keyword Arguments And As Custom Tag With Keyword Arguments And As
+31 -2
View File
@@ -486,13 +486,27 @@ For Reversed With A Tag In Its Body
================== ==================
For With Reversed Twice For With Reversed Twice
:error
================== ==================
{% for x in y reversed reversed %}a{% endfor %} {% for x in y reversed reversed %}a{% endfor %}
--- ---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(template
(content)))
(endfor_tag))
(content))
================== ==================
For With Reversed Before In For With Reversed Before In
@@ -503,6 +517,7 @@ For With Reversed Before In
--- ---
================== ==================
For Over A Variable Named Reversed For Over A Variable Named Reversed
================== ==================
@@ -587,10 +602,24 @@ For With Reversed As Its Whole Sequence
================== ==================
For With Reversed Three Times For With Reversed Three Times
:error
================== ==================
{% for x in reversed reversed reversed %}a{% endfor %} {% for x in reversed reversed reversed %}a{% endfor %}
--- ---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(template
(content)))
(endfor_tag))
(content))
+33 -3
View File
@@ -180,13 +180,21 @@ Trans Without A Message
================== ==================
Trans With An Option Twice Trans With An Option Twice
:error
================== ==================
{% trans "hi" noop noop %} {% trans "hi" noop noop %}
--- ---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Trans With As Twice Trans With As Twice
@@ -200,13 +208,21 @@ Trans With As Twice
================== ==================
Trans With An Unknown Option Trans With An Unknown Option
:error
================== ==================
{% trans "hi" bogus %} {% trans "hi" bogus %}
--- ---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Trans With Context And No Value Trans With Context And No Value
@@ -400,13 +416,26 @@ Language Without An Argument
================== ==================
Language With Two Arguments Language With Two Arguments
:error
================== ==================
{% language "a" "b" %}x{% endlanguage %} {% language "a" "b" %}x{% endlanguage %}
--- ---
(template
(content)
(language_group
(language_block
(language_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endlanguage_tag))
(content))
================== ==================
Language Name Filter Language Name Filter
@@ -493,6 +522,7 @@ Language Name Filter With An Argument
--- ---
================== ==================
Translate With A Context Named After Noop Translate With A Context Named After Noop
:error :error
+5 -2
View File
@@ -219,8 +219,11 @@ b
(ifchanged_block (ifchanged_block
(ifchanged_tag) (ifchanged_tag)
(template (template
(content) (content)))
(unexpected_tag) (else_block
(else_tag
(unexpected_argument))
(template
(content))) (content)))
(endifchanged_tag)) (endifchanged_tag))
(content)) (content))
+56 -5
View File
@@ -163,13 +163,26 @@ Include With Only Before With
================== ==================
Include With A Binding Twice Include With A Binding Twice
:error
================== ==================
{% include "t.html" with a=1 a=2 %} {% include "t.html" with a=1 a=2 %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Include Without A Name Include Without A Name
@@ -193,33 +206,62 @@ Include With Nothing To Bind
================== ==================
Include With With Twice Include With With Twice
:error
================== ==================
{% include "t.html" with a=1 with b=2 %} {% include "t.html" with a=1 with b=2 %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Include With Only Twice Include With Only Twice
:error
================== ==================
{% include "t.html" only only %} {% include "t.html" only only %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Include With An Unknown Option Include With An Unknown Option
:error
================== ==================
{% include "t.html" bogus %} {% include "t.html" bogus %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Include With A Legacy Binding Include With A Legacy Binding
@@ -233,9 +275,18 @@ Include With A Legacy Binding
================== ==================
Include With A Second Name Include With A Second Name
:error
================== ==================
{% include "t.html" "u.html" %} {% include "t.html" "u.html" %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
+21 -3
View File
@@ -95,23 +95,41 @@ Localize Nested
================== ==================
Localize With Another Argument Localize With Another Argument
:error
================== ==================
{% localize maybe %}x{% endlocalize %} {% localize maybe %}x{% endlocalize %}
--- ---
(template
(content)
(localize_group
(localize_block
(localize_tag
(unexpected_argument))
(template
(content)))
(endlocalize_tag))
(content))
================== ==================
Localize With Two Arguments Localize With Two Arguments
:error
================== ==================
{% localize on off %}x{% endlocalize %} {% localize on off %}x{% endlocalize %}
--- ---
(template
(content)
(localize_group
(localize_block
(localize_tag
(unexpected_argument))
(template
(content)))
(endlocalize_tag))
(content))
================== ==================
Localize Unclosed Localize Unclosed
@@ -130,7 +148,6 @@ Localize Unclosed
(content))) (content)))
(missing_endlocalize_tag))) (missing_endlocalize_tag)))
================== ==================
Localize Filter Localize Filter
================== ==================
@@ -187,3 +204,4 @@ Unlocalize Filter With An Argument
{{ x|unlocalize:1 }} {{ x|unlocalize:1 }}
--- ---
+2 -1
View File
@@ -950,7 +950,8 @@ Endblock Naming No Open Block
(block_tag (block_tag
(push_block)) (push_block))
(template (template
(unexpected_tag) (unexpected_tag
(unexpected_argument))
(content))) (content)))
(missing_endblock_tag)))) (missing_endblock_tag))))
(missing_endblock_tag))) (missing_endblock_tag)))
+26 -2
View File
@@ -100,19 +100,43 @@ Querystring With Two Keys
================== ==================
Querystring With A Key Twice Querystring With A Key Twice
:error
================== ==================
{% querystring page=2 page=3 %} {% querystring page=2 page=3 %}
--- ---
(template
(content)
(querystring_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Querystring With A Key Repeated Later Querystring With A Key Repeated Later
:error
================== ==================
{% querystring a=1 b=2 a=3 %} {% querystring a=1 b=2 a=3 %}
--- ---
(template
(content)
(querystring_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
+24 -3
View File
@@ -81,23 +81,40 @@ Static Without A Path
================== ==================
Static With A Second Path Static With A Second Path
:error
================== ==================
{% static "a.css" "b.css" %} {% static "a.css" "b.css" %}
--- ---
(template
(content)
(static_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Static With Anything After As Static With Anything After As
:error
================== ==================
{% static "a.css" as url junk %} {% static "a.css" as url junk %}
--- ---
(template
(content)
(static_tag
(filtered_value
(value
(literal
(string))))
(identifier)
(unexpected_argument))
(content))
================== ==================
Get Static Prefix Get Static Prefix
@@ -142,13 +159,17 @@ Get Media Prefix With As
================== ==================
Get Static Prefix Without As Get Static Prefix Without As
:error
================== ==================
{% get_static_prefix prefix %} {% get_static_prefix prefix %}
--- ---
(template
(content)
(get_static_prefix_tag
(unexpected_argument))
(content))
================== ==================
Get Static Prefix Without A Variable Get Static Prefix Without A Variable
+110
View File
@@ -211,6 +211,7 @@ Template Variable Literal Adjacent To Name
--- ---
================== ==================
Template Variable With Escaped String Template Variable With Escaped String
================== ==================
@@ -272,6 +273,7 @@ Template Variable Attribute Beginning With Underscore
--- ---
================== ==================
Template Variable Translated String Template Variable Translated String
================== ==================
@@ -320,6 +322,7 @@ Template Variable Translated String With Spaces
--- ---
================== ==================
Template Variable Spanning Lines Template Variable Spanning Lines
================== ==================
@@ -462,6 +465,7 @@ Template Variable Attribute Of A Literal
--- ---
================== ==================
Template Variable Trailing Dot With An Exponent Template Variable Trailing Dot With An Exponent
================== ==================
@@ -489,6 +493,7 @@ Template Variable Trailing Dot
--- ---
================== ==================
Template Variable Signed Trailing Dot Template Variable Signed Trailing Dot
:error :error
@@ -498,3 +503,108 @@ Template Variable Signed Trailing Dot
--- ---
==================
Braces That Open Nothing Are Text
==================
{% if x %}Hello {} world{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
A Brace Before A Tag Is Text
==================
Hello {{% if x %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
A Brace Before A Comment Is Text
==================
a{{# c #}
---
(template
(content)
(template_comment)
(content))
==================
A Brace At The End Is Text
==================
a{
---
(template
(content))
==================
A Brace Before The End Tag Of A Group Is Text
==================
{% if x %}Hello {{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
Three Braces Open A Variable
:error
==================
a{{{ x }}
---
+41 -4
View File
@@ -54,23 +54,41 @@ Localtime Off
================== ==================
Localtime With Another Argument Localtime With Another Argument
:error
================== ==================
{% localtime maybe %}x{% endlocaltime %} {% localtime maybe %}x{% endlocaltime %}
--- ---
(template
(content)
(localtime_group
(localtime_block
(localtime_tag
(unexpected_argument))
(template
(content)))
(endlocaltime_tag))
(content))
================== ==================
Localtime With Two Arguments Localtime With Two Arguments
:error
================== ==================
{% localtime on off %}x{% endlocaltime %} {% localtime on off %}x{% endlocaltime %}
--- ---
(template
(content)
(localtime_group
(localtime_block
(localtime_tag
(unexpected_argument))
(template
(content)))
(endlocaltime_tag))
(content))
================== ==================
Timezone Timezone
@@ -152,13 +170,26 @@ Timezone Without An Argument
================== ==================
Timezone With Two Arguments Timezone With Two Arguments
:error
================== ==================
{% timezone "a" "b" %}x{% endtimezone %} {% timezone "a" "b" %}x{% endtimezone %}
--- ---
(template
(content)
(timezone_group
(timezone_block
(timezone_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endtimezone_tag))
(content))
================== ==================
Get Current Timezone Get Current Timezone
@@ -186,13 +217,18 @@ Get Current Timezone Without As
================== ==================
Get Current Timezone With Anything After As Get Current Timezone With Anything After As
:error
================== ==================
{% get_current_timezone as tz junk %} {% get_current_timezone as tz junk %}
--- ---
(template
(content)
(get_current_timezone_tag
(identifier)
(unexpected_argument))
(content))
================== ==================
Localtime Filter Localtime Filter
@@ -272,3 +308,4 @@ Localtime Filter With An Argument
{{ x|localtime:1 }} {{ x|localtime:1 }}
--- ---
+35 -2
View File
@@ -68,23 +68,56 @@ With Multiple Variables
================== ==================
With A Binding Twice With A Binding Twice
:error
================== ==================
{% with a=1 a=2 %}x{% endwith %} {% with a=1 a=2 %}x{% endwith %}
--- ---
(template
(content)
(with_group
(with_block
(with_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(template
(content)))
(endwith_tag))
(content))
================== ==================
With A Binding Repeated Later With A Binding Repeated Later
:error
================== ==================
{% with a=1 b=2 a=3 %}x{% endwith %} {% with a=1 b=2 a=3 %}x{% endwith %}
--- ---
(template
(content)
(with_group
(with_block
(with_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(template
(content)))
(endwith_tag))
(content))
================== ==================
With Repeating A Binding Of The Tag Before It With Repeating A Binding Of The Tag Before It