77 lines
3.5 KiB
Scheme
77 lines
3.5 KiB
Scheme
; Which parts of a template render conditionally, for a tool checking that a
|
|
; variable is defined on every path that reaches a use of it.
|
|
;
|
|
; Django binds an "as" target straight into the context, and a tag that pushes
|
|
; no context leaves that binding visible after its group closes. So
|
|
;
|
|
; {% if a %}{% now "Y" as n %}{% endif %}{{ n }}
|
|
;
|
|
; renders the year when a is truthy and string_if_invalid ("" by default) when
|
|
; it is not — a silent empty string, not an error. queries/locals.scm cannot
|
|
; see that: a locals query matches a reference to the definition whose scope
|
|
; contains it and has no notion of a path that may not be taken.
|
|
;
|
|
; The halves a tool joins, by grouping matches on the @conditional.group node:
|
|
;
|
|
; @conditional.group the whole construct
|
|
; @conditional.branch one alternative; at most one of a group's branches
|
|
; renders on any given pass
|
|
; @conditional.default the branch that renders when no other one does
|
|
;
|
|
; A group is exhaustive iff one of its matches carries a @conditional.default.
|
|
; If it is not, some pass through it binds nothing, so every definition inside
|
|
; is conditional. If it is, a definition escapes only when every branch makes
|
|
; it. Each capture names both halves of the pair, so no ancestor walk is needed
|
|
; and no block has to be attributed to a group by name.
|
|
;
|
|
; Branches nest, so the same test applies again to a group inside a branch.
|
|
;
|
|
; A branch that is also a @local.scope in queries/locals.scm confines its
|
|
; bindings whether or not it renders; the two queries compose and the scope
|
|
; decides first.
|
|
|
|
; if/elif are alternatives tested in order; else runs when none matched. None
|
|
; of the three pushes a context, so this is the case the check exists for.
|
|
(if_group
|
|
[
|
|
(if_block)
|
|
(elif_block)
|
|
] @conditional.branch) @conditional.group
|
|
|
|
(if_group
|
|
(else_block) @conditional.default) @conditional.group
|
|
|
|
; ifchanged renders its body only when the watched value differs from the
|
|
; previous pass, and its else otherwise. It pushes no context either.
|
|
(ifchanged_group
|
|
(ifchanged_block) @conditional.branch) @conditional.group
|
|
|
|
(ifchanged_group
|
|
(else_block) @conditional.default) @conditional.group
|
|
|
|
; The loop body runs once per item and empty runs when there are none, so the
|
|
; two partition every render. Both are inside ForNode's context.push() — the
|
|
; empty branch included, which is easy to miss — so locals.scm scopes both and
|
|
; nothing escapes either way. Named here because the construct does branch, and
|
|
; because a tool reading only this file should not have to infer that it does.
|
|
(for_group
|
|
(for_block) @conditional.branch) @conditional.group
|
|
|
|
(for_group
|
|
(empty_block) @conditional.default) @conditional.group
|
|
|
|
; A cache body is skipped entirely on a cache hit, and CacheNode pushes no
|
|
; context, so a binding made inside it reaches the rest of the template on the
|
|
; first render and is gone on the next. One branch and no default: never
|
|
; exhaustive, which is the right answer.
|
|
(cache_group
|
|
(cache_block) @conditional.branch) @conditional.group
|
|
|
|
; Not here, deliberately: block and partialdef bodies may also fail to render —
|
|
; a child template overrides the block, and a partial renders only where
|
|
; {% partial %} names it — but both blocks are @local.scope in locals.scm, so
|
|
; nothing they bind escapes to be checked. Bodies that always render once
|
|
; (with, filter, autoescape, spaceless, language, localize, localtime,
|
|
; timezone) are not conditional at all, and comment, verbatim and
|
|
; blocktranslate bodies hold no tags to bind anything.
|