added more utility rules and updated example
This commit is contained in:
1 parent
036928d628
commit
cb8c9f9c0b
12 files changed
+34911
-32242
No files matched your search
@@ -46,7 +46,8 @@ argument, so `{% cycle 1as x %}` is the single token `1as` — an error, not `1`
|
|||||||
can never be made mandatory, so the separator is an explicit token and every rule is built from helpers
|
can never be made mandatory, so the separator is an explicit token and every rule is built from helpers
|
||||||
that place it:
|
that place it:
|
||||||
|
|
||||||
- `SEP` — `/[ \t\r\n]+/`. Newlines included: Django matches tags with `re.DOTALL`, so tags may span lines.
|
- `SEP` — `sym("_sep")`, the hidden rule `_sep: /[ \t\r\n]+/`, which a grammar extending this one writes as
|
||||||
|
`$._sep`. Newlines included: Django matches tags with `re.DOTALL`, so tags may span lines.
|
||||||
- `part(...)` — one tag part, taking the whitespace that separates it from the part before.
|
- `part(...)` — one tag part, taking the whitespace that separates it from the part before.
|
||||||
- `joined(first, ...rest)` — parts separated from _each other_, with no leading separator (`not in`).
|
- `joined(first, ...rest)` — parts separated from _each other_, with no leading separator (`not in`).
|
||||||
- `block(tag, ...args)` — `{%`, optional separator, tag name, args, optional separator, `%}`; tags may
|
- `block(tag, ...args)` — `{%`, optional separator, tag name, args, optional separator, `%}`; tags may
|
||||||
@@ -90,9 +91,16 @@ Consequences worth knowing before editing a rule:
|
|||||||
Every group is split so that each tag that opens a body owns a node of its own:
|
Every group is split so that each tag that opens a body owns a node of its own:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
if_clause: ($) => seq(block("if", part($.predicate)), optional($.template)),
|
if_block: ($) => startBlock("if", part($.predicate)),
|
||||||
|
if_clause: ($) => seq($.if_block, optional($.template)),
|
||||||
|
endif_block: ($) => endBlock("endif"),
|
||||||
if_group: ($) =>
|
if_group: ($) =>
|
||||||
seq($.if_clause, repeat($.elif_clause), optional($.else_clause), block("endif")),
|
seq(
|
||||||
|
$.if_clause,
|
||||||
|
repeat($.elif_clause),
|
||||||
|
optional($.else_clause),
|
||||||
|
choice($.endif_block, alias($._missing_block, $.missing_endif_block)),
|
||||||
|
),
|
||||||
```
|
```
|
||||||
|
|
||||||
**A clause is complete without the closing tag, and that is the point.** While a template is being edited
|
**A clause is complete without the closing tag, and that is the point.** While a template is being edited
|
||||||
@@ -111,7 +119,20 @@ Stripping `_block` gives the name; a new tag, or a tag group's new part, follows
|
|||||||
|
|
||||||
A group's last child is its end block or, where the end tag is missing, a zero-width marker named for it
|
A group's last child is its end block or, where the end tag is missing, a zero-width marker named for it
|
||||||
(`missing_endif_block`; see the scanner section). So an unterminated group still reduces to a group node,
|
(`missing_endif_block`; see the scanner section). So an unterminated group still reduces to a group node,
|
||||||
and only a stray tag or a body the grammar cannot read produces an `ERROR`.
|
and a group's tag written where no open group can hold it is an `unexpected_block` (see the load section
|
||||||
|
below), so only a body the grammar cannot read produces an `ERROR`.
|
||||||
|
|
||||||
|
Every tag is built from ten inline utility rules — `simpleBlockOpen`/`simpleBlockClose`,
|
||||||
|
`startBlockOpen`/`startBlockClose`,
|
||||||
|
`followBlockOpen`/`followBlockClose`, `repeatBlockOpen`/`repeatBlockClose`, `endBlockOpen`/`endBlockClose` —
|
||||||
|
through the helpers `block` (a tag outside any group, from `simpleBlockOpen`/`simpleBlockClose`), `startBlock`, `followBlock` (a middle tag a group
|
||||||
|
holds at most once: `else`, `empty`, `plural`), `repeatBlock` (one it may repeat: `elif`) and `endBlock`.
|
||||||
|
They are inline rules rather than JS helpers so that a grammar extending this one can use them. **Every
|
||||||
|
tag must begin with one of the opens**, one an extending grammar adds included: the token they read the
|
||||||
|
tag's name with is valid wherever a tag's name is and the scanner always returns it, so a tag written from a bare `"{%"` never matches: its name is read as a `custom_tag_block` instead,
|
||||||
|
with no error to show for it. Choose `followBlock` or `repeatBlock` by what the grammar allows, because the
|
||||||
|
scanner trusts it: a tag marked once-only that can in fact repeat is an `unexpected_block` from its
|
||||||
|
second use on, and one marked repeatable that cannot is never flagged when repeated.
|
||||||
|
|
||||||
The names themselves need no hand-maintained list either: in the generated `src/node-types.json`, a group's
|
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 end block, and a clause's children include its opening block, so
|
`children` are its clause types and its end block, and a clause's children include its opening block, so
|
||||||
@@ -141,7 +162,8 @@ silently discarded the parse in which the body continues, so every tag inside a
|
|||||||
Sharing the clauses also made the table markedly smaller — 7,200 states and a 515KB library became 5,150
|
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
|
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. Giving every `{% %}` its own `_block`
|
stops duplicating the body states in every group's inline context. Giving every `{% %}` its own `_block`
|
||||||
rule shrank it again, from 1,999 states to 1,818, by the same mechanism.
|
rule shrank it again, from 1,999 states to 1,818, by the same mechanism. The group stack's tokens (see the
|
||||||
|
scanner section) cost 247, to 3,304.
|
||||||
|
|
||||||
### Filter pipes differ by context
|
### Filter pipes differ by context
|
||||||
|
|
||||||
@@ -170,6 +192,20 @@ listed in `NAMES_INSIDE_A_TAG_GROUP` and applied through the `tag_name` reserved
|
|||||||
wraps, so wrapping the whole rule would un-reserve `as` and reject `{% mytag endif %}`. Adding a tag group
|
wraps, so wrapping the whole rule would un-reserve `as` and reject `{% mytag endif %}`. Adding a tag group
|
||||||
with a new part or end tag means adding its name there too.
|
with a new part or end tag means adding its name there too.
|
||||||
|
|
||||||
|
**The same names are what `unexpected_block` accepts.** Once reserved, a group's tag written where no open
|
||||||
|
group can hold it — `{% endif %}` with nothing open, `{% else %}` in a `{% for %}` — used to be an `ERROR`,
|
||||||
|
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
|
||||||
|
stray tag into whichever tag comes next, which then holds the `ERROR`. `unexpected_block` takes any of
|
||||||
|
`NAMES_INSIDE_A_TAG_GROUP` with loose arguments, so there is nothing to recover; `prec.dynamic(-1)` keeps it
|
||||||
|
from displacing a tag the group does take, and `queries/errors.scm` reports it as
|
||||||
|
`@error.unexpected_block`. It added 31 states and no `conflicts` entry.
|
||||||
|
|
||||||
|
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_block` is -1 when
|
||||||
|
`_group_held` confirms the innermost group already holds the name and -2 otherwise, so each tag flagged
|
||||||
|
before its time costs one more, and the first stays the clause 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
|
||||||
each builtin's name into its own token, which the lexer prefers wherever it is valid, so a builtin commits
|
each builtin's name into its own token, which the lexer prefers wherever it is valid, so a builtin commits
|
||||||
to its own alternative and its argument rules still apply. This is why a filter's name and its `:` are
|
to its own alternative and its argument rules still apply. This is why a filter's name and its `:` are
|
||||||
@@ -213,12 +249,12 @@ README nothing checks at all. The library-contents differential below, run again
|
|||||||
|
|
||||||
### External scanner (`src/scanner.c`)
|
### External scanner (`src/scanner.c`)
|
||||||
|
|
||||||
Fifteen external tokens, for the constraints a context-free grammar cannot express:
|
Eighteen external tokens, for the constraints a context-free grammar cannot express:
|
||||||
|
|
||||||
- `push_block`/`push_partial`/`push_verbatim` and the matching `pop_*` — name matching for
|
- `push_block`/`push_partial`/`push_verbatim` and the matching `pop_*` — the names in
|
||||||
`{% block a %}…{% endblock a %}`. They share **one** stack of `{kind, name}` entries, not one stack per
|
`{% block a %}…{% endblock a %}`. A push token reads the name into `pending_name`, which the group takes
|
||||||
kind: the grammar already guarantees nesting, so only the innermost open tag can be closed, and there is
|
when `_group_open_tag_push` pushes it; a pop token checks the written name, or its absence, against the
|
||||||
never a second candidate name to try. Serialized as `<kind><name> ` per entry (`tag_kind_chars`).
|
innermost open group, the only one the grammar lets it close, and `_group_close` then pops it.
|
||||||
- `verbatim_content`, `comment_content` — raw text up to the matching close tag.
|
- `verbatim_content`, `comment_content` — raw text up to the matching close tag.
|
||||||
- `_tag_open`, `_bt_option`, `_kwarg_name` — what a tag has already been given, so that a repeated
|
- `_tag_open`, `_bt_option`, `_kwarg_name` — what a tag has already been given, so that a repeated
|
||||||
argument is refused: `_bt_option` for `{% blocktranslate %}`'s five options, `_kwarg_name` for `name=value`
|
argument is refused: `_bt_option` for `{% blocktranslate %}`'s five options, `_kwarg_name` for `name=value`
|
||||||
@@ -230,28 +266,48 @@ Fifteen external tokens, for the constraints a context-free grammar cannot expre
|
|||||||
flags, not a stack, because the tag cannot nest: its body admits no tags. It lives outside the
|
flags, not a stack, because the tag cannot nest: its body admits no tags. It lives outside the
|
||||||
`stack`/`error` union, is cleared in both branches of `reset_scanner`, and is serialized after the
|
`stack`/`error` union, is cleared in both branches of `reset_scanner`, and is serialized after the
|
||||||
status character, so a GLR stack split copies it like everything else.
|
status character, so a GLR stack split copies it like everything else.
|
||||||
- `_group_open`, `_group_close` — zero width, before the name of a tag that opens or closes a tag group
|
- `_group_open_tag_read`, `_group_open_tag_push`, `_group_follow`, `_group_repeat`, `_group_close`, `_group_held` —
|
||||||
(`groupBlock`). The scanner reads the name ahead of the grammar and pushes or pops a nameless entry, so
|
the stack
|
||||||
the stack holds every open group and not only the three whose names it matches; `block`, `partialdef`
|
of open groups, which the scanner keeps **without knowing any group by name**, so a grammar extending this
|
||||||
and `verbatim` still push and pop through their own tokens, and are the only kinds with a pop token.
|
one gets it for its own groups. The read token sits right after `{%` in every tag, always returns, and
|
||||||
`{% plural %}` goes through `_group_close` too: reading the `count` option (`_bt_option`) turns a
|
reads the tag's name into `read_word`. The other four sit right before `%}` and check that it follows:
|
||||||
blocktranslate entry into its counted kind, which waits for `plural` before its end tag. `tag_groups`
|
push opens a group expecting `"end"` + `read_word` (every Django end tag is spelled that way), follow
|
||||||
holds each kind's opening name and the names that belong to it, and `group_names` **must stay in sync
|
records a middle tag the innermost group may hold only once, repeat records nothing, and close pops the
|
||||||
with `NAMES_INSIDE_A_TAG_GROUP`**.
|
innermost group once `read_word` is what it expects. `_group_held` belongs to `unexpected_block` (see the load section) and returns only when
|
||||||
|
the innermost group already holds a tag named `read_word`. Reading `count` (`_bt_option`) marks the group about
|
||||||
|
to be pushed as waiting for `plural`, which its follow clears. An entry is
|
||||||
|
`{expected, name, middles, awaits_plural}`, serialized as `expected:name:middles:flag` and a newline per
|
||||||
|
entry; entries hold whole names, so serializing checks `TREE_SITTER_SERIALIZATION_BUFFER_SIZE` and goes to
|
||||||
|
the error state rather than overrun it. Push and follow before `%}` cost 247 states over the table-driven
|
||||||
|
scanner (3,057 to 3,304), because argument-final states stop being shared with tags where the token is not
|
||||||
|
valid; after `%}`, where each tag's states are its own, saved only 15.
|
||||||
- `_missing_block` — the marker where a group's required tag block is missing, aliased at each use to
|
- `_missing_block` — the marker where a group's required tag block is missing, aliased at each use to
|
||||||
`missing_endX_block` or `missing_plural_block` (`MISSING_BLOCKS`). It is placed only at the end of
|
`missing_endX_block` or `missing_plural_block` (`MISSING_BLOCKS`). It is placed only at the end of input;
|
||||||
input; before a tag whose name is reserved to some other group, which therefore cannot belong to this
|
before the end tag of a group enclosing the innermost one; before an `endblock`/`endpartialdef` naming a
|
||||||
body; or before an `endblock`/`endpartialdef` naming a block further out than the innermost. It closes
|
block further out; or before a second `plural`, since a blocktranslate body holds no tag that could take it.
|
||||||
the innermost group on the stack as the end tag would, the group reduces with no `ERROR`, and
|
Any other tag is taken to be the innermost group's, as Django's parser takes it — a middle tag it has not
|
||||||
`queries/errors.scm` is what reports it. **It is one token aliased per group, never a token per
|
held yet, and a second of one it holds only once, which is an `unexpected_block` inside it. Marking the
|
||||||
group**: tree-sitter does not merge parse states whose valid external tokens differ, so a token per
|
group closed before a second `else` was tried and dropped: inside `{% block %}`, where most template code
|
||||||
group cloned every body state for each kind of group and took the table from 1,818 states to 17,664
|
sits, there is always an enclosing group to hand the `else` to, so the inner group's own end tag became a
|
||||||
(it is 3,057 as built). That trade is also why the scanner keeps a stack of every group instead of
|
stray instead. A counted blocktranslate is missing its
|
||||||
being told by the token which group it is in. A supertype would be the natural way to match every
|
plural before anything but `plural`. The marker closes the group as its end tag would (or records the
|
||||||
marker, but the generator silently drops one whose members are aliases, so `errors.scm` lists them.
|
plural), the group reduces with no `ERROR`, and `queries/errors.scm` reports it. Tags in a raw body are
|
||||||
|
not read: the marker tells the body is raw from `verbatim_content`/`comment_content` being valid, and only
|
||||||
|
the end of input counts there. **It is one token aliased per group, never a token per group**: tree-sitter
|
||||||
|
does not merge parse states whose valid external tokens differ, so a token per group cloned every body
|
||||||
|
state for each kind of group and took the table from 1,818 states to 17,664. A supertype would be the
|
||||||
|
natural way to match every marker, but the generator silently drops one whose members are aliases, so
|
||||||
|
`errors.scm` lists them.
|
||||||
|
|
||||||
|
What a stack that knows no group cannot tell: a middle tag of an enclosing group that the innermost group
|
||||||
|
has never held — `{% if a %}{% for x in y %}{% else %}` — is an `ERROR`, not a marker. Learning each
|
||||||
|
group's middle tags as they appear was tried first and put false markers on valid nesting such as
|
||||||
|
`{% if %}…{% else %}{% if %}…{% else %}`, because the outer group, having held an `else`, claimed the
|
||||||
|
inner one's.
|
||||||
|
|
||||||
- `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
|
||||||
scanner skips whitespace itself while matching names.
|
scanner skips whitespace itself while matching names.
|
||||||
|
|
||||||
Prefer the grammar over the scanner. A useful test: does the constraint change how the input _parses_?
|
Prefer the grammar over the scanner. A useful test: does the constraint change how the input _parses_?
|
||||||
@@ -278,6 +334,11 @@ matching. `_missing_block` is the one exception, and is safe only because it ret
|
|||||||
of input or at `{%`, where `content` cannot match. It is scanned above the raw-text scanners, which find
|
of input or at `{%`, where `content` cannot match. It is scanned above the raw-text scanners, which find
|
||||||
nothing at the end of an empty body.
|
nothing at the end of an empty body.
|
||||||
|
|
||||||
|
The group tokens were checked against that hazard with malformed groups — a missing predicate, junk before
|
||||||
|
`%}`, in an end tag, in `{% else %}` and in `{% plural %}`, and a mismatched block name. Recovery never
|
||||||
|
inserted one of them as `MISSING`, and the stack never fell out of step with the parser: later groups still
|
||||||
|
got the right markers. Junk inside an end tag does leave the whole file an `ERROR`.
|
||||||
|
|
||||||
### Queries
|
### Queries
|
||||||
|
|
||||||
`queries/highlights.scm` lists tag and filter names explicitly, so adding either to the grammar means
|
`queries/highlights.scm` lists tag and filter names explicitly, so adding either to the grammar means
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ Five of the files in `queries/` are query files an editor loads:
|
|||||||
a template is being written — and the tree reports no error of its own, so this query is how to find
|
a template is being written — and the tree reports no error of its own, so this query is how to find
|
||||||
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
|
||||||
|
`unexpected_block`, captured as `@error.unexpected_block`.
|
||||||
|
|
||||||
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:
|
||||||
@@ -137,7 +139,7 @@ What the fallbacks cannot do:
|
|||||||
registered. Cross-referencing names against `{% load %}` needs the project's Python, so it belongs in a
|
registered. Cross-referencing names against `{% load %}` needs the project's Python, so it belongs in a
|
||||||
linter; `queries/libraries.scm` and the `load_block` rule supply what one needs.
|
linter; `queries/libraries.scm` and the `load_block` rule supply what one needs.
|
||||||
- **Names that only exist inside a tag group are reserved**, so `{% endif %}` or `{% empty %}` standing on
|
- **Names that only exist inside a tag group are reserved**, so `{% endif %}` or `{% empty %}` standing on
|
||||||
its own is an error rather than a call to a custom tag of that name.
|
its own is an `unexpected_block`, reported by `errors.scm`, rather than a call to a custom tag of that name.
|
||||||
|
|
||||||
### Adding rules for your own tags and filters
|
### Adding rules for your own tags and filters
|
||||||
|
|
||||||
@@ -149,27 +151,48 @@ alternative of `filter`.
|
|||||||
```js
|
```js
|
||||||
import django from "tree-sitter-django/grammar";
|
import django from "tree-sitter-django/grammar";
|
||||||
|
|
||||||
const SEP = /[ \t\r\n]+/;
|
|
||||||
|
|
||||||
export default grammar(django, {
|
export default grammar(django, {
|
||||||
name: "djangox",
|
name: "djangox",
|
||||||
|
|
||||||
|
conflicts: ($, previous) => [...previous, [$.loop_clause]],
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
|
tag_block_group: ($, previous) =>
|
||||||
|
choice(previous, $.map_block, $.loop_group),
|
||||||
|
|
||||||
// {% map items over rows %}
|
// {% map items over rows %}
|
||||||
tag_block_group: ($, previous) => choice(previous, $.map_block),
|
|
||||||
map_block: ($) =>
|
map_block: ($) =>
|
||||||
seq(
|
seq(
|
||||||
"{%",
|
$.simpleBlockOpen,
|
||||||
optional(SEP),
|
field("tag", "map"),
|
||||||
"map",
|
$._sep,
|
||||||
SEP,
|
|
||||||
$.filtered_value,
|
$.filtered_value,
|
||||||
SEP,
|
$._sep,
|
||||||
"over",
|
"over",
|
||||||
SEP,
|
$._sep,
|
||||||
$.filtered_value,
|
$.filtered_value,
|
||||||
optional(SEP),
|
$.simpleBlockClose,
|
||||||
"%}",
|
),
|
||||||
|
|
||||||
|
// {% loop x %}…{% endloop %}
|
||||||
|
loop_block: ($) =>
|
||||||
|
seq(
|
||||||
|
$.startBlockOpen,
|
||||||
|
field("tag", "loop"),
|
||||||
|
$._sep,
|
||||||
|
$.identifier,
|
||||||
|
$.startBlockClose,
|
||||||
|
),
|
||||||
|
loop_clause: ($) => seq($.loop_block, optional($.template)),
|
||||||
|
endloop_block: ($) =>
|
||||||
|
seq($.endBlockOpen, field("tag", "endloop"), $.endBlockClose),
|
||||||
|
loop_group: ($) =>
|
||||||
|
seq(
|
||||||
|
$.loop_clause,
|
||||||
|
choice(
|
||||||
|
$.endloop_block,
|
||||||
|
alias($._missing_block, $.missing_endloop_block),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// {{ value|shout:"!" }}
|
// {{ value|shout:"!" }}
|
||||||
@@ -182,23 +205,34 @@ export default grammar(django, {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Six things to know:
|
Seven things to know:
|
||||||
|
|
||||||
- **Whitespace is explicit.** `extras` is empty, because Django splits a tag's contents before parsing any
|
- **Whitespace is explicit.** `extras` is empty, because Django splits a tag's contents before parsing any
|
||||||
argument, so every separator has to be written out. The `part`/`block`/`simpleTag` helpers the base
|
argument, so every separator has to be written out. The `part`/`block`/`simpleTag` helpers the base
|
||||||
grammar is built from are private to it, hence the `SEP` above.
|
grammar is built from are private to it, but the separator is the hidden rule `$._sep`, and it and the
|
||||||
|
tag utilities (`simpleBlockOpen` and the rest) are rules, so an extending grammar uses them as they are.
|
||||||
|
- **Every tag begins with one of the opens**: `$.simpleBlockOpen`, with `$.simpleBlockClose` at its end, for a
|
||||||
|
tag outside any group such as `map_block`, and `startBlockOpen`, `followBlockOpen`, `repeatBlockOpen` or
|
||||||
|
`endBlockOpen` for the tags of a group. Its
|
||||||
|
zero-width token hands the tag's name to the scanner and is taken wherever a tag can start, so a tag
|
||||||
|
written from a bare `"{%"` never matches: `{% map items over rows %}` would quietly parse as a
|
||||||
|
`custom_tag_block` instead.
|
||||||
- **A new name wins over the fallback.** `shout` becomes a token of its own, so it commits to your
|
- **A new name wins over the fallback.** `shout` becomes a token of its own, so it commits to your
|
||||||
alternative and your arity applies — `{{ x|shout }}` is now an error, while names you have not modelled
|
alternative and your arity applies — `{{ x|shout }}` is now an error, while names you have not modelled
|
||||||
still fall through to `(filter name: (identifier))`.
|
still fall through to `(filter name: (identifier))`.
|
||||||
- **A tag with a body is split the way the base grammar's are**, so every `{% %}` is a node of its own:
|
- **A tag with a body is split the way the base grammar's are**, so every `{% %}` is a node of its own:
|
||||||
a `map_block` for the opening tag, a `map_clause` of that block and the body, an `endmap_block`, and a
|
a `loop_block` ending in `$.startBlockClose`, a `loop_clause` of that block and the body, an
|
||||||
`map_group` of the clause and the end block. The clause needs a `conflicts` entry, as every clause in
|
`endloop_block` built from `$.endBlockOpen` and `$.endBlockClose`, and a `loop_group` of the clause and the
|
||||||
the base grammar does: `conflicts: ($, previous) => [...previous, [$.map_clause]]`. The generator
|
end block. A middle tag uses `followBlockOpen`/`followBlockClose` if the group holds it at most once, and
|
||||||
offers an associativity instead; taking it silently discards the parse in which the body continues.
|
`repeatBlockOpen`/`repeatBlockClose` if it may repeat; the scanner trusts that choice. The clause needs a
|
||||||
|
`conflicts` entry, as every clause in the base grammar does. The generator offers an associativity
|
||||||
|
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_block` rather than an error — `reserved` takes no
|
- **End tags are not reserved**, a stray `{% endmap %}` parses as a `custom_tag_block` 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_block` 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_block` rule to avoid parsing end tags as custom tags.
|
||||||
- **Your own tag groups get no missing-tag markers.** Which groups the scanner tracks is fixed in its
|
- **Your groups get missing-tag markers.** The scanner knows no group by name — a group is closed by `end`
|
||||||
tables, so an unterminated `{% map %}` is an `ERROR`.
|
followed by its opening tag's name — so the `alias($._missing_block, $.missing_endloop_block)` choice
|
||||||
|
above is all it takes: an unclosed `{% loop x %}`, or one left open inside an `{% if %}` that
|
||||||
|
`{% endif %}` closes, holds a `missing_endloop_block`. Add the name to your copy of `errors.scm`.
|
||||||
- **The external scanner has to be re-exported under your grammar's name**, or block-name matching and the
|
- **The external scanner has to be re-exported under your grammar's name**, or block-name matching and the
|
||||||
repeated-argument guards will not link. Give your grammar a `src/scanner.c` that renames the base's five entry points to the
|
repeated-argument guards will not link. Give your grammar a `src/scanner.c` that renames the base's five entry points to the
|
||||||
ones your generated `parser.c` calls, and includes it:
|
ones your generated `parser.c` calls, and includes it:
|
||||||
|
|||||||
+184
-77
@@ -10,8 +10,12 @@
|
|||||||
* A newline separates parts like any other whitespace, since Django matches a
|
* A newline separates parts like any other whitespace, since Django matches a
|
||||||
* tag with re.DOTALL and splits its contents on whitespace of any kind. The
|
* tag with re.DOTALL and splits its contents on whitespace of any kind. The
|
||||||
* scanner skips the same characters when it matches a name of its own.
|
* scanner skips the same characters when it matches a name of its own.
|
||||||
|
*
|
||||||
|
* The pattern is the hidden rule _sep, which SEP refers to, rather than a regex
|
||||||
|
* written inline, so that a grammar extending this one can write $._sep and
|
||||||
|
* share the one token instead of repeating the pattern.
|
||||||
*/
|
*/
|
||||||
const SEP = /[ \t\r\n]+/;
|
const SEP = sym("_sep");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parts of a tag, each separated from the one before it by whitespace.
|
* Parts of a tag, each separated from the one before it by whitespace.
|
||||||
@@ -44,37 +48,87 @@ function part(first, ...rest) {
|
|||||||
*/
|
*/
|
||||||
function block(tag, ...args) {
|
function block(tag, ...args) {
|
||||||
return seq(
|
return seq(
|
||||||
"{%",
|
sym("simpleBlockOpen"),
|
||||||
optional(SEP),
|
|
||||||
field("tag", tag),
|
field("tag", tag),
|
||||||
...args,
|
...args,
|
||||||
optional(SEP),
|
sym("simpleBlockClose"),
|
||||||
"%}",
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tag that opens or closes a tag group. The scanner keeps a stack of the
|
* The tags of a tag group. The scanner keeps a stack of the groups that are
|
||||||
* groups that are open, which is what lets it tell a missing end tag from one
|
* open, which is what lets it tell a missing end tag from one that belongs to
|
||||||
* that belongs to the group, so `marker` is a zero-width token placed before the
|
* the group, and it knows no group by name: every tag begins with
|
||||||
* tag's name: the scanner reads the name ahead of the grammar to push or pop the
|
* one of the opens (simpleBlockOpen for a tag outside any group), whose zero-width token reads the tag's name to
|
||||||
* group. block, partialdef and verbatim push and pop through the tokens that
|
* the scanner, and the end of the tag says what that name means.
|
||||||
* match their names instead.
|
*
|
||||||
|
* - startBlock: a tag that opens a group, which pushes one expecting "end"
|
||||||
|
* followed by the tag's name.
|
||||||
|
* - followBlock: a middle tag a group may hold only once, such as "else". The
|
||||||
|
* first belongs to the innermost group; a second, once that group already
|
||||||
|
* holds one, cannot be its own, so the innermost group is taken to be missing
|
||||||
|
* its end tag and the tag goes to a group around it.
|
||||||
|
* - repeatBlock: a middle tag a group may hold any number of times, such as
|
||||||
|
* "elif", which always belongs to the innermost group.
|
||||||
|
* - endBlock: the tag that closes the innermost group, which pops it.
|
||||||
|
*
|
||||||
|
* The ten utilities are inline rules rather than helpers, so that a grammar
|
||||||
|
* extending this one can build its own groups from them; every tag it adds has
|
||||||
|
* to begin with one of the opens, since the token they read with is valid
|
||||||
|
* wherever a tag's name is and always wins.
|
||||||
*
|
*
|
||||||
* @param {Rule} marker
|
|
||||||
* @param {RuleOrLiteral} tag
|
* @param {RuleOrLiteral} tag
|
||||||
* @param {RuleOrLiteral[]} args
|
* @param {RuleOrLiteral[]} args
|
||||||
* @returns {SeqRule}
|
* @returns {SeqRule}
|
||||||
*/
|
*/
|
||||||
function groupBlock(marker, tag, ...args) {
|
function startBlock(tag, ...args) {
|
||||||
return seq(
|
return seq(
|
||||||
"{%",
|
sym("startBlockOpen"),
|
||||||
optional(SEP),
|
|
||||||
marker,
|
|
||||||
field("tag", tag),
|
field("tag", tag),
|
||||||
...args,
|
...args,
|
||||||
optional(SEP),
|
sym("startBlockClose"),
|
||||||
"%}",
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RuleOrLiteral} tag
|
||||||
|
* @param {RuleOrLiteral[]} args
|
||||||
|
* @returns {SeqRule}
|
||||||
|
*/
|
||||||
|
function followBlock(tag, ...args) {
|
||||||
|
return seq(
|
||||||
|
sym("followBlockOpen"),
|
||||||
|
field("tag", tag),
|
||||||
|
...args,
|
||||||
|
sym("followBlockClose"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RuleOrLiteral} tag
|
||||||
|
* @param {RuleOrLiteral[]} args
|
||||||
|
* @returns {SeqRule}
|
||||||
|
*/
|
||||||
|
function repeatBlock(tag, ...args) {
|
||||||
|
return seq(
|
||||||
|
sym("repeatBlockOpen"),
|
||||||
|
field("tag", tag),
|
||||||
|
...args,
|
||||||
|
sym("repeatBlockClose"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RuleOrLiteral} tag
|
||||||
|
* @param {RuleOrLiteral[]} args
|
||||||
|
* @returns {SeqRule}
|
||||||
|
*/
|
||||||
|
function endBlock(tag, ...args) {
|
||||||
|
return seq(
|
||||||
|
sym("endBlockOpen"),
|
||||||
|
field("tag", tag),
|
||||||
|
...args,
|
||||||
|
sym("endBlockClose"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,10 +296,9 @@ function simpleTag($, tag, args = true, kwargs = true) {
|
|||||||
function blocktransRules(tag) {
|
function blocktransRules(tag) {
|
||||||
return {
|
return {
|
||||||
[`_${tag}_block`]: ($) =>
|
[`_${tag}_block`]: ($) =>
|
||||||
groupBlock($._group_open, tag, $._tag_open, repeat($._translate_option)),
|
startBlock(tag, $._tag_open, repeat($._translate_option)),
|
||||||
[`_${tag}_count_block`]: ($) =>
|
[`_${tag}_count_block`]: ($) =>
|
||||||
groupBlock(
|
startBlock(
|
||||||
$._group_open,
|
|
||||||
tag,
|
tag,
|
||||||
$._tag_open,
|
$._tag_open,
|
||||||
repeat($._translate_option),
|
repeat($._translate_option),
|
||||||
@@ -267,7 +320,7 @@ function blocktransRules(tag) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
[`end${tag}_block`]: ($) => groupBlock($._group_close, `end${tag}`),
|
[`end${tag}_block`]: ($) => endBlock(`end${tag}`),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,13 +513,24 @@ const django = grammar({
|
|||||||
[$.spaceless_clause],
|
[$.spaceless_clause],
|
||||||
[$.timezone_clause],
|
[$.timezone_clause],
|
||||||
[$.with_clause],
|
[$.with_clause],
|
||||||
[$.predicate],
|
|
||||||
[$.binaryOperator],
|
[$.binaryOperator],
|
||||||
[$.library, $.load_block],
|
[$.library, $.load_block],
|
||||||
[$._translate_option],
|
|
||||||
[$._filtered_value_spaced],
|
[$._filtered_value_spaced],
|
||||||
[$._filter_expression_spaced],
|
[$._filter_expression_spaced],
|
||||||
],
|
],
|
||||||
|
// the tags of a group; see startBlock
|
||||||
|
inline: ($) => [
|
||||||
|
$.simpleBlockOpen,
|
||||||
|
$.simpleBlockClose,
|
||||||
|
$.startBlockOpen,
|
||||||
|
$.startBlockClose,
|
||||||
|
$.followBlockOpen,
|
||||||
|
$.followBlockClose,
|
||||||
|
$.repeatBlockOpen,
|
||||||
|
$.repeatBlockClose,
|
||||||
|
$.endBlockOpen,
|
||||||
|
$.endBlockClose,
|
||||||
|
],
|
||||||
supertypes: ($) => [$.template_node, $.tag_block_group],
|
supertypes: ($) => [$.template_node, $.tag_block_group],
|
||||||
externals: ($) => [
|
externals: ($) => [
|
||||||
$.matcher_error,
|
$.matcher_error,
|
||||||
@@ -482,8 +546,12 @@ const django = grammar({
|
|||||||
$._bt_option,
|
$._bt_option,
|
||||||
$._kwarg_name,
|
$._kwarg_name,
|
||||||
$._missing_block,
|
$._missing_block,
|
||||||
$._group_open,
|
$._group_open_tag_read,
|
||||||
|
$._group_open_tag_push,
|
||||||
|
$._group_follow,
|
||||||
|
$._group_repeat,
|
||||||
$._group_close,
|
$._group_close,
|
||||||
|
$._group_held,
|
||||||
],
|
],
|
||||||
reserved: {
|
reserved: {
|
||||||
global: ($) => ["not", "if", "in", "is", "as", "for", "from"],
|
global: ($) => ["not", "if", "in", "is", "as", "for", "from"],
|
||||||
@@ -491,7 +559,20 @@ const django = grammar({
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
template: ($) => repeat1(choice($.template_node, $.content)),
|
template: ($) => repeat1(choice($.template_node, $.content)),
|
||||||
|
// the tags of a group; see startBlock
|
||||||
|
simpleBlockOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
|
||||||
|
simpleBlockClose: ($) => seq(optional(SEP), "%}"),
|
||||||
|
startBlockOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
|
||||||
|
startBlockClose: ($) => seq($._group_open_tag_push, optional(SEP), "%}"),
|
||||||
|
followBlockOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
|
||||||
|
followBlockClose: ($) => seq($._group_follow, optional(SEP), "%}"),
|
||||||
|
repeatBlockOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
|
||||||
|
repeatBlockClose: ($) => seq($._group_repeat, optional(SEP), "%}"),
|
||||||
|
endBlockOpen: ($) => seq("{%", $._group_open_tag_read, optional(SEP)),
|
||||||
|
endBlockClose: ($) => seq($._group_close, optional(SEP), "%}"),
|
||||||
content: ($) => /(?:[^\{]|\{[^\{#%}])+/,
|
content: ($) => /(?:[^\{]|\{[^\{#%}])+/,
|
||||||
|
// the separator between the parts of a tag; see SEP
|
||||||
|
_sep: ($) => /[ \t\r\n]+/,
|
||||||
template_node: ($) =>
|
template_node: ($) =>
|
||||||
choice($.tag_block_group, $.template_variable, $.template_comment),
|
choice($.tag_block_group, $.template_variable, $.template_comment),
|
||||||
filtered_value: ($) =>
|
filtered_value: ($) =>
|
||||||
@@ -627,6 +708,7 @@ const django = grammar({
|
|||||||
$.templatetag_block,
|
$.templatetag_block,
|
||||||
$.timezone_group,
|
$.timezone_group,
|
||||||
$.translate_block,
|
$.translate_block,
|
||||||
|
$.unexpected_block,
|
||||||
$.url_block,
|
$.url_block,
|
||||||
$.verbatim_group,
|
$.verbatim_group,
|
||||||
$.widthratio_block,
|
$.widthratio_block,
|
||||||
@@ -676,9 +758,9 @@ const django = grammar({
|
|||||||
optional(seq(":", field("argument", $.value))),
|
optional(seq(":", field("argument", $.value))),
|
||||||
),
|
),
|
||||||
autoescape_block: ($) =>
|
autoescape_block: ($) =>
|
||||||
groupBlock($._group_open, "autoescape", part(choice("on", "off"))),
|
startBlock("autoescape", part(choice("on", "off"))),
|
||||||
autoescape_clause: ($) => seq($.autoescape_block, optional($.template)),
|
autoescape_clause: ($) => seq($.autoescape_block, optional($.template)),
|
||||||
endautoescape_block: ($) => groupBlock($._group_close, "endautoescape"),
|
endautoescape_block: ($) => endBlock("endautoescape"),
|
||||||
autoescape_group: ($) =>
|
autoescape_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.autoescape_clause,
|
$.autoescape_clause,
|
||||||
@@ -688,9 +770,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// the scanner takes the whitespace before a name it has to match itself
|
// the scanner takes the whitespace before a name it has to match itself
|
||||||
block_block: ($) => block("block", field("name", $.push_block)),
|
block_block: ($) => startBlock("block", field("name", $.push_block)),
|
||||||
block_clause: ($) => seq($.block_block, optional($.template)),
|
block_clause: ($) => seq($.block_block, optional($.template)),
|
||||||
endblock_block: ($) => block("endblock", $.pop_block),
|
endblock_block: ($) => endBlock("endblock", $.pop_block),
|
||||||
block_group: ($) =>
|
block_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.block_clause,
|
$.block_clause,
|
||||||
@@ -699,7 +781,7 @@ const django = grammar({
|
|||||||
alias($._missing_block, $.missing_endblock_block),
|
alias($._missing_block, $.missing_endblock_block),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
plural_block: ($) => groupBlock($._group_close, "plural"),
|
plural_block: ($) => followBlock("plural"),
|
||||||
plural_clause: ($) => seq($.plural_block, optional($._translate_body)),
|
plural_clause: ($) => seq($.plural_block, optional($._translate_body)),
|
||||||
...blocktransRules("blocktrans"),
|
...blocktransRules("blocktrans"),
|
||||||
...blocktransRules("blocktranslate"),
|
...blocktransRules("blocktranslate"),
|
||||||
@@ -765,8 +847,7 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
cache_block: ($) =>
|
cache_block: ($) =>
|
||||||
groupBlock(
|
startBlock(
|
||||||
$._group_open,
|
|
||||||
"cache",
|
"cache",
|
||||||
part($.filtered_value),
|
part($.filtered_value),
|
||||||
part(choice($.identifier, $.string)),
|
part(choice($.identifier, $.string)),
|
||||||
@@ -774,7 +855,7 @@ const django = grammar({
|
|||||||
optional(part(seq("using=", $.filtered_value))),
|
optional(part(seq("using=", $.filtered_value))),
|
||||||
),
|
),
|
||||||
cache_clause: ($) => seq($.cache_block, optional($.template)),
|
cache_clause: ($) => seq($.cache_block, optional($.template)),
|
||||||
endcache_block: ($) => groupBlock($._group_close, "endcache"),
|
endcache_block: ($) => endBlock("endcache"),
|
||||||
cache_group: ($) =>
|
cache_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.cache_clause,
|
$.cache_clause,
|
||||||
@@ -783,10 +864,9 @@ const django = grammar({
|
|||||||
alias($._missing_block, $.missing_endcache_block),
|
alias($._missing_block, $.missing_endcache_block),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
comment_block: ($) =>
|
comment_block: ($) => startBlock("comment", optional(part($.string))),
|
||||||
groupBlock($._group_open, "comment", optional(part($.string))),
|
|
||||||
comment_clause: ($) => seq($.comment_block, optional($.comment_content)),
|
comment_clause: ($) => seq($.comment_block, optional($.comment_content)),
|
||||||
endcomment_block: ($) => groupBlock($._group_close, "endcomment"),
|
endcomment_block: ($) => endBlock("endcomment"),
|
||||||
comment_group: ($) =>
|
comment_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.comment_clause,
|
$.comment_clause,
|
||||||
@@ -823,13 +903,12 @@ const django = grammar({
|
|||||||
// than as split arguments, so pipes here take whitespace just as they do
|
// than as split arguments, so pipes here take whitespace just as they do
|
||||||
// between "{{" and "}}"
|
// between "{{" and "}}"
|
||||||
filter_block: ($) =>
|
filter_block: ($) =>
|
||||||
groupBlock(
|
startBlock(
|
||||||
$._group_open,
|
|
||||||
"filter",
|
"filter",
|
||||||
part(alias($._filter_expression_spaced, $.filter_expression)),
|
part(alias($._filter_expression_spaced, $.filter_expression)),
|
||||||
),
|
),
|
||||||
filter_clause: ($) => seq($.filter_block, optional($.template)),
|
filter_clause: ($) => seq($.filter_block, optional($.template)),
|
||||||
endfilter_block: ($) => groupBlock($._group_close, "endfilter"),
|
endfilter_block: ($) => endBlock("endfilter"),
|
||||||
filter_group: ($) =>
|
filter_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.filter_clause,
|
$.filter_clause,
|
||||||
@@ -845,8 +924,7 @@ const django = grammar({
|
|||||||
optional(asVariable($)),
|
optional(asVariable($)),
|
||||||
),
|
),
|
||||||
for_block: ($) =>
|
for_block: ($) =>
|
||||||
groupBlock(
|
startBlock(
|
||||||
$._group_open,
|
|
||||||
"for",
|
"for",
|
||||||
part(field("variable", $.identifier)),
|
part(field("variable", $.identifier)),
|
||||||
repeat(
|
repeat(
|
||||||
@@ -873,9 +951,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
for_clause: ($) => seq($.for_block, optional($.template)),
|
for_clause: ($) => seq($.for_block, optional($.template)),
|
||||||
empty_block: ($) => block("empty"),
|
empty_block: ($) => followBlock("empty"),
|
||||||
empty_clause: ($) => seq($.empty_block, optional($.template)),
|
empty_clause: ($) => seq($.empty_block, optional($.template)),
|
||||||
endfor_block: ($) => groupBlock($._group_close, "endfor"),
|
endfor_block: ($) => endBlock("endfor"),
|
||||||
for_group: ($) =>
|
for_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.for_clause,
|
$.for_clause,
|
||||||
@@ -911,14 +989,14 @@ const django = grammar({
|
|||||||
block("get_media_prefix", optional(asVariable($))),
|
block("get_media_prefix", optional(asVariable($))),
|
||||||
get_static_prefix_block: ($) =>
|
get_static_prefix_block: ($) =>
|
||||||
block("get_static_prefix", optional(asVariable($))),
|
block("get_static_prefix", optional(asVariable($))),
|
||||||
if_block: ($) => groupBlock($._group_open, "if", part($.predicate)),
|
if_block: ($) => startBlock("if", part($.predicate)),
|
||||||
if_clause: ($) => seq($.if_block, optional($.template)),
|
if_clause: ($) => seq($.if_block, optional($.template)),
|
||||||
elif_block: ($) => block("elif", part($.predicate)),
|
elif_block: ($) => repeatBlock("elif", part($.predicate)),
|
||||||
elif_clause: ($) => seq($.elif_block, optional($.template)),
|
elif_clause: ($) => seq($.elif_block, optional($.template)),
|
||||||
/** Shared by if_group and ifchanged_group, which spell it the same way. */
|
/** Shared by if_group and ifchanged_group, which spell it the same way. */
|
||||||
else_block: ($) => block("else"),
|
else_block: ($) => followBlock("else"),
|
||||||
else_clause: ($) => seq($.else_block, optional($.template)),
|
else_clause: ($) => seq($.else_block, optional($.template)),
|
||||||
endif_block: ($) => groupBlock($._group_close, "endif"),
|
endif_block: ($) => endBlock("endif"),
|
||||||
if_group: ($) =>
|
if_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.if_clause,
|
$.if_clause,
|
||||||
@@ -927,9 +1005,9 @@ const django = grammar({
|
|||||||
choice($.endif_block, alias($._missing_block, $.missing_endif_block)),
|
choice($.endif_block, alias($._missing_block, $.missing_endif_block)),
|
||||||
),
|
),
|
||||||
ifchanged_block: ($) =>
|
ifchanged_block: ($) =>
|
||||||
groupBlock($._group_open, "ifchanged", repeat(part($.filtered_value))),
|
startBlock("ifchanged", repeat(part($.filtered_value))),
|
||||||
ifchanged_clause: ($) => seq($.ifchanged_block, optional($.template)),
|
ifchanged_clause: ($) => seq($.ifchanged_block, optional($.template)),
|
||||||
endifchanged_block: ($) => groupBlock($._group_close, "endifchanged"),
|
endifchanged_block: ($) => endBlock("endifchanged"),
|
||||||
ifchanged_group: ($) =>
|
ifchanged_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.ifchanged_clause,
|
$.ifchanged_clause,
|
||||||
@@ -955,10 +1033,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
/** django.templatetags.i18n. The language tag takes the one argument. */
|
/** django.templatetags.i18n. The language tag takes the one argument. */
|
||||||
language_block: ($) =>
|
language_block: ($) => startBlock("language", part($.filtered_value)),
|
||||||
groupBlock($._group_open, "language", part($.filtered_value)),
|
|
||||||
language_clause: ($) => seq($.language_block, optional($.template)),
|
language_clause: ($) => seq($.language_block, optional($.template)),
|
||||||
endlanguage_block: ($) => groupBlock($._group_close, "endlanguage"),
|
endlanguage_block: ($) => endBlock("endlanguage"),
|
||||||
language_group: ($) =>
|
language_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.language_clause,
|
$.language_clause,
|
||||||
@@ -981,13 +1058,9 @@ const django = grammar({
|
|||||||
* rejects anything but "on" or "off", including a second word.
|
* rejects anything but "on" or "off", including a second word.
|
||||||
*/
|
*/
|
||||||
localize_block: ($) =>
|
localize_block: ($) =>
|
||||||
groupBlock(
|
startBlock("localize", optional(part(choice("on", "off")))),
|
||||||
$._group_open,
|
|
||||||
"localize",
|
|
||||||
optional(part(choice("on", "off"))),
|
|
||||||
),
|
|
||||||
localize_clause: ($) => seq($.localize_block, optional($.template)),
|
localize_clause: ($) => seq($.localize_block, optional($.template)),
|
||||||
endlocalize_block: ($) => groupBlock($._group_close, "endlocalize"),
|
endlocalize_block: ($) => endBlock("endlocalize"),
|
||||||
localize_group: ($) =>
|
localize_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.localize_clause,
|
$.localize_clause,
|
||||||
@@ -998,13 +1071,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
/** django.templatetags.tz, taking "on" or "off" as localize does. */
|
/** django.templatetags.tz, taking "on" or "off" as localize does. */
|
||||||
localtime_block: ($) =>
|
localtime_block: ($) =>
|
||||||
groupBlock(
|
startBlock("localtime", optional(part(choice("on", "off")))),
|
||||||
$._group_open,
|
|
||||||
"localtime",
|
|
||||||
optional(part(choice("on", "off"))),
|
|
||||||
),
|
|
||||||
localtime_clause: ($) => seq($.localtime_block, optional($.template)),
|
localtime_clause: ($) => seq($.localtime_block, optional($.template)),
|
||||||
endlocaltime_block: ($) => groupBlock($._group_close, "endlocaltime"),
|
endlocaltime_block: ($) => endBlock("endlocaltime"),
|
||||||
localtime_group: ($) =>
|
localtime_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.localtime_clause,
|
$.localtime_clause,
|
||||||
@@ -1023,9 +1092,9 @@ const django = grammar({
|
|||||||
now_block: ($) => block("now", part($.string), optional(asVariable($))),
|
now_block: ($) => block("now", part($.string), optional(asVariable($))),
|
||||||
partial_block: ($) => block("partial", part($.identifier)),
|
partial_block: ($) => block("partial", part($.identifier)),
|
||||||
partialdef_block: ($) =>
|
partialdef_block: ($) =>
|
||||||
block("partialdef", $.push_partial, optional(part("inline"))),
|
startBlock("partialdef", $.push_partial, optional(part("inline"))),
|
||||||
partialdef_clause: ($) => seq($.partialdef_block, optional($.template)),
|
partialdef_clause: ($) => seq($.partialdef_block, optional($.template)),
|
||||||
endpartialdef_block: ($) => block("endpartialdef", $.pop_partial),
|
endpartialdef_block: ($) => endBlock("endpartialdef", $.pop_partial),
|
||||||
partialdef_group: ($) =>
|
partialdef_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.partialdef_clause,
|
$.partialdef_clause,
|
||||||
@@ -1045,9 +1114,9 @@ const django = grammar({
|
|||||||
optional(asVariable($)),
|
optional(asVariable($)),
|
||||||
),
|
),
|
||||||
resetcycle_block: ($) => block("resetcycle", optional(part($.identifier))),
|
resetcycle_block: ($) => block("resetcycle", optional(part($.identifier))),
|
||||||
spaceless_block: ($) => groupBlock($._group_open, "spaceless"),
|
spaceless_block: ($) => startBlock("spaceless"),
|
||||||
spaceless_clause: ($) => seq($.spaceless_block, optional($.template)),
|
spaceless_clause: ($) => seq($.spaceless_block, optional($.template)),
|
||||||
endspaceless_block: ($) => groupBlock($._group_close, "endspaceless"),
|
endspaceless_block: ($) => endBlock("endspaceless"),
|
||||||
spaceless_group: ($) =>
|
spaceless_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.spaceless_clause,
|
$.spaceless_clause,
|
||||||
@@ -1075,10 +1144,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
/** django.templatetags.tz. timezone_tag takes the one argument. */
|
/** django.templatetags.tz. timezone_tag takes the one argument. */
|
||||||
timezone_block: ($) =>
|
timezone_block: ($) => startBlock("timezone", part($.filtered_value)),
|
||||||
groupBlock($._group_open, "timezone", part($.filtered_value)),
|
|
||||||
timezone_clause: ($) => seq($.timezone_block, optional($.template)),
|
timezone_clause: ($) => seq($.timezone_block, optional($.template)),
|
||||||
endtimezone_block: ($) => groupBlock($._group_close, "endtimezone"),
|
endtimezone_block: ($) => endBlock("endtimezone"),
|
||||||
timezone_group: ($) =>
|
timezone_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.timezone_clause,
|
$.timezone_clause,
|
||||||
@@ -1109,6 +1177,43 @@ const django = grammar({
|
|||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* A tag of some group, written where no open group can hold it: an end tag
|
||||||
|
* with nothing open, or a middle tag the innermost group does not take. It
|
||||||
|
* is a node of its own rather than an ERROR so that error recovery has
|
||||||
|
* nothing to repair: recovery rewinds to just after the "{%" every tag
|
||||||
|
* starts with, and folds the stray tag into whichever tag comes next.
|
||||||
|
* Dynamic precedence keeps it from displacing a tag its group does take,
|
||||||
|
* and queries/errors.scm reports it.
|
||||||
|
*
|
||||||
|
* Of several tags with a name the group may hold only once, such as a
|
||||||
|
* 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
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
unexpected_block: ($) =>
|
||||||
|
choice(
|
||||||
|
prec.dynamic(
|
||||||
|
-1,
|
||||||
|
seq(
|
||||||
|
sym("simpleBlockOpen"),
|
||||||
|
field("tag", choice(...NAMES_INSIDE_A_TAG_GROUP)),
|
||||||
|
repeat(seq(SEP, /[^\s%]+/)),
|
||||||
|
$._group_held,
|
||||||
|
sym("simpleBlockClose"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
prec.dynamic(
|
||||||
|
-2,
|
||||||
|
seq(
|
||||||
|
sym("simpleBlockOpen"),
|
||||||
|
field("tag", choice(...NAMES_INSIDE_A_TAG_GROUP)),
|
||||||
|
repeat(seq(SEP, /[^\s%]+/)),
|
||||||
|
sym("simpleBlockClose"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
url_block: ($) =>
|
url_block: ($) =>
|
||||||
block(
|
block(
|
||||||
"url",
|
"url",
|
||||||
@@ -1121,9 +1226,9 @@ const django = grammar({
|
|||||||
),
|
),
|
||||||
optional(asVariable($)),
|
optional(asVariable($)),
|
||||||
),
|
),
|
||||||
verbatim_block: ($) => block("verbatim", $.push_verbatim),
|
verbatim_block: ($) => startBlock("verbatim", $.push_verbatim),
|
||||||
verbatim_clause: ($) => seq($.verbatim_block, optional($.verbatim_content)),
|
verbatim_clause: ($) => seq($.verbatim_block, optional($.verbatim_content)),
|
||||||
endverbatim_block: ($) => block("endverbatim", $.pop_verbatim),
|
endverbatim_block: ($) => endBlock("endverbatim", $.pop_verbatim),
|
||||||
verbatim_group: ($) =>
|
verbatim_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.verbatim_clause,
|
$.verbatim_clause,
|
||||||
@@ -1140,14 +1245,16 @@ const django = grammar({
|
|||||||
part($.filtered_value),
|
part($.filtered_value),
|
||||||
optional(asVariable($)),
|
optional(asVariable($)),
|
||||||
),
|
),
|
||||||
with_block: ($) =>
|
with_block: ($) => startBlock("with", $._tag_open, $._tag_kwargs),
|
||||||
groupBlock($._group_open, "with", $._tag_open, $._tag_kwargs),
|
|
||||||
with_clause: ($) => seq($.with_block, optional($.template)),
|
with_clause: ($) => seq($.with_block, optional($.template)),
|
||||||
endwith_block: ($) => groupBlock($._group_close, "endwith"),
|
endwith_block: ($) => endBlock("endwith"),
|
||||||
with_group: ($) =>
|
with_group: ($) =>
|
||||||
seq(
|
seq(
|
||||||
$.with_clause,
|
$.with_clause,
|
||||||
choice($.endwith_block, alias($._missing_block, $.missing_endwith_block)),
|
choice(
|
||||||
|
$.endwith_block,
|
||||||
|
alias($._missing_block, $.missing_endwith_block),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,3 +39,5 @@
|
|||||||
(missing_endverbatim_block)
|
(missing_endverbatim_block)
|
||||||
(missing_endwith_block)
|
(missing_endwith_block)
|
||||||
(missing_plural_block)] @error.missing_block
|
(missing_plural_block)] @error.missing_block
|
||||||
|
|
||||||
|
(unexpected_block) @error.unexpected_block
|
||||||
+1154
-2422
File diff suppressed because it is too large.
Load diff
@@ -167,6 +167,10 @@
|
|||||||
"type": "translate_block",
|
"type": "translate_block",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "unexpected_block",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "url_block",
|
"type": "url_block",
|
||||||
"named": true
|
"named": true
|
||||||
@@ -3050,6 +3054,106 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "unexpected_block",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"tag": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "elif",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "else",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "empty",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endautoescape",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endblock",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endblocktrans",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endblocktranslate",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endcache",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endcomment",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endfilter",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endfor",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endif",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endifchanged",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endlanguage",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endlocalize",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endlocaltime",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endpartialdef",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endspaceless",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endtimezone",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endverbatim",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "endwith",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "plural",
|
||||||
|
"named": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "url_block",
|
"type": "url_block",
|
||||||
"named": true,
|
"named": true,
|
||||||
|
|||||||
+32298
-29126
File diff suppressed because it is too large.
Load diff
+474
-534
File diff suppressed because it is too large.
Load diff
@@ -335,7 +335,6 @@ Blocktrans Closed By The Other Spelling
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktranslate Closed By The Other Spelling
|
Blocktranslate Closed By The Other Spelling
|
||||||
:error
|
:error
|
||||||
@@ -346,7 +345,6 @@ Blocktranslate Closed By The Other Spelling
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Count And No Plural
|
Blocktrans With Count And No Plural
|
||||||
==================
|
==================
|
||||||
@@ -370,8 +368,6 @@ Blocktrans With Count And No Plural
|
|||||||
(endblocktrans_block))
|
(endblocktrans_block))
|
||||||
(content))
|
(content))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Plural And No Count
|
Blocktrans With Plural And No Count
|
||||||
:error
|
:error
|
||||||
@@ -382,7 +378,6 @@ Blocktrans With Plural And No Count
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans Counting Two Bindings
|
Blocktrans Counting Two Bindings
|
||||||
:error
|
:error
|
||||||
@@ -393,7 +388,6 @@ Blocktrans Counting Two Bindings
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Nothing To Bind
|
Blocktrans With Nothing To Bind
|
||||||
:error
|
:error
|
||||||
@@ -404,7 +398,6 @@ Blocktrans With Nothing To Bind
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With An Unknown Option
|
Blocktrans With An Unknown Option
|
||||||
:error
|
:error
|
||||||
@@ -415,7 +408,6 @@ Blocktrans With An Unknown Option
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Asvar And No Name
|
Blocktrans With Asvar And No Name
|
||||||
:error
|
:error
|
||||||
@@ -426,7 +418,6 @@ Blocktrans With Asvar And No Name
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans Holding A Tag
|
Blocktrans Holding A Tag
|
||||||
:error
|
:error
|
||||||
@@ -437,7 +428,6 @@ Blocktrans Holding A Tag
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans Holding A Comment
|
Blocktrans Holding A Comment
|
||||||
:error
|
:error
|
||||||
@@ -448,7 +438,6 @@ Blocktrans Holding A Comment
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans Holding Another Blocktrans
|
Blocktrans Holding Another Blocktrans
|
||||||
:error
|
:error
|
||||||
@@ -459,17 +448,33 @@ Blocktrans Holding Another Blocktrans
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Two Plurals
|
Blocktrans With Two Plurals
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% blocktrans count n=x %}one{% plural %}many{% plural %}x{% endblocktrans %}
|
{% blocktrans count n=x %}one{% plural %}many{% plural %}x{% endblocktrans %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(blocktranslate_group
|
||||||
|
(blocktrans_clause
|
||||||
|
(blocktrans_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(content)
|
||||||
|
(plural_clause
|
||||||
|
(plural_block)
|
||||||
|
(content)))
|
||||||
|
(missing_endblocktrans_block))
|
||||||
|
(unexpected_block)
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With An Argument On Plural
|
Blocktrans With An Argument On Plural
|
||||||
@@ -481,7 +486,6 @@ Blocktrans With An Argument On Plural
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Trimmed Twice
|
Blocktrans With Trimmed Twice
|
||||||
:error
|
:error
|
||||||
@@ -492,7 +496,6 @@ Blocktrans With Trimmed Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With With Twice
|
Blocktrans With With Twice
|
||||||
:error
|
:error
|
||||||
@@ -503,7 +506,6 @@ Blocktrans With With Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Context Twice
|
Blocktrans With Context Twice
|
||||||
:error
|
:error
|
||||||
@@ -514,7 +516,6 @@ Blocktrans With Context Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Asvar Twice
|
Blocktrans With Asvar Twice
|
||||||
:error
|
:error
|
||||||
@@ -525,7 +526,6 @@ Blocktrans With Asvar Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans With Count Twice
|
Blocktrans With Count Twice
|
||||||
:error
|
:error
|
||||||
@@ -536,7 +536,6 @@ Blocktrans With Count Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktranslate With Trimmed Twice
|
Blocktranslate With Trimmed Twice
|
||||||
:error
|
:error
|
||||||
@@ -547,7 +546,6 @@ Blocktranslate With Trimmed Twice
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Blocktrans Repeating An Option Of The Tag Before It
|
Blocktrans Repeating An Option Of The Tag Before It
|
||||||
==================
|
==================
|
||||||
@@ -579,4 +577,3 @@ Blocktrans With A Binding Twice
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
@@ -230,25 +230,32 @@ Builtin Filter With An Argument It Does Not Take
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Custom Tag Does Not Cover An End Tag
|
Custom Tag Does Not Cover An End Tag
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Custom Tag Does Not Cover A Part Of A Tag Group
|
Custom Tag Does Not Cover A Part Of A Tag Group
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
A Name Reserved For A Tag Is Still A Variable
|
A Name Reserved For A Tag Is Still A Variable
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ c
|
|||||||
|
|
||||||
==================
|
==================
|
||||||
Ifchanged With Two Elses
|
Ifchanged With Two Elses
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% ifchanged %}
|
{% ifchanged %}
|
||||||
@@ -186,10 +185,24 @@ c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(ifchanged_group
|
||||||
|
(ifchanged_clause
|
||||||
|
(ifchanged_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endifchanged_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Ifchanged With An Argument On Else
|
Ifchanged With An Argument On Else
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% ifchanged %}
|
{% ifchanged %}
|
||||||
@@ -199,3 +212,15 @@ b
|
|||||||
{% endifchanged %}
|
{% endifchanged %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(ifchanged_group
|
||||||
|
(ifchanged_clause
|
||||||
|
(ifchanged_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endifchanged_block))
|
||||||
|
(content))
|
||||||
+496
-8
@@ -164,7 +164,7 @@ Missing Endif Before An Outer End Tag
|
|||||||
(content))
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Missing Endfor Before An Outer Else
|
Unseen Outer Else Is Not A Missing Tag
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% if a %}{% for x in y %}{% else %}b{% endif %}
|
{% if a %}{% for x in y %}{% else %}b{% endif %}
|
||||||
@@ -189,12 +189,11 @@ Missing Endfor Before An Outer Else
|
|||||||
(filtered_value
|
(filtered_value
|
||||||
(value
|
(value
|
||||||
(variable_attribute
|
(variable_attribute
|
||||||
(identifier))))))
|
(identifier)))))
|
||||||
(missing_endfor_block))))
|
|
||||||
(else_clause
|
|
||||||
(else_block)
|
|
||||||
(template
|
(template
|
||||||
|
(unexpected_block)
|
||||||
(content)))
|
(content)))
|
||||||
|
(missing_endfor_block))))
|
||||||
(endif_block))
|
(endif_block))
|
||||||
(content))
|
(content))
|
||||||
|
|
||||||
@@ -462,35 +461,524 @@ Missing Endif After A Custom End Tag
|
|||||||
(content)))
|
(content)))
|
||||||
(missing_endif_block)))
|
(missing_endif_block)))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second Else After A Nested Else Is Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% if a %}{% if b %}x{% else %}y{% else %}z{% endif %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endif_block))
|
||||||
|
(content)))
|
||||||
|
(missing_endif_block)))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second Empty After A Nested Empty Is Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% for x in y %}{% for z in w %}a{% empty %}b{% empty %}c{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(empty_clause
|
||||||
|
(empty_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endfor_block))
|
||||||
|
(content)))
|
||||||
|
(missing_endfor_block)))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Else Of An If Nested In An Else
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% if a %}x{% else %}{% if b %}y{% else %}z{% endif %}{% endif %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(endif_block))))
|
||||||
|
(endif_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Elif Of An If Nested In An Elif
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% if a %}x{% elif b %}{% if c %}y{% elif d %}z{% endif %}{% endif %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(elif_clause
|
||||||
|
(elif_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(elif_clause
|
||||||
|
(elif_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(endif_block))))
|
||||||
|
(endif_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Empty Of A For Nested In An Empty
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% for x in y %}a{% empty %}{% for z in w %}b{% empty %}c{% endfor %}{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(empty_clause
|
||||||
|
(empty_block)
|
||||||
|
(template
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(empty_clause
|
||||||
|
(empty_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(endfor_block))))
|
||||||
|
(endfor_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Else Of An Ifchanged Nested In An Else Of An If
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% if a %}x{% else %}{% ifchanged %}y{% else %}z{% endifchanged %}{% endif %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(ifchanged_group
|
||||||
|
(ifchanged_clause
|
||||||
|
(ifchanged_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(endifchanged_block))))
|
||||||
|
(endif_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Else Of An If Nested In An Else Of An Ifchanged
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% ifchanged %}a{% else %}{% if b %}y{% else %}z{% endif %}{% endifchanged %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(ifchanged_group
|
||||||
|
(ifchanged_clause
|
||||||
|
(ifchanged_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(endif_block))))
|
||||||
|
(endifchanged_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Third Else Is Unexpected Too
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% if a %}w{% else %}x{% else %}y{% else %}z{% endif %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endif_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second And Third Empty Are Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% for x in y %}a{% empty %}b{% empty %}c{% empty %}d{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(empty_clause
|
||||||
|
(empty_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endfor_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second And Third Else Of An Ifchanged Are Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% ifchanged %}a{% else %}b{% else %}c{% else %}d{% endifchanged %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(ifchanged_group
|
||||||
|
(ifchanged_clause
|
||||||
|
(ifchanged_block)
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endifchanged_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second Else Inside A Block Is Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% block a %}{% if b %}x{% else %}y{% else %}z{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(block_group
|
||||||
|
(block_clause
|
||||||
|
(block_block
|
||||||
|
(push_block))
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endif_block))))
|
||||||
|
(endblock_block
|
||||||
|
(pop_block)))
|
||||||
|
(content))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Second Else Of An If In A For Is Unexpected
|
||||||
|
==================
|
||||||
|
|
||||||
|
{% for x in y %}{% if a %}b{% else %}c{% else %}d{% endif %}{% endfor %}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(for_group
|
||||||
|
(for_clause
|
||||||
|
(for_block
|
||||||
|
(identifier)
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))
|
||||||
|
(template
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(content)))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(endif_block))))
|
||||||
|
(endfor_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Stray End Tag After A Missing One
|
Stray End Tag After A Missing One
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% if a %}{% endfor %}
|
{% if a %}{% endfor %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier))))))
|
||||||
|
(template
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(missing_endif_block)))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Endblock Naming No Open Block
|
Endblock Naming No Open Block
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% block a %}{% block b %}{% endblock c %}
|
{% block a %}{% block b %}{% endblock c %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(block_group
|
||||||
|
(block_clause
|
||||||
|
(block_block
|
||||||
|
(push_block))
|
||||||
|
(template
|
||||||
|
(block_group
|
||||||
|
(block_clause
|
||||||
|
(block_block
|
||||||
|
(push_block))
|
||||||
|
(template
|
||||||
|
(unexpected_block)
|
||||||
|
(content)))
|
||||||
|
(missing_endblock_block))))
|
||||||
|
(missing_endblock_block)))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Second Else Is Not A Missing Tag
|
Second Else Is Not A Missing Tag
|
||||||
:error
|
|
||||||
==================
|
==================
|
||||||
|
|
||||||
{% if a %}{% else %}{% else %}{% endif %}
|
{% if a %}{% else %}{% else %}{% endif %}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
(template
|
||||||
|
(content)
|
||||||
|
(if_group
|
||||||
|
(if_clause
|
||||||
|
(if_block
|
||||||
|
(predicate
|
||||||
|
(filtered_value
|
||||||
|
(value
|
||||||
|
(variable_attribute
|
||||||
|
(identifier)))))))
|
||||||
|
(else_clause
|
||||||
|
(else_block)
|
||||||
|
(template
|
||||||
|
(unexpected_block)))
|
||||||
|
(endif_block))
|
||||||
|
(content))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Plural Without Count Is Not A Missing Tag
|
Plural Without Count Is Not A Missing Tag
|
||||||
|
|||||||
Reference in new issue
Block a user