added more utility rules and updated example
This commit is contained in:
1 parent
036928d628
commit
cb8c9f9c0b
12 files changed
+34909
-32240
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
|
||||
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.
|
||||
- `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
|
||||
@@ -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:
|
||||
|
||||
```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: ($) =>
|
||||
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
|
||||
@@ -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
|
||||
(`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
|
||||
`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
|
||||
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`
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
@@ -213,12 +249,12 @@ README nothing checks at all. The library-contents differential below, run again
|
||||
|
||||
### 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
|
||||
`{% block a %}…{% endblock a %}`. They share **one** stack of `{kind, name}` entries, not one stack per
|
||||
kind: the grammar already guarantees nesting, so only the innermost open tag can be closed, and there is
|
||||
never a second candidate name to try. Serialized as `<kind><name> ` per entry (`tag_kind_chars`).
|
||||
- `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
|
||||
when `_group_open_tag_push` pushes it; a pop token checks the written name, or its absence, against the
|
||||
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.
|
||||
- `_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`
|
||||
@@ -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
|
||||
`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.
|
||||
- `_group_open`, `_group_close` — zero width, before the name of a tag that opens or closes a tag group
|
||||
(`groupBlock`). The scanner reads the name ahead of the grammar and pushes or pops a nameless entry, so
|
||||
the stack holds every open group and not only the three whose names it matches; `block`, `partialdef`
|
||||
and `verbatim` still push and pop through their own tokens, and are the only kinds with a pop token.
|
||||
`{% plural %}` goes through `_group_close` too: reading the `count` option (`_bt_option`) turns a
|
||||
blocktranslate entry into its counted kind, which waits for `plural` before its end tag. `tag_groups`
|
||||
holds each kind's opening name and the names that belong to it, and `group_names` **must stay in sync
|
||||
with `NAMES_INSIDE_A_TAG_GROUP`**.
|
||||
- `_group_open_tag_read`, `_group_open_tag_push`, `_group_follow`, `_group_repeat`, `_group_close`, `_group_held` —
|
||||
the stack
|
||||
of open groups, which the scanner keeps **without knowing any group by name**, so a grammar extending this
|
||||
one gets it for its own groups. The read token sits right after `{%` in every tag, always returns, and
|
||||
reads the tag's name into `read_word`. The other four sit right before `%}` and check that it follows:
|
||||
push opens a group expecting `"end"` + `read_word` (every Django end tag is spelled that way), follow
|
||||
records a middle tag the innermost group may hold only once, repeat records nothing, and close pops the
|
||||
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_endX_block` or `missing_plural_block` (`MISSING_BLOCKS`). It is placed only at the end of
|
||||
input; before a tag whose name is reserved to some other group, which therefore cannot belong to this
|
||||
body; or before an `endblock`/`endpartialdef` naming a block further out than the innermost. It closes
|
||||
the innermost group on the stack as the end tag would, the group reduces with no `ERROR`, and
|
||||
`queries/errors.scm` is what reports it. **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
|
||||
(it is 3,057 as built). That trade is also why the scanner keeps a stack of every group instead of
|
||||
being told by the token which group it is in. 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.
|
||||
`missing_endX_block` or `missing_plural_block` (`MISSING_BLOCKS`). It is placed only at the end of input;
|
||||
before the end tag of a group enclosing the innermost one; before an `endblock`/`endpartialdef` naming a
|
||||
block further out; or before a second `plural`, since a blocktranslate body holds no tag that could take it.
|
||||
Any other tag is taken to be the innermost group's, as Django's parser takes it — a middle tag it has not
|
||||
held yet, and a second of one it holds only once, which is an `unexpected_block` inside it. Marking the
|
||||
group closed before a second `else` was tried and dropped: inside `{% block %}`, where most template code
|
||||
sits, there is always an enclosing group to hand the `else` to, so the inner group's own end tag became a
|
||||
stray instead. A counted blocktranslate is missing its
|
||||
plural before anything but `plural`. The marker closes the group as its end tag would (or records the
|
||||
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.
|
||||
|
||||
`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.
|
||||
|
||||
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
|
||||
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/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
|
||||
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.
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -149,27 +151,48 @@ alternative of `filter`.
|
||||
```js
|
||||
import django from "tree-sitter-django/grammar";
|
||||
|
||||
const SEP = /[ \t\r\n]+/;
|
||||
|
||||
export default grammar(django, {
|
||||
name: "djangox",
|
||||
|
||||
conflicts: ($, previous) => [...previous, [$.loop_clause]],
|
||||
|
||||
rules: {
|
||||
tag_block_group: ($, previous) =>
|
||||
choice(previous, $.map_block, $.loop_group),
|
||||
|
||||
// {% map items over rows %}
|
||||
tag_block_group: ($, previous) => choice(previous, $.map_block),
|
||||
map_block: ($) =>
|
||||
seq(
|
||||
"{%",
|
||||
optional(SEP),
|
||||
"map",
|
||||
SEP,
|
||||
$.simpleBlockOpen,
|
||||
field("tag", "map"),
|
||||
$._sep,
|
||||
$.filtered_value,
|
||||
SEP,
|
||||
$._sep,
|
||||
"over",
|
||||
SEP,
|
||||
$._sep,
|
||||
$.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:"!" }}
|
||||
@@ -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
|
||||
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
|
||||
alternative and your arity applies — `{{ x|shout }}` is now an error, while names you have not modelled
|
||||
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 `map_block` for the opening tag, a `map_clause` of that block and the body, an `endmap_block`, and a
|
||||
`map_group` of the clause and the end block. The clause needs a `conflicts` entry, as every clause in
|
||||
the base grammar does: `conflicts: ($, previous) => [...previous, [$.map_clause]]`. The generator
|
||||
offers an associativity instead; taking it silently discards the parse in which the body continues.
|
||||
a `loop_block` ending in `$.startBlockClose`, a `loop_clause` of that block and the body, an
|
||||
`endloop_block` built from `$.endBlockOpen` and `$.endBlockClose`, and a `loop_group` of the clause and the
|
||||
end block. A middle tag uses `followBlockOpen`/`followBlockClose` if the group holds it at most once, and
|
||||
`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
|
||||
`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
|
||||
tables, so an unterminated `{% map %}` is an `ERROR`.
|
||||
- **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_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
|
||||
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:
|
||||
|
||||
+184
-77
@@ -10,8 +10,12 @@
|
||||
* 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
|
||||
* 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.
|
||||
@@ -44,37 +48,87 @@ function part(first, ...rest) {
|
||||
*/
|
||||
function block(tag, ...args) {
|
||||
return seq(
|
||||
"{%",
|
||||
optional(SEP),
|
||||
sym("simpleBlockOpen"),
|
||||
field("tag", tag),
|
||||
...args,
|
||||
optional(SEP),
|
||||
"%}",
|
||||
sym("simpleBlockClose"),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A tag that opens or closes a tag group. The scanner keeps a stack of the
|
||||
* groups that are open, which is what lets it tell a missing end tag from one
|
||||
* that belongs to the group, so `marker` is a zero-width token placed before the
|
||||
* tag's name: the scanner reads the name ahead of the grammar to push or pop the
|
||||
* group. block, partialdef and verbatim push and pop through the tokens that
|
||||
* match their names instead.
|
||||
* The tags of a tag group. The scanner keeps a stack of the groups that are
|
||||
* open, which is what lets it tell a missing end tag from one that belongs to
|
||||
* the group, and it knows no group by name: every tag begins with
|
||||
* one of the opens (simpleBlockOpen for a tag outside any group), whose zero-width token reads the tag's name to
|
||||
* the scanner, and the end of the tag says what that name means.
|
||||
*
|
||||
* - 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[]} args
|
||||
* @returns {SeqRule}
|
||||
*/
|
||||
function groupBlock(marker, tag, ...args) {
|
||||
function startBlock(tag, ...args) {
|
||||
return seq(
|
||||
"{%",
|
||||
optional(SEP),
|
||||
marker,
|
||||
sym("startBlockOpen"),
|
||||
field("tag", tag),
|
||||
...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) {
|
||||
return {
|
||||
[`_${tag}_block`]: ($) =>
|
||||
groupBlock($._group_open, tag, $._tag_open, repeat($._translate_option)),
|
||||
startBlock(tag, $._tag_open, repeat($._translate_option)),
|
||||
[`_${tag}_count_block`]: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
startBlock(
|
||||
tag,
|
||||
$._tag_open,
|
||||
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],
|
||||
[$.timezone_clause],
|
||||
[$.with_clause],
|
||||
[$.predicate],
|
||||
[$.binaryOperator],
|
||||
[$.library, $.load_block],
|
||||
[$._translate_option],
|
||||
[$._filtered_value_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],
|
||||
externals: ($) => [
|
||||
$.matcher_error,
|
||||
@@ -482,8 +546,12 @@ const django = grammar({
|
||||
$._bt_option,
|
||||
$._kwarg_name,
|
||||
$._missing_block,
|
||||
$._group_open,
|
||||
$._group_open_tag_read,
|
||||
$._group_open_tag_push,
|
||||
$._group_follow,
|
||||
$._group_repeat,
|
||||
$._group_close,
|
||||
$._group_held,
|
||||
],
|
||||
reserved: {
|
||||
global: ($) => ["not", "if", "in", "is", "as", "for", "from"],
|
||||
@@ -491,7 +559,20 @@ const django = grammar({
|
||||
},
|
||||
rules: {
|
||||
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: ($) => /(?:[^\{]|\{[^\{#%}])+/,
|
||||
// the separator between the parts of a tag; see SEP
|
||||
_sep: ($) => /[ \t\r\n]+/,
|
||||
template_node: ($) =>
|
||||
choice($.tag_block_group, $.template_variable, $.template_comment),
|
||||
filtered_value: ($) =>
|
||||
@@ -627,6 +708,7 @@ const django = grammar({
|
||||
$.templatetag_block,
|
||||
$.timezone_group,
|
||||
$.translate_block,
|
||||
$.unexpected_block,
|
||||
$.url_block,
|
||||
$.verbatim_group,
|
||||
$.widthratio_block,
|
||||
@@ -676,9 +758,9 @@ const django = grammar({
|
||||
optional(seq(":", field("argument", $.value))),
|
||||
),
|
||||
autoescape_block: ($) =>
|
||||
groupBlock($._group_open, "autoescape", part(choice("on", "off"))),
|
||||
startBlock("autoescape", part(choice("on", "off"))),
|
||||
autoescape_clause: ($) => seq($.autoescape_block, optional($.template)),
|
||||
endautoescape_block: ($) => groupBlock($._group_close, "endautoescape"),
|
||||
endautoescape_block: ($) => endBlock("endautoescape"),
|
||||
autoescape_group: ($) =>
|
||||
seq(
|
||||
$.autoescape_clause,
|
||||
@@ -688,9 +770,9 @@ const django = grammar({
|
||||
),
|
||||
),
|
||||
// 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)),
|
||||
endblock_block: ($) => block("endblock", $.pop_block),
|
||||
endblock_block: ($) => endBlock("endblock", $.pop_block),
|
||||
block_group: ($) =>
|
||||
seq(
|
||||
$.block_clause,
|
||||
@@ -699,7 +781,7 @@ const django = grammar({
|
||||
alias($._missing_block, $.missing_endblock_block),
|
||||
),
|
||||
),
|
||||
plural_block: ($) => groupBlock($._group_close, "plural"),
|
||||
plural_block: ($) => followBlock("plural"),
|
||||
plural_clause: ($) => seq($.plural_block, optional($._translate_body)),
|
||||
...blocktransRules("blocktrans"),
|
||||
...blocktransRules("blocktranslate"),
|
||||
@@ -765,8 +847,7 @@ const django = grammar({
|
||||
),
|
||||
),
|
||||
cache_block: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
startBlock(
|
||||
"cache",
|
||||
part($.filtered_value),
|
||||
part(choice($.identifier, $.string)),
|
||||
@@ -774,7 +855,7 @@ const django = grammar({
|
||||
optional(part(seq("using=", $.filtered_value))),
|
||||
),
|
||||
cache_clause: ($) => seq($.cache_block, optional($.template)),
|
||||
endcache_block: ($) => groupBlock($._group_close, "endcache"),
|
||||
endcache_block: ($) => endBlock("endcache"),
|
||||
cache_group: ($) =>
|
||||
seq(
|
||||
$.cache_clause,
|
||||
@@ -783,10 +864,9 @@ const django = grammar({
|
||||
alias($._missing_block, $.missing_endcache_block),
|
||||
),
|
||||
),
|
||||
comment_block: ($) =>
|
||||
groupBlock($._group_open, "comment", optional(part($.string))),
|
||||
comment_block: ($) => startBlock("comment", optional(part($.string))),
|
||||
comment_clause: ($) => seq($.comment_block, optional($.comment_content)),
|
||||
endcomment_block: ($) => groupBlock($._group_close, "endcomment"),
|
||||
endcomment_block: ($) => endBlock("endcomment"),
|
||||
comment_group: ($) =>
|
||||
seq(
|
||||
$.comment_clause,
|
||||
@@ -823,13 +903,12 @@ const django = grammar({
|
||||
// than as split arguments, so pipes here take whitespace just as they do
|
||||
// between "{{" and "}}"
|
||||
filter_block: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
startBlock(
|
||||
"filter",
|
||||
part(alias($._filter_expression_spaced, $.filter_expression)),
|
||||
),
|
||||
filter_clause: ($) => seq($.filter_block, optional($.template)),
|
||||
endfilter_block: ($) => groupBlock($._group_close, "endfilter"),
|
||||
endfilter_block: ($) => endBlock("endfilter"),
|
||||
filter_group: ($) =>
|
||||
seq(
|
||||
$.filter_clause,
|
||||
@@ -845,8 +924,7 @@ const django = grammar({
|
||||
optional(asVariable($)),
|
||||
),
|
||||
for_block: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
startBlock(
|
||||
"for",
|
||||
part(field("variable", $.identifier)),
|
||||
repeat(
|
||||
@@ -873,9 +951,9 @@ const django = grammar({
|
||||
),
|
||||
),
|
||||
for_clause: ($) => seq($.for_block, optional($.template)),
|
||||
empty_block: ($) => block("empty"),
|
||||
empty_block: ($) => followBlock("empty"),
|
||||
empty_clause: ($) => seq($.empty_block, optional($.template)),
|
||||
endfor_block: ($) => groupBlock($._group_close, "endfor"),
|
||||
endfor_block: ($) => endBlock("endfor"),
|
||||
for_group: ($) =>
|
||||
seq(
|
||||
$.for_clause,
|
||||
@@ -911,14 +989,14 @@ const django = grammar({
|
||||
block("get_media_prefix", optional(asVariable($))),
|
||||
get_static_prefix_block: ($) =>
|
||||
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)),
|
||||
elif_block: ($) => block("elif", part($.predicate)),
|
||||
elif_block: ($) => repeatBlock("elif", part($.predicate)),
|
||||
elif_clause: ($) => seq($.elif_block, optional($.template)),
|
||||
/** 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)),
|
||||
endif_block: ($) => groupBlock($._group_close, "endif"),
|
||||
endif_block: ($) => endBlock("endif"),
|
||||
if_group: ($) =>
|
||||
seq(
|
||||
$.if_clause,
|
||||
@@ -927,9 +1005,9 @@ const django = grammar({
|
||||
choice($.endif_block, alias($._missing_block, $.missing_endif_block)),
|
||||
),
|
||||
ifchanged_block: ($) =>
|
||||
groupBlock($._group_open, "ifchanged", repeat(part($.filtered_value))),
|
||||
startBlock("ifchanged", repeat(part($.filtered_value))),
|
||||
ifchanged_clause: ($) => seq($.ifchanged_block, optional($.template)),
|
||||
endifchanged_block: ($) => groupBlock($._group_close, "endifchanged"),
|
||||
endifchanged_block: ($) => endBlock("endifchanged"),
|
||||
ifchanged_group: ($) =>
|
||||
seq(
|
||||
$.ifchanged_clause,
|
||||
@@ -955,10 +1033,9 @@ const django = grammar({
|
||||
),
|
||||
),
|
||||
/** django.templatetags.i18n. The language tag takes the one argument. */
|
||||
language_block: ($) =>
|
||||
groupBlock($._group_open, "language", part($.filtered_value)),
|
||||
language_block: ($) => startBlock("language", part($.filtered_value)),
|
||||
language_clause: ($) => seq($.language_block, optional($.template)),
|
||||
endlanguage_block: ($) => groupBlock($._group_close, "endlanguage"),
|
||||
endlanguage_block: ($) => endBlock("endlanguage"),
|
||||
language_group: ($) =>
|
||||
seq(
|
||||
$.language_clause,
|
||||
@@ -981,13 +1058,9 @@ const django = grammar({
|
||||
* rejects anything but "on" or "off", including a second word.
|
||||
*/
|
||||
localize_block: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
"localize",
|
||||
optional(part(choice("on", "off"))),
|
||||
),
|
||||
startBlock("localize", optional(part(choice("on", "off")))),
|
||||
localize_clause: ($) => seq($.localize_block, optional($.template)),
|
||||
endlocalize_block: ($) => groupBlock($._group_close, "endlocalize"),
|
||||
endlocalize_block: ($) => endBlock("endlocalize"),
|
||||
localize_group: ($) =>
|
||||
seq(
|
||||
$.localize_clause,
|
||||
@@ -998,13 +1071,9 @@ const django = grammar({
|
||||
),
|
||||
/** django.templatetags.tz, taking "on" or "off" as localize does. */
|
||||
localtime_block: ($) =>
|
||||
groupBlock(
|
||||
$._group_open,
|
||||
"localtime",
|
||||
optional(part(choice("on", "off"))),
|
||||
),
|
||||
startBlock("localtime", optional(part(choice("on", "off")))),
|
||||
localtime_clause: ($) => seq($.localtime_block, optional($.template)),
|
||||
endlocaltime_block: ($) => groupBlock($._group_close, "endlocaltime"),
|
||||
endlocaltime_block: ($) => endBlock("endlocaltime"),
|
||||
localtime_group: ($) =>
|
||||
seq(
|
||||
$.localtime_clause,
|
||||
@@ -1023,9 +1092,9 @@ const django = grammar({
|
||||
now_block: ($) => block("now", part($.string), optional(asVariable($))),
|
||||
partial_block: ($) => block("partial", part($.identifier)),
|
||||
partialdef_block: ($) =>
|
||||
block("partialdef", $.push_partial, optional(part("inline"))),
|
||||
startBlock("partialdef", $.push_partial, optional(part("inline"))),
|
||||
partialdef_clause: ($) => seq($.partialdef_block, optional($.template)),
|
||||
endpartialdef_block: ($) => block("endpartialdef", $.pop_partial),
|
||||
endpartialdef_block: ($) => endBlock("endpartialdef", $.pop_partial),
|
||||
partialdef_group: ($) =>
|
||||
seq(
|
||||
$.partialdef_clause,
|
||||
@@ -1045,9 +1114,9 @@ const django = grammar({
|
||||
optional(asVariable($)),
|
||||
),
|
||||
resetcycle_block: ($) => block("resetcycle", optional(part($.identifier))),
|
||||
spaceless_block: ($) => groupBlock($._group_open, "spaceless"),
|
||||
spaceless_block: ($) => startBlock("spaceless"),
|
||||
spaceless_clause: ($) => seq($.spaceless_block, optional($.template)),
|
||||
endspaceless_block: ($) => groupBlock($._group_close, "endspaceless"),
|
||||
endspaceless_block: ($) => endBlock("endspaceless"),
|
||||
spaceless_group: ($) =>
|
||||
seq(
|
||||
$.spaceless_clause,
|
||||
@@ -1075,10 +1144,9 @@ const django = grammar({
|
||||
),
|
||||
),
|
||||
/** django.templatetags.tz. timezone_tag takes the one argument. */
|
||||
timezone_block: ($) =>
|
||||
groupBlock($._group_open, "timezone", part($.filtered_value)),
|
||||
timezone_block: ($) => startBlock("timezone", part($.filtered_value)),
|
||||
timezone_clause: ($) => seq($.timezone_block, optional($.template)),
|
||||
endtimezone_block: ($) => groupBlock($._group_close, "endtimezone"),
|
||||
endtimezone_block: ($) => endBlock("endtimezone"),
|
||||
timezone_group: ($) =>
|
||||
seq(
|
||||
$.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: ($) =>
|
||||
block(
|
||||
"url",
|
||||
@@ -1121,9 +1226,9 @@ const django = grammar({
|
||||
),
|
||||
optional(asVariable($)),
|
||||
),
|
||||
verbatim_block: ($) => block("verbatim", $.push_verbatim),
|
||||
verbatim_block: ($) => startBlock("verbatim", $.push_verbatim),
|
||||
verbatim_clause: ($) => seq($.verbatim_block, optional($.verbatim_content)),
|
||||
endverbatim_block: ($) => block("endverbatim", $.pop_verbatim),
|
||||
endverbatim_block: ($) => endBlock("endverbatim", $.pop_verbatim),
|
||||
verbatim_group: ($) =>
|
||||
seq(
|
||||
$.verbatim_clause,
|
||||
@@ -1140,14 +1245,16 @@ const django = grammar({
|
||||
part($.filtered_value),
|
||||
optional(asVariable($)),
|
||||
),
|
||||
with_block: ($) =>
|
||||
groupBlock($._group_open, "with", $._tag_open, $._tag_kwargs),
|
||||
with_block: ($) => startBlock("with", $._tag_open, $._tag_kwargs),
|
||||
with_clause: ($) => seq($.with_block, optional($.template)),
|
||||
endwith_block: ($) => groupBlock($._group_close, "endwith"),
|
||||
endwith_block: ($) => endBlock("endwith"),
|
||||
with_group: ($) =>
|
||||
seq(
|
||||
$.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_endwith_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",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "unexpected_block",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "url_block",
|
||||
"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",
|
||||
"named": true,
|
||||
|
||||
+32298
-29126
File diff suppressed because it is too large.
Load diff
+472
-532
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
|
||||
:error
|
||||
@@ -346,7 +345,6 @@ Blocktranslate Closed By The Other Spelling
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Count And No Plural
|
||||
==================
|
||||
@@ -370,8 +368,6 @@ Blocktrans With Count And No Plural
|
||||
(endblocktrans_block))
|
||||
(content))
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Plural And No Count
|
||||
:error
|
||||
@@ -382,7 +378,6 @@ Blocktrans With Plural And No Count
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Counting Two Bindings
|
||||
:error
|
||||
@@ -393,7 +388,6 @@ Blocktrans Counting Two Bindings
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Nothing To Bind
|
||||
:error
|
||||
@@ -404,7 +398,6 @@ Blocktrans With Nothing To Bind
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With An Unknown Option
|
||||
:error
|
||||
@@ -415,7 +408,6 @@ Blocktrans With An Unknown Option
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Asvar And No Name
|
||||
:error
|
||||
@@ -426,7 +418,6 @@ Blocktrans With Asvar And No Name
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding A Tag
|
||||
:error
|
||||
@@ -437,7 +428,6 @@ Blocktrans Holding A Tag
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding A Comment
|
||||
:error
|
||||
@@ -448,7 +438,6 @@ Blocktrans Holding A Comment
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans Holding Another Blocktrans
|
||||
:error
|
||||
@@ -459,17 +448,33 @@ Blocktrans Holding Another Blocktrans
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Two Plurals
|
||||
:error
|
||||
==================
|
||||
|
||||
{% 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
|
||||
@@ -481,7 +486,6 @@ Blocktrans With An Argument On Plural
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Trimmed Twice
|
||||
:error
|
||||
@@ -492,7 +496,6 @@ Blocktrans With Trimmed Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With With Twice
|
||||
:error
|
||||
@@ -503,7 +506,6 @@ Blocktrans With With Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Context Twice
|
||||
:error
|
||||
@@ -514,7 +516,6 @@ Blocktrans With Context Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Asvar Twice
|
||||
:error
|
||||
@@ -525,7 +526,6 @@ Blocktrans With Asvar Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktrans With Count Twice
|
||||
:error
|
||||
@@ -536,7 +536,6 @@ Blocktrans With Count Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
Blocktranslate With Trimmed Twice
|
||||
:error
|
||||
@@ -547,7 +546,6 @@ Blocktranslate With Trimmed Twice
|
||||
---
|
||||
|
||||
|
||||
|
||||
==================
|
||||
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
|
||||
:error
|
||||
==================
|
||||
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(unexpected_block)
|
||||
(content))
|
||||
|
||||
==================
|
||||
Custom Tag Does Not Cover A Part Of A Tag Group
|
||||
:error
|
||||
==================
|
||||
|
||||
{% else %}
|
||||
|
||||
---
|
||||
|
||||
(template
|
||||
(content)
|
||||
(unexpected_block)
|
||||
(content))
|
||||
|
||||
==================
|
||||
A Name Reserved For A Tag Is Still A Variable
|
||||
|
||||
@@ -173,7 +173,6 @@ c
|
||||
|
||||
==================
|
||||
Ifchanged With Two Elses
|
||||
:error
|
||||
==================
|
||||
|
||||
{% 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
|
||||
:error
|
||||
==================
|
||||
|
||||
{% ifchanged %}
|
||||
@@ -199,3 +212,15 @@ b
|
||||
{% 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))
|
||||
|
||||
==================
|
||||
Missing Endfor Before An Outer Else
|
||||
Unseen Outer Else Is Not A Missing Tag
|
||||
==================
|
||||
|
||||
{% if a %}{% for x in y %}{% else %}b{% endif %}
|
||||
@@ -189,12 +189,11 @@ Missing Endfor Before An Outer Else
|
||||
(filtered_value
|
||||
(value
|
||||
(variable_attribute
|
||||
(identifier))))))
|
||||
(missing_endfor_block))))
|
||||
(else_clause
|
||||
(else_block)
|
||||
(identifier)))))
|
||||
(template
|
||||
(unexpected_block)
|
||||
(content)))
|
||||
(missing_endfor_block))))
|
||||
(endif_block))
|
||||
(content))
|
||||
|
||||
@@ -462,35 +461,524 @@ Missing Endif After A Custom End Tag
|
||||
(content)))
|
||||
(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
|
||||
:error
|
||||
==================
|
||||
|
||||
{% 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
|
||||
:error
|
||||
==================
|
||||
|
||||
{% 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
|
||||
:error
|
||||
==================
|
||||
|
||||
{% 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
|
||||
|
||||
Reference in new issue
Block a user