Compare commits

...
39 Commits
Author SHA1 Message Date
danhanson 2181ff3403 imported test cases
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and stage (push) Canceled after 0s
2026-09-18 04:18:23 -05:00
danhanson b154625f1b remove macos-13
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and stage (push) Canceled after 0s
2026-09-17 03:52:56 -05:00
danhanson 1ac669eee9 increase version
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-13) (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and stage (push) Canceled after 0s
2026-09-17 01:50:27 -05:00
danhanson a3de44f4ca fix windows binding
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-13) (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and stage (push) Canceled after 0s
2026-09-17 01:37:30 -05:00
danhanson 96e7b2a9c5 use stage in workflow
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-13) (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and stage (push) Canceled after 0s
2026-09-17 01:25:49 -05:00
danhanson 99123289f7 add release workflow
Release / Version (push) Canceled after 0s
Release / CI (push) Canceled after 0s
Release / Prebuild (macos-13) (push) Canceled after 0s
Release / Prebuild (macos-latest) (push) Canceled after 0s
Release / Prebuild (ubuntu-24.04-arm) (push) Canceled after 0s
Release / Prebuild (ubuntu-latest) (push) Canceled after 0s
Release / Prebuild (windows-latest) (push) Canceled after 0s
Release / Tag and publish (push) Canceled after 0s
2026-09-16 16:05:32 -05:00
danhanson 13c774dedb fix parser version to match package.json 2026-09-16 16:05:14 -05:00
danhanson 969ba61e88 remove private tag 2026-09-16 15:33:22 -05:00
danhanson 5183f6aa52 proofread readme 2026-09-16 14:59:07 -05:00
danhanson 53e1ee2036 add bindings and fix documentation 2026-09-16 14:26:03 -05:00
danhanson 0e8ef8e7a5 fix scanner warning 2026-09-16 14:25:46 -05:00
danhanson 4a79d32f6a add _missing_variable_close 2026-09-14 22:08:39 -05:00
danhanson 7979890227 contain template variable errors 2026-09-14 21:50:16 -05:00
danhanson ccb80972b6 handle content edge cases 2026-09-14 21:40:26 -05:00
danhanson fa0d880e68 fixed content regex 2026-09-14 21:29:27 -05:00
danhanson 831acd3516 add unexpected argument 2026-09-14 18:32:37 -05:00
danhanson f17fdd2d74 add unexpected argument error 2026-09-14 17:51:45 -05:00
danhanson 5f66027dcf use $ instead of sym 2026-09-14 16:13:57 -05:00
danhanson 70543ffe3b replace $ with sym in helpers for consistency 2026-09-14 16:08:54 -05:00
danhanson 9f2fdaf746 massive rename 2026-09-14 15:59:53 -05:00
danhanson cb8c9f9c0b added more utility rules and updated example 2026-09-14 15:33:17 -05:00
danhanson 036928d628 add more scanner logs describing reject reasons 2026-09-13 17:09:45 -05:00
danhanson b5fbd86fd5 fix scanner error logging 2026-09-13 16:54:22 -05:00
danhanson 1648dc82f3 rename missing_tag to missing_block 2026-09-13 05:46:41 -05:00
danhanson 2d7d0db471 add missing tag marker and error query 2026-09-12 19:21:12 -05:00
danhanson f2d3a727f6 add rules for each tag block, and renamed some rules 2026-09-12 18:14:14 -05:00
danhanson db3caa1672 add clause rule to verbatim, comment, and blocktrans 2026-09-12 17:41:25 -05:00
danhanson 22362ed393 add license, review README.md, fix found bugs 2026-09-12 16:36:46 -05:00
danhanson 9a254328f6 add queries to readme 2026-09-12 15:30:15 -05:00
danhanson 1f556f6687 add exports and library info to README.md 2026-09-12 15:29:56 -05:00
danhanson b239bf6509 use dynamic precedence to fix 'is' and allow not not 2026-09-11 04:21:08 -05:00
danhanson 9701fab04a add divergences section to README.md and fixed reversed and noop arguments 2026-09-10 15:56:16 -05:00
danhanson 913ca69e6c add True, False, None 2026-09-10 15:00:39 -05:00
danhanson c0a2e0099c add conditionals query 2026-09-10 14:38:59 -05:00
danhanson 606bf7457f add exports query 2026-09-08 19:13:56 -05:00
danhanson aceae012fa improved scope rules 2026-09-08 19:00:19 -05:00
danhanson ca01dd5f95 add clauses to support autocomplete better 2026-09-08 14:27:57 -05:00
danhanson 06aa8706c8 fix for and add reversed argument 2026-09-08 02:42:50 -05:00
danhanson 356cc3a885 enforce key uniqueness on with expression, fix scanner.c 2026-09-07 20:33:43 -05:00
85 changed files with 68516 additions and 109957 deletions

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
root = true
[*]
charset = utf-8
[*.{json,toml,yml,gyp,xml}]
indent_style = space
indent_size = 2
[*.{js,ts}]
indent_style = space
indent_size = 2
[*.scm]
indent_style = space
indent_size = 2
[*.{c,cc,h}]
indent_style = space
indent_size = 4
[*.rs]
indent_style = space
indent_size = 4
[*.{py,pyi}]
indent_style = space
indent_size = 4
[*.swift]
indent_style = space
indent_size = 4
[*.java]
indent_style = space
indent_size = 4
[*.go]
indent_style = tab
indent_size = 8
[Makefile]
indent_style = tab
indent_size = 8
[parser.c]
indent_size = 2
[{alloc,array,parser}.h]
indent_size = 2
+46
View File
@@ -0,0 +1,46 @@
* text=auto eol=lf
# Generated source files
src/*.json linguist-generated
src/parser.c linguist-generated
src/tree_sitter/* linguist-generated
# C bindings
bindings/c/** linguist-generated
CMakeLists.txt linguist-generated
Makefile linguist-generated
# Rust bindings
bindings/rust/* linguist-generated
Cargo.toml linguist-generated
Cargo.lock linguist-generated
# Node.js bindings
bindings/node/* linguist-generated
binding.gyp linguist-generated
package.json linguist-generated
package-lock.json linguist-generated
# Python bindings
bindings/python/** linguist-generated
setup.py linguist-generated
pyproject.toml linguist-generated
# Go bindings
bindings/go/* linguist-generated
go.mod linguist-generated
go.sum linguist-generated
# Swift bindings
bindings/swift/** linguist-generated
Package.swift linguist-generated
Package.resolved linguist-generated
# Zig bindings
bindings/zig/* linguist-generated
build.zig linguist-generated
build.zig.zon linguist-generated
# Java bindings
pom.xml linguist-generated
bindings/java/** linguist-generated
+91
View File
@@ -0,0 +1,91 @@
name: CI
# Pull requests run this directly; pushes to main reach it through release.yml,
# which calls this workflow before it tags and publishes.
on:
pull_request:
workflow_call:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
parser:
name: Corpus and generated files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- name: Corpus tests
run: npm run parser-test
- name: src/ matches grammar.js
run: |
npm run parser-generate
if ! git diff --exit-code --stat src/; then
echo "::error::src/ is out of date: run 'npm run parser-generate' and commit the result"
exit 1
fi
node:
name: Node binding (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
# windows-latest is Windows Server 2025 with Visual Studio 2026, which the
# node-gyp bundled with the runner's npm (11.5) reports as "unknown
# version undefined" and then gives up on. node-gyp learned VS 18 in
# 12.1.0 and npm 12 bundles 13.
- if: runner.os == 'Windows'
run: npm install -g npm@^12
# npm ci builds the addon through the install script (node-gyp-build)
- run: npm ci
- run: npm run binding-test
rust:
name: Rust binding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: cargo test
python:
name: Python binding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install --upgrade pip
- run: python -m pip install . "tree-sitter~=0.24"
- run: python -m unittest discover -s bindings/python/tests -v
go:
name: Go binding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version: stable
- run: go test ./bindings/go/
swift:
name: Swift binding
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
- run: swift build
+245
View File
@@ -0,0 +1,245 @@
name: Release
# Releasing is a version bump: push a commit to main that changes the version in
# package.json, and this tags v<version>, creates the GitHub release and stages
# the npm publish. Every other push to main only runs the tests.
#
# Staging is not publishing: "npm stage publish" hands npm the tarball and nobody
# can install it until a maintainer runs "npm stage approve <stage-id>" and
# answers a 2FA prompt. That is the point — npm is deprecating the tokens that
# let a workflow publish on its own, and staging defers the proof of presence
# instead of removing it. The job summary carries the command to run.
#
# Running it by hand instead stages whatever version package.json holds, as long
# as npm has neither published nor staged it yet, and skips the tag and the
# release if a previous attempt already made them. That is how a half-finished
# release is retried.
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
version:
name: Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.check.outputs.version }}
changed: ${{ steps.check.outputs.changed }}
tag_exists: ${{ steps.check.outputs.tag_exists }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 2
fetch-tags: true
- id: check
shell: bash
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
run: |
set -euo pipefail
name="$(node -p "require('./package.json').name")"
version="$(node -p "require('./package.json').version")"
echo "version=$version" >> "$GITHUB_OUTPUT"
if git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then
tag_exists=true
else
tag_exists=false
fi
echo "tag_exists=$tag_exists" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
# by hand: stage what package.json says, unless npm has it already.
# A version staged but not yet approved is invisible to npm view; the
# stage job looks for that one itself.
if npm view "$name@$version" version >/dev/null 2>&1; then
echo "::warning::$name@$version is already on npm; nothing to stage"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "staging $name@$version by hand (tag exists: $tag_exists)"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
exit 0
fi
if git cat-file -e HEAD~1:package.json 2>/dev/null; then
previous="$(git show HEAD~1:package.json | node -p "JSON.parse(require('node:fs').readFileSync(0, 'utf8')).version")"
else
previous=""
fi
if [ "$version" = "$previous" ]; then
echo "version is unchanged at $version; not releasing"
echo "changed=false" >> "$GITHUB_OUTPUT"
elif [ "$tag_exists" = "true" ]; then
echo "::warning::tag v$version already exists; not releasing"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "releasing $previous -> $version"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
ci:
name: CI
needs: version
uses: ./.github/workflows/ci.yml
prebuild:
name: Prebuild (${{ matrix.os }})
needs: [version, ci]
if: needs.version.outputs.changed == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
# one runner per platform and architecture npm should ship a binary for;
# the arm Linux runner is free for public repositories only
os:
[
ubuntu-latest,
ubuntu-24.04-arm,
macos-latest,
windows-latest,
]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
# the Windows runner's own npm bundles a node-gyp too old to see the
# Visual Studio 2026 on that image; see ci.yml
- if: runner.os == 'Windows'
run: npm install -g npm@^12
- run: npm ci
- run: npm run prebuildify
- uses: actions/upload-artifact@v4
with:
name: prebuilds-${{ matrix.os }}
path: prebuilds/
if-no-files-found: error
publish:
name: Tag and stage
needs: [version, prebuild]
if: needs.version.outputs.changed == 'true'
runs-on: ubuntu-latest
permissions:
contents: write # the tag and the release
id-token: write # the OIDC exchange, and provenance for npm
env:
VERSION: ${{ needs.version.outputs.version }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
- name: Collect the prebuilds
uses: actions/download-artifact@v4
with:
pattern: prebuilds-*
path: prebuilds/
merge-multiple: true
- run: ls -R prebuilds/
- run: npm ci
# tree-sitter build --wasm needs Emscripten; the CLI runs it through
# Docker, which the ubuntu runners have
- name: Build the Wasm parser
run: npm run parser-build
# a retry of a half-finished release finds its tag and release already made
- name: Tag
if: needs.version.outputs.tag_exists != 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "v$VERSION" -m "v$VERSION"
git push origin "v$VERSION"
- name: Release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if gh release view "v$VERSION" >/dev/null 2>&1; then
echo "release v$VERSION exists; replacing its Wasm build"
gh release upload "v$VERSION" tree-sitter-django.wasm --clobber
else
gh release create "v$VERSION" --title "v$VERSION" --generate-notes tree-sitter-django.wasm
fi
# "npm stage" arrived in npm 12, which is newer than the npm any runner
# image ships with
- name: Install npm 12
run: npm install -g npm@^12
# staged versions share npm's version index with published ones, so a
# second stage of the same version is an error rather than a no-op. A run
# that staged and then failed later should find its stage and go on.
- name: Look for an existing stage
id: staged
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -uo pipefail
name="$(node -p "require('./package.json').name")"
# a short-lived OIDC token cannot run "npm stage list" at all, so a
# failure here means "ask npm by staging", not "stop"
if ! items="$(npm stage list "$name" --json 2>/dev/null)"; then
echo "could not list staged versions; assuming there are none"
echo "staged=false" >> "$GITHUB_OUTPUT"
exit 0
fi
id="$(node -e '
const items = JSON.parse(require("node:fs").readFileSync(0, "utf8"));
const match = items.find((item) => item.version === process.env.VERSION);
if (match) console.log(match.id);
' <<< "$items")"
if [ -n "$id" ]; then
echo "::warning::$name@$VERSION is already staged as $id; not staging it again"
echo "staged=true" >> "$GITHUB_OUTPUT"
echo "stage_id=$id" >> "$GITHUB_OUTPUT"
else
echo "staged=false" >> "$GITHUB_OUTPUT"
fi
# NPM_TOKEN is the fallback: where the package has a trusted publisher for
# this workflow, npm exchanges the OIDC token above for one of its own and
# the secret is not read at all.
- name: Stage the publish
id: stage
if: steps.staged.outputs.staged != 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
npm stage publish --provenance --access public 2>&1 | tee stage.log
id="$(sed -n 's/.*staged with id \([0-9a-fA-F-]\{36\}\).*/\1/p' stage.log | tail -n1)"
echo "stage_id=$id" >> "$GITHUB_OUTPUT"
- name: Say how to approve it
env:
STAGE_ID: ${{ steps.stage.outputs.stage_id || steps.staged.outputs.stage_id }}
run: |
set -euo pipefail
name="$(node -p "require('./package.json').name")"
{
echo "### $name@$VERSION is staged, not published"
echo
echo "npm has the tarball and nobody can install it until a maintainer approves it, which asks for 2FA:"
echo
echo '```sh'
if [ -n "$STAGE_ID" ]; then
echo "npm stage view $STAGE_ID # what was staged"
echo "npm stage download $STAGE_ID # the tarball itself"
echo "npm stage approve $STAGE_ID # publish it"
echo "npm stage reject $STAGE_ID # throw it away"
else
echo "npm stage list $name"
echo "npm stage approve <stage-id>"
fi
echo '```'
echo
echo "Approving needs npm 12: \`npm install -g npm@^12\`."
} >> "$GITHUB_STEP_SUMMARY"
+28
View File
@@ -1,2 +1,30 @@
node_modules node_modules
tree-sitter-django.wasm tree-sitter-django.wasm
# Node binding
build/
prebuilds/
*.node
# Rust binding
target/
Cargo.lock
# Python binding
dist/
*.egg-info/
__pycache__/
*.so
*.pyd
# C binding
*.o
*.a
*.dylib
libtree-sitter-django.*
# Swift binding
.build/
Package.resolved
*.pc
*.tgz
+388 -31
View File
@@ -11,6 +11,10 @@ npm run parser-generate # regenerate src/parser.c, grammar.json, node-types.js
npm run parser-test # run the corpus tests npm run parser-test # run the corpus tests
npm run parser-build # build tree-sitter-django.wasm npm run parser-build # build tree-sitter-django.wasm
npm run playground # explore the grammar interactively npm run playground # explore the grammar interactively
npm install # also builds the Node binding, through node-gyp-build
npm run binding-test # the Node binding's own test
cargo test # the Rust binding
make # the C library, and make clean to remove it
npx tree-sitter test -i "Cycle With As" # one test, by name regex npx tree-sitter test -i "Cycle With As" # one test, by name regex
npx tree-sitter test --file-name cycle.txt # one corpus file npx tree-sitter test --file-name cycle.txt # one corpus file
@@ -30,6 +34,95 @@ back, producing churn in both directions.
`npm run lint` does not work: `eslint.config.mjs` was copied from a Next.js project and enables `npm run lint` does not work: `eslint.config.mjs` was copied from a Next.js project and enables
`@typescript-eslint` and `react-hooks` rules whose plugins aren't installed. Formatting is prettier's job. `@typescript-eslint` and `react-hooks` rules whose plugins aren't installed. Formatting is prettier's job.
### Bindings
`bindings/` and the manifests beside it — `binding.gyp`, `Cargo.toml`, `pyproject.toml`, `setup.py`,
`go.mod`, `Package.swift`, `Makefile`, `CMakeLists.txt` — are generated by `tree-sitter init` from the
`bindings` and `metadata` sections of `tree-sitter.json`, and `.gitattributes` marks them
`linguist-generated`. Do not edit them by hand: change `tree-sitter.json` and regenerate.
`tree-sitter init --update` only rewrites what it considers outdated, and a changed `version` or
`description` does not count, so the manifests keep the old values until they are deleted and `init` is run
again. It leaves `README.md`, `package.json` and `.gitignore` alone, which is why the Node binding's
`package.json` entries — `main`/`types`, the `.` export, `files`, `node-addon-api`, `node-gyp-build`, the
`install` script and the optional `tree-sitter` peer — are maintained here rather than generated.
Every binding was built and run on macOS: `npm install` plus `npm run binding-test`, `cargo test`,
`go test ./bindings/go/`, `pip install .` with a parse, `swift build`, and `make`. The Go binding needs a
`go.sum`, which `go mod tidy` writes and which is committed with the rest.
`src/scanner.c` compiles without warnings, and should stay that way: cc and clang warn about `&&` within
`||` (`-Wlogical-op-parentheses`), so a comparison chain written with both needs its `&&` groups
parenthesized.
### CI and releasing
`.github/workflows/ci.yml` runs on pull requests, and the release workflow calls it: the corpus tests, a
check that `npm run parser-generate` leaves `src/` unchanged, the Node binding on Linux, macOS and Windows,
and the Rust, Python, Go and Swift bindings.
`.github/workflows/release.yml` runs on every push to `main` but only releases when `package.json`'s version
differs from the previous commit's and no `v<version>` tag exists yet. Then it runs CI, `prebuildify` on
five runners (Linux x64 and arm64, macOS x64 and arm64, Windows x64), and a last job that tags `v<version>`,
creates the GitHub release with `tree-sitter-django.wasm` attached, and runs
`npm stage publish --provenance`. **A release is a version-bump commit on `main`**; no other push starts one.
**The workflow stages the publish; it does not publish.** `npm stage publish` hands npm the tarball and
holds it there, unavailable to install, until a maintainer runs `npm stage approve <stage-id>` and answers a
2FA prompt — proof of presence deferred rather than dropped, which is npm's answer to deprecating the tokens
that let a workflow publish unattended. The job summary prints the `view`/`download`/`approve`/`reject`
commands for the stage it made. Approving needs npm 12 (`npm install -g npm@^12`), which is also why the job
installs it: no runner image ships an npm that has the command.
Running the workflow by hand (`workflow_dispatch`) is the retry: it stages whatever version `package.json`
holds as long as `npm view` does not find it on the registry already, and it skips the tag and the release
when a previous attempt left them behind, replacing only the Wasm asset. So a run that tagged and then
failed to stage is finished by running it again, and a run against an already published version stops at the
version job. A version that is _staged_ and not yet approved is invisible to `npm view` while still
occupying npm's version index, so a second stage of it is an error rather than a no-op; the stage job asks
`npm stage list` first and skips straight to the approval summary when it finds one. That listing is
best-effort — a short-lived OIDC token may not run `npm stage` subcommands at all — and a failure there just
means the stage is attempted.
Releasing needs three things to line up:
- credentials to stage with: either a trusted publisher configured for this workflow
(`npm trust github --allow-stage-publish`), which needs no secret at all because npm exchanges the
workflow's OIDC token for one of its own, or the `NPM_TOKEN` secret as the fallback. Staging works with any
token type, so that token no longer needs — and should no longer have — the 2FA bypass an automation token
carries. Both the exchange and provenance need `id-token: write`, which the publish job already declares.
- **the version in `package.json` and in `tree-sitter.json`'s `metadata`**. The release trigger reads the
first; the second is compiled into `src/parser.c` as the language's version, so bumping one alone leaves
the parser disagreeing with the manifest.
- `npm run parser-generate` after that bump, committed with it, or CI's up-to-date check fails on a two-line
diff in `src/parser.c`.
The arm64 Linux runner (`ubuntu-24.04-arm`) is free for public repositories only. `tree-sitter build --wasm`
runs Emscripten through Docker, which the ubuntu runners provide.
**Windows needs a newer npm than its runner ships.** `windows-latest` is Windows Server 2025 **with Visual
Studio 2026**, and the node-gyp bundled with that image's npm (11.5.0) reads VS 18 as
`unknown version "undefined"` and then reports "Could not find any Visual Studio installation to use", so
`npm ci` fails in the install script. node-gyp learned VS 2026 in 12.1.0 and npm 12 bundles 13, which is why
both jobs that compile the addon there — CI's Node binding and the release's `prebuild` — install `npm@^12`
first. Pinning `windows-2022` instead would work, at the price of testing against a toolchain the image no
longer has. Installs of the published package never meet this: they unpack a prebuild and never run node-gyp.
**The npm package must carry its prebuilds**, or `npm install` runs `node-gyp-build`, finds nothing and
compiles, which fails on an image without `python3`, `make` and a compiler — `node:20-slim`, for instance.
0.1.0 was published by hand before the workflow existed and shipped none, which is what that image hit. The
`prebuild` jobs are what produce them, and `files` carries `prebuilds/**`.
`npm run prebuildify` passes `--napi --strip --tag-libc`. `--napi` makes one binary per platform that every
Node ABI can load, so the file needs no ABI tag and is named after the package. `--tag-libc` adds `glibc` or
`musl`: `node-gyp-build` treats a file with no libc tag as matching anything, so an Alpine install would
otherwise load a glibc binary and fail at run time; with the tag it compiles instead. Only glibc is built,
so musl installs need a toolchain.
`package.json` exports the Wasm build as `tree-sitter-django/tree-sitter-django.wasm`. An `exports` map
hides everything it does not name, so each published path — the queries, `grammar.js`, `src/scanner.c` —
needs its own entry.
### Stale parser library ### Stale parser library
tree-sitter caches the compiled parser in `~/.cache/tree-sitter/lib/django.*`, and it has served a stale tree-sitter caches the compiled parser in `~/.cache/tree-sitter/lib/django.*`, and it has served a stale
@@ -46,20 +139,31 @@ 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 - `tag(name, ...args)` — `{%`, optional separator, tag name, args, optional separator, `%}`; tags may
hug their delimiters (`{%cycle 1%}`). hug their delimiters (`{%cycle 1%}`).
- `asVariable($)` — the `as name` clause. Every tag that takes one names the target `field("variable", …)`; - `asVariable($)` — the `as name` clause. Every tag that takes one names the target `field("variable", …)`;
do not invent a second name for it. do not invent a second name for it.
- `simpleTag($, tag, args, kwargs)` — a tag registered with Django's `simple_tag`, which always accepts - `simpleTag($, name, args, kwargs)` — a tag registered with Django's `simple_tag`, which always accepts
positional filter expressions, then `name=value` keywords, then `as name`. `args`: `true` (`*args`), a positional filter expressions, then `name=value` keywords, then `as name`. `args`: `true` (`*args`), a
count (at most N), or `false`. `kwargs`: `true` (`**kwargs`), `false`, or `{name: rule}` for a known count (at most N), or `false`. `kwargs`: `true` (`**kwargs`), `false`, or `{name: rule}` for a known
signature. Put the Python signature in a comment above the call site. signature. Put the Python signature in a comment above the call site.
- `arrangements(items)` — every ordered subset of `items`, used by `simpleTag` so that known keyword - `arrangements(items)` — every ordered subset of `items`, so that a tag's options may be written in any
arguments may appear in any order but never twice. Grows as `Σ C(n,k)·k!` (65 for four names), which is order but never twice. Used by `translate_tag` and `include_tag`; keyword arguments are guarded by the scanner
instead. Grows as `Σ C(n,k)·k!` (65 for four names), which is
why it is only used for signatures that name their arguments. why it is only used for signatures that name their arguments.
- `wordAsValueRules(word)` / `wordAsValue($, word, continued)` — a tag's own keyword written where a value
goes. Django tells its flag from a variable by comparing the whole bit, so `reversed` is the flag of
`{% for %}` while `reversed.x` and `reversed|last` are lookups; `continued` selects that second group.
Needed because keyword extraction hands the parser the keyword wherever both it and `identifier` are
valid, so the reading in which the word names a variable has to be built back out of it. The rules are
hidden and aliased at the use site, one per level (`variable_attribute`, `value`, `filtered_value`),
because an alias renames the node its own rule makes and **cannot nest one inside another** — writing
it as nested inline `alias(alias(...))` calls silently yields a flat, partly zero-width tree that still
generates and still parses. Check the tree, not just the accept.
Consequences worth knowing before editing a rule: Consequences worth knowing before editing a rule:
@@ -69,7 +173,90 @@ Consequences worth knowing before editing a rule:
fail at generate time; it shows up as `MISSING` nodes in a passing-looking parse. fail at generate time; it shows up as `MISSING` nodes in a passing-looking parse.
- **`conflicts` entries are the normal fix, not precedence.** With explicit separators the lookahead at a - **`conflicts` entries are the normal fix, not precedence.** With explicit separators the lookahead at a
part boundary is `SEP` instead of the meaningful token, so LR(1) loses its discriminator. That is why part boundary is `SEP` instead of the meaningful token, so LR(1) loses its discriminator. That is why
`predicate` and `[$.library, $.load]` are listed there. `predicate`, `binaryOperator` and `[$.library, $.load]` are listed there. Where two readings both stay
valid to the end — `is not` as one operator or as `is` and a prefix `not` — the conflict needs a
`prec.dynamic` alongside it to settle which wins; a static `prec` resolves the shift/reduce while the
table is built and discards the other parse outright, which is how a bare `{% if x is y %}` came to be
an error.
### A tag group is a sequence of blocks
Every group is split so that each tag that opens a body owns a node of its own:
```js
if_tag: ($) => startTag("if", part($.predicate)),
if_block: ($) => seq($.if_tag, optional($.template)),
endif_tag: ($) => endTag("endif"),
if_group: ($) =>
seq(
$.if_block,
repeat($.elif_block),
optional($.else_block),
choice($.endif_tag, alias($._missing_tag, $.missing_endif_tag)),
),
```
**A block is complete without the closing tag, and that is the point.** While a template is being edited
the closer usually is not there yet, and a group rule cannot be reduced without it — so `{% if a %}{% x %}`
used to produce a top-level `ERROR` with the body's `template` parented to nothing that says which tag
opened it. A block reduces anyway, so it survives error recovery and tooling can walk up from the cursor
to find the enclosing tag. That is what makes completion of `{% elif %}`, `{% else %}`, `{% empty %}` and
the end tags possible.
**Every `{% %}` is a node of its own, named `<name>_tag`** after the tag's name as Django spells it: `if_tag`,
`elif_tag`, `endif_tag`, `block_tag`, `templatetag_tag`, `custom_tag`. A block is its
opening tag and a body; a group is its blocks and its end tag. A tag ends at `%}` and nothing can
extend it, so it reduces on whatever token follows — the end of input included — and a half-written
template still holds the last `{% for … %}` or `{% endif %}` typed as a whole node, not as loose tokens.
Stripping `_tag` gives the tag name; a new tag, or a tag group's new part, follows the same scheme.
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_tag`; see the scanner section). So an unterminated group still reduces to a group node,
and a group's tag written where no open group can hold it is an `unexpected_tag` (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 — `simpleTagOpen`/`simpleTagClose`,
`startTagOpen`/`startTagClose`,
`followTagOpen`/`followTagClose`, `repeatTagOpen`/`repeatTagClose`, `endTagOpen`/`endTagClose` —
through the helpers `tag` (a tag outside any group, from `simpleTagOpen`/`simpleTagClose`), `startTag`, `followTag` (a middle tag a group
holds at most once: `else`, `empty`, `plural`), `repeatTag` (one it may repeat: `elif`) and `endTag`.
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` instead,
with no error to show for it. Choose `followTag` or `repeatTag` by what the grammar allows, because the
scanner trusts it: a tag marked once-only that can in fact repeat is an `unexpected_tag` 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 block types and its end tag, and a block's children include its opening tag, so
the candidates for a group are its end tag plus the opening tag of each block it can hold. For
`if_group` that is `endif_tag` from the group and `if_tag`, `elif_tag`, `else_tag` from its three
blocks. Going the other way — from a block the cursor sits in to the
groups that can hold it — is the same table read backwards, which is what a block needs when error
recovery has left it without its group.
Do not reach for lookahead to solve that instead: `ts_language_next_state` follows only the shift on `{%`,
which lands in the "a tag starts here" state no matter how the tree is shaped.
`else_block` is shared by `if_group` and `ifchanged_group`. `comment_block` and `verbatim_block` hold raw
scanner text that admits no tags. `blocktranslate_group` has one block per spelling, `blocktrans_block` and
`blocktranslate_block`, because each must be closed by its own end tag; the counted form nests a
`plural_block` inside it, since `{% plural %}` is only valid after `count`. The block has to know which
form its opening tag was, so `blocktransRules` builds each as a hidden rule (`_blocktrans_tag`,
`_blocktrans_count_tag`) and aliases both to the one visible `blocktrans_tag` where the block uses
it. Their bodies are
`_translate_body`, not `template`, which is why these four blocks need no `conflicts` entry — the rule
below is about a body that can itself start with `{%`.
**Every block needs a `conflicts` entry.** The generator offers a left associativity instead; taking it is
what broke `{% for %}` in `06aa870`, where `prec.left` resolved the shift/reduce on `{%` statically and
silently discarded the parse in which the body continues, so every tag inside a loop became an `ERROR`.
Sharing the blocks 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 `_tag`
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
@@ -85,7 +272,10 @@ arity, or whether a tag is a block tag. Two fallback rules accept them:
- `custom_tag` — `simpleTag($, $.identifier)`, i.e. what `simple_tag`/`inclusion_tag` accept, which is how - `custom_tag` — `simpleTag($, $.identifier)`, i.e. what `simple_tag`/`inclusion_tag` accept, which is how
a tag is registered unless it needs the parser itself. a tag is registered unless it needs the parser itself.
- the last alternative of `filter`, whose name is `$.identifier` rather than one of the builtin literals. - `_custom_filter`, the last alternative of `filter`, whose name is `$.identifier` rather than one of the
builtin literals. Hidden, so the tree is what an inline alternative would give, but named so that a
grammar extending this one can filter it out of `filter`'s `members` the way it can `custom_tag` — see
the extension section in `README.md`.
**A name that only exists inside a tag group has to be reserved instead.** `endif`, `else`, `empty` and **A name that only exists inside a tag group has to be reserved instead.** `endif`, `else`, `empty` and
the rest are tokens only in the state their group opens, so where a tag is named the lexer reads them as an the rest are tokens only in the state their group opens, so where a tag is named the lexer reads them as an
@@ -95,6 +285,55 @@ 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_tag` 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_tag` takes any of
`NAMES_INSIDE_A_TAG_GROUP` with loose arguments, so there is nothing to recover; `prec.dynamic(-2)`, or `-4`, keeps it
from displacing a tag the group does take, and `queries/errors.scm` reports it as
`@error.unexpected_tag`. It added 31 states and no `conflicts` entry.
**Words a tag does not take are an `unexpected_argument` inside it.** Every close utility accepts one
before its scanner token, so `{% else x %}` stays the `else` of its group, `{% endif junk %}` still closes
its `if` (it used to leave the whole file an `ERROR`), and `{% static "a.css" "b.css" %}` is a `static_tag`.
The node is a run of hidden `_unexpected_word`s, and three details make it work:
- **Lexical precedence -1.** A loose word is `token(prec(-1, /(?:[^\s%}]|\}[^\s%}])+/))`, which never holds `}}`:
lexer states are shared between parse states, so the token is tried inside `{{ }}` too, and after
`{{ x| ` it once swallowed the closing braces and left recovery nothing to close the variable with. Tree-sitter weighs lexical
precedence before match length, so a token an argument really takes wins wherever one is valid
(`b|upper` stays a filtered value) and a loose word only matches where nothing else can.
- **Identifiers are words too.** Wherever a keyword is valid — after `{% for a in b`, where `reversed` may
follow, or after `as`, `only`, `noop`, `silent` — tree-sitter lexes every word as an `identifier` to
check for the keyword, and the loose-word token never gets a turn. So a word may also be an
`identifier`, aliased to an anonymous `unexpected_word` so `queries/locals.scm` does not count it as a
reference, with whatever touches it (`a=2`, `x.y|upper`, `"z"|lower:"q"`) as an immediate tail.
- **-1 of dynamic precedence per word**, not per node, so a reading that flags more words than it must
always loses: `{% cycle a b as x silent zqx %}` flags `zqx`, not everything after `a`. `unexpected_tag`
costs -2 or -4 so that a misused tag its group does take beats reading it as a stray.
A tag with a known keyword signature (`simpleTag` given names, so far only `csp_nonce_attr`) needs one more
piece: the scanner's `_kwarg_name` accepts any name followed by `=` and commits the parser to a keyword
argument before the name is checked, so `{% csp_nonce_attr other="b" %}` would be an `ERROR`. `simpleTag`
offers `_unexpected_keyword` — an identifier and its tail, aliased to `unexpected_argument` — beside the
names it knows.
A separator after any argument can now also start stray words, so this needs `conflicts` entries for
`unexpected_argument`, `predicate`, `_translate_option`, and `_unexpected_word` with `variable_attribute`,
`resetcycle_tag` and `library`. It took the table from 3,363 states to 4,913 and the Wasm build from 251KB to
323KB. Two consequences: a repeat the scanner refuses — `{% with a=1 a=2 %}`, `{% blocktrans trimmed trimmed %}`
— is reported as an unexpected argument rather than an `ERROR`; and a stray **reserved** word is still an
`ERROR`, since `not`, `if`, `in`, `is`, `as`, `for` and `from` can never be an identifier:
`{% trans "hi" as x as y %}` errors on the second `as`. A tag missing a required argument, such as
`{% with a = 1 %}`, is an `ERROR` too; `unexpected_argument` only takes words after a tag that is otherwise
complete.
Where several tags share a name the group holds only once — `{% if %}{% else %}{% else %}{% else %}` — reading
any of them as the stray parses, so the ties are broken by dynamic precedence: `unexpected_tag` is -2 when
`_group_held` confirms the innermost group already holds the name and -4 otherwise, so each tag flagged
before its time costs one more, and the first still opens the block however many follow.
**Builtin names must stay keyword-extractable, or the fallbacks swallow them.** `word: $.identifier` turns **Builtin names must stay keyword-extractable, or the fallbacks swallow them.** `word: $.identifier` turns
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
@@ -128,27 +367,86 @@ Modelling a library name makes it keyword-extracted, so it no longer reaches `cu
registers its own tag under one of these names and a different signature therefore gets a parse error. That registers its own tag under one of these names and a different signature therefore gets a parse error. That
is accepted: these names are common enough that shadowing them is the mistake. is accepted: these names are common enough that shadowing them is the mistake.
**Adding a library tag or filter touches four files**: `grammar.js`, `queries/libraries.scm` (which library
it came from), `queries/highlights.scm` (the name, spelled out) and the table in `README.md`, which lists
every library name the grammar models. All four are hand-maintained, and **no test catches a name left out
of any of them**: `tree-sitter test` loads every `.scm` under `queries/`, so a pattern naming a node that
no longer exists aborts the whole run, but a name that was never added passes 304/304 in silence, and the
README nothing checks at all. The library-contents differential below, run against
`django.templatetags.<name>.register`, is what finds a gap.
### External scanner (`src/scanner.c`) ### External scanner (`src/scanner.c`)
Twelve external tokens, for the constraints a context-free grammar cannot express: Twenty 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 the argument is refused: `_bt_option` for `{% blocktranslate %}`'s five options, `_kwarg_name` for `name=value`
`name=value` arguments of a tag registered with `simple_tag`, whose names are open-ended and so are kept arguments — those of a tag registered with `simple_tag`, and the `with` bindings of `{% with %}`,
as names rather than as a mask. `_kwarg_name` returns nothing unless the name is followed by `=`, which `{% include %}` and `{% blocktranslate %}` — whose names are open-ended and so are kept as names rather
than as a mask. `_kwarg_name` returns nothing unless the name is followed by `=`, which
is also what tells an argument apart from the `as name` clause or the end of the tag. `_tag_open` clears is also what tells an argument apart from the `as name` clause or the end of the tag. `_tag_open` clears
both, and every rule that uses either has one. Both are zero width and hidden, so no tree changes. The state is one byte of both, and every rule that uses either has one. Both are zero width and hidden, so no tree changes. The state is one byte of
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_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_tag` (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_tag` — the marker where a group's required tag block is missing, aliased at each use to
`missing_endX_tag` or `missing_plural_tag` (`MISSING_TAGS`). 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_tag` 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.
- `_text_brace` — a single `{` that is text although the content regex cannot tell: before `{%` or `{#`,
and at the end of input. Django splits a template with `({%.*?%}|{{.*?}}|{#.*?#})`, so a `{` is text
unless one of those starts at it; `{{{` is not a text brace, since `{{` starts a variable at its first
brace. `content` is a right-associative run of `_text` and `_text_brace`, so it stays one node. Inside a
group's body the missing-tag check reads past `{` first, so it scans the brace itself
(`scan_text_brace_rest`) rather than give up the position.
- `_missing_variable_close` — the marker where a variable's `}}` is missing, aliased to
`missing_variable_close`. It is placed only where the variable cannot go on — at the end of input, or
before a `}` that is not `}}`, a `%` or a `{` — so `{{ hello }{% if x %}` closes the variable, reads the
stray `}` as text and parses the `if` normally, where error recovery used to take the rest of the file.
It is valid only after a variable's value, never where `content` is. It added 18 states.
- `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_?
@@ -159,31 +457,65 @@ and the reason is worth keeping straight: its five options are a fixed set, so u
`arrangements` expresses exactly that — and the scanner is standing in for a grammar that would be correct `arrangements` expresses exactly that — and the scanner is standing in for a grammar that would be correct
but takes minutes to build. Where the set is open, as with `{% querystring %}`'s `**kwargs`, no grammar but takes minutes to build. Where the set is open, as with `{% querystring %}`'s `**kwargs`, no grammar
can express it at all, and the scanner remembers the names the tag has been given instead of a mask of can express it at all, and the scanner remembers the names the tag has been given instead of a mask of
known ones. Which tags get that guard is itself a differential question: only `parse_bits` refuses a known ones. Only `parse_bits` actually refuses a repeat: the `with` bindings of
repeat, so `{% with a=1 a=2 %}`, `{% include "t" with a=1 a=2 %}` and `{% blocktranslate with a=1 a=2 %}` `{% with %}`, `{% include %}` and `{% blocktranslate %}` all go through `token_kwargs`, which builds a dict
are all accepted by Django — `token_kwargs` builds a dict and the later value simply wins — and must not and lets the later value win. They are guarded here anyway, as a deliberate divergence — writing a name
be guarded. twice has no use and is a mistake worth reporting. `{% blocktranslate %}`'s `count` is not part of that,
because Django keeps it in a dict of its own, so `{% blocktranslate with a=1 count a=2 %}` is accepted here
as it is there.
Zero-width guards carry one hazard: `recover_with_missing` can supply one without the scanner running. The Zero-width guards carry one hazard: `recover_with_missing` can supply one without the scanner running. The
`if (valid_symbols[MatcherError]) return false;` near the top of `scan` is what stops a guard being scanned `if (valid_symbols[MatcherError]) return false;` near the top of `scan` is what stops a guard being scanned
during tree-sitter's mark-everything-valid recovery pass, so new guards go **below** it — and above the during tree-sitter's mark-everything-valid recovery pass, so new guards go **below** it — and above the
whitespace loop, which skips the separator the grammar still has to match. A guard must also never be valid whitespace loop, which skips the separator the grammar still has to match. A guard must also never be valid
where `content` is: a zero-width token at a content boundary preempts the internal lexer and `content` stops where `content` is: a zero-width token at a content boundary preempts the internal lexer and `content` stops
matching. matching. `_missing_tag` is the one exception, and is safe only because it returns a token solely at the end
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
`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
adding it there too; filter names appear there without their colon. A name the grammar does not know is adding it there too; filter names appear there without their colon. A name the grammar does not know is
captured through its field instead (`(filter name: (identifier))`, `(custom_tag tag: (identifier))`). captured through its field instead (`(filter filter_name: (identifier))`, `(custom_tag tag_name: (identifier))`).
`queries/libraries.scm` is not a query editors run themselves; it is data for a linter, and every tag or `queries/libraries.scm` is not a query editors run themselves; it is data for a linter, and every tag or
filter added from a library belongs in it. `queries/locals.scm` relies on the `variable:` field to tell a filter added from a library belongs in it. `queries/locals.scm` relies on the `variable:` field to tell a
binding from a reference — another reason to keep `asVariable` uniform. binding from a reference — another reason to keep `asVariable` uniform. `{% blocktranslate %}`'s `asvar`
target carries an `asvar:` field instead, because it is written inside the tag but assigned outside it:
the scope on `blocktrans_block` / `blocktranslate_block` confines the `with` and `count` bindings, and would swallow `asvar`
too if it were spelled the same way. `queries/exports.scm` names it for a tool to
bind in the scope around the block, which a locals query cannot do: it places a definition in the innermost
scope containing it and offers no way out again.
`queries/conditionals.scm` is data for a tool too: `@conditional.group` / `@conditional.branch` /
`@conditional.default` say which bodies render on only some passes, so a checker can tell that
`{% if a %}{% now "Y" as n %}{% endif %}{{ n }}` may reach `{{ n }}` with nothing bound. A locals query
cannot: it matches a reference to the definition whose scope contains it and has no notion of a path not
taken. A group is exhaustive iff it has a `@conditional.default`, so each pattern captures the group
alongside one of its parts and a tool needs no list of which block belongs to which group. Adding a tag
group whose body may be skipped means adding it there — `{% cache %}` is the non-obvious one, since a
cache hit skips the body outright and `CacheNode` pushes no context.
A branch that is also a `@local.scope` confines its bindings whichever way the branch goes, which is why
`{% for %}` is inert for that check: `ForNode` renders `nodelist_empty` **inside** the same
`context.push()` as the loop body, so `empty_block` is a scope alongside `for_block`.
`queries/errors.scm` collects what an editor should report: `(ERROR)` as `@error.syntax`, `(MISSING)` as
`@error.missing`, every missing-tag marker as `@error.missing_tag`, `unexpected_tag` and
`unexpected_argument` as `@error.unexpected_tag` and `@error.unexpected_argument`, and
`missing_variable_close` as `@error.missing_variable_close`. A tree holding only markers has
no `has_error`, so a tool that checks that flag alone misses them. Adding a tag group means adding its
marker there, since no supertype matches them all.
## Testing ## Testing
Corpus tests live in `test/corpus/*.txt`, one file per tag. Corpus tests live in `test/corpus/*.txt`, one file per tag, and `missing.txt` for the missing-tag markers.
- A test whose header carries `:error` asserts only that the parse fails. Prefer it for syntax-error cases - A test whose header carries `:error` asserts only that the parse fails. Prefer it for syntax-error cases
over pinning an error-recovery tree, which is brittle. over pinning an error-recovery tree, which is brittle.
@@ -195,8 +527,8 @@ Corpus tests live in `test/corpus/*.txt`, one file per tag.
### Checking against Django itself ### Checking against Django itself
The `Pipfile` pins Django so the grammar can be diffed against the real implementation instead of against `Pipfile.lock` pins Django (`Pipfile` itself says `django = "*"`) so the grammar can be diffed against the
documentation or memory. Both checks are worth repeating whenever tags, filters or literals change: real implementation instead of against documentation or memory. Both checks are worth repeating whenever tags, filters or literals change:
- **Builtin parity** — enumerate `Engine.default_builtins`, then compare `register.tags` and - **Builtin parity** — enumerate `Engine.default_builtins`, then compare `register.tags` and
`register.filters` against `grammar.js`. For filters also compare argument arity via `register.filters` against `grammar.js`. For filters also compare argument arity via
@@ -214,12 +546,30 @@ documentation or memory. Both checks are worth repeating whenever tags, filters
### Deliberate divergences from Django ### Deliberate divergences from Django
`README.md` carries a shorter list of its own, and the two are not the same set on purpose: the README
lists only the divergences where Django accepts an input that is almost certainly a mistake, which is the
rule it states. Anything added here that fits that rule belongs there too; anything that does not — an
unavoidable limitation, or a case where this grammar is the more permissive one — stays here.
- Django has no number token: `[\w.]+` is one lexeme and `int()`/`float()` decides whether it is a literal, - Django has no number token: `[\w.]+` is one lexeme and `int()`/`float()` decides whether it is a literal,
so `1a`, `1e`, `0x1f`, `1.2.3`, `1__0` and `1.` are variable _lookups_ there and errors here. so `1a`, `1e`, `0x1f`, `1.2.3`, `1__0` and `1.` are variable _lookups_ there and errors here. `1.` is the
one that needs care: `Variable` calls `float()` and then rejects what it parsed if the last character is
a dot (`# "2." is invalid`), so a trailing dot is a number only with an exponent after it — `1.e5` is a
float and `1.` is not. The `number` regex is spelled to say that, and `test/corpus/template_variable.txt`
pins all three.
- Django has no boolean or `None` token either. `True`, `False` and `None` are lookups, which resolve
because `BaseContext._reset_dicts` seeds every context with `{"True": True, "False": False, "None": None}`.
They are `boolean` and `none` literals here, so that a tool reading `queries/locals.scm` does not count
them as references to something the template never defines. Shadowing still parses — `{% with True=1 %}`
binds an `identifier`, because the keyword is not valid where a binding name is — but `{{ True.x }}` is
an error here and an ordinary two-part lookup in Django, which renders `string_if_invalid`.
- A `with` binding written twice (`{% with a=1 a=2 %}`, and the same in `{% include %}` and
`{% blocktranslate %}`) is reported here, as an `unexpected_argument`, and not in Django, where
`token_kwargs` builds a dict and the later value wins. It has no use, so it is treated as the mistake it almost certainly is.
- `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time. - `{% csp_nonce_attr "a" media="b" %}` is accepted, matching Django, which only rejects it at render time.
- A misspelled tag or filter cannot be caught: `{{ x|lenght }}` is indistinguishable from a filter some - A misspelled tag or filter cannot be caught: `{{ x|lenght }}` is indistinguishable from a filter some
library registered. Cross-referencing names against `{% load %}` needs the project's Python, so it library registered. Cross-referencing names against `{% load %}` needs the project's Python, so it
belongs in a linter; the `load` rule already parses the library and `from` names for one to use. belongs in a linter; the `load_tag` rule already parses the library and `from` names for one to use.
- `{% mytag %}…{% endmytag %}` parses as two sibling `custom_tag`s. Nesting them would mean guessing that - `{% mytag %}…{% endmytag %}` parses as two sibling `custom_tag`s. Nesting them would mean guessing that
an unknown tag is a block tag. an unknown tag is a block tag.
- A custom tag's contents are only assumed to be the `simple_tag` shape. `@register.tag` receives the raw - A custom tag's contents are only assumed to be the `simple_tag` shape. `@register.tag` receives the raw
@@ -228,14 +578,21 @@ documentation or memory. Both checks are worth repeating whenever tags, filters
redefines `length` with a different arity gets a false error here. redefines `length` with a different arity gets a false error here.
- Django's `static` tags ignore what follows their argument: `StaticNode.handle_token` looks for `as` two - Django's `static` tags ignore what follows their argument: `StaticNode.handle_token` looks for `as` two
bits from the end, so `{% static "a" junk %}` and `{% static "a" as u v %}` are accepted there and bits from the end, so `{% static "a" junk %}` and `{% static "a" as u v %}` are accepted there and
rejected here, and `PrefixNode.handle_token` raises `IndexError` rather than `TemplateSyntaxError` on reported here as an `unexpected_argument`, and `PrefixNode.handle_token` raises `IndexError` rather than `TemplateSyntaxError` on
`{% get_static_prefix as %}`. `{% get_static_prefix as %}`.
- Django's `{% cache %}` takes its fragment name as the raw word in that position and finds the cache to - Django's `{% cache %}` takes its fragment name as the raw word in that position and finds the cache to
use by testing whether the last word starts with `using=`, and only when something precedes it. So use by testing whether the last word starts with `using=`, and only when something precedes it. So
`{% cache 500 sidebar.name %}` names a fragment there, `{% cache 500 using="c" %}` names a fragment `{% cache 500 sidebar.name %}` names a fragment there, `{% cache 500 using="c" %}` names a fragment
called `using="c"` rather than choosing a cache, and `{% cache 500 sidebar using= %}` takes an empty called `using="c"` rather than choosing a cache, and `{% cache 500 sidebar using= %}` takes an empty
name. All three are errors here. name. All three are errors here.
- `do_translate` refuses `as` and `noop` as the value of its `context` option, so
`{% trans "hi" context noop %}` is an error there and a context named by the variable `noop` here.
- `partial`/`partialdef` are Django builtins as of Django 6; `elif`/`else`/`empty` are modelled as parts of - `partial`/`partialdef` are Django builtins as of Django 6; `elif`/`else`/`empty` are modelled as parts of
their enclosing tag rather than as separate tags. their enclosing tag rather than as separate tags.
- An unterminated group parses here and raises `TemplateSyntaxError` in Django: `{% if a %}x`, or a
`{% for %}` closed while an `{% if %}` inside it is still open, holds a `missing_endif_tag` marker
where the end tag belongs. The marker is the error, reported through `queries/errors.scm`; a check
for `ERROR` nodes alone accepts the tree.
- A `{{` that does not close is text in Django, whose lexer only takes a variable written `{{ … }}` on one
line, so `{{ hello }` renders as written. Here it is a `template_variable` closed by a
`missing_variable_close` marker, reported through `queries/errors.scm`, since a half-written variable is
what it almost always is. The same lexer makes `{{ hello\n}}` text too, which this grammar accepts as a
variable, because its separators allow newlines.
+76
View File
@@ -0,0 +1,76 @@
cmake_minimum_required(VERSION 3.13)
project(tree-sitter-django
VERSION "0.1.0"
DESCRIPTION "Django grammar for tree-sitter"
HOMEPAGE_URL "https://github.com/danhanson/tree-sitter-django"
LANGUAGES C)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF)
set(TREE_SITTER_ABI_VERSION 15 CACHE STRING "Tree-sitter ABI version")
if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$")
unset(TREE_SITTER_ABI_VERSION CACHE)
message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer")
endif()
include(GNUInstallDirs)
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
"${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js"
COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating grammar.json")
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h"
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h"
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
--abi=${TREE_SITTER_ABI_VERSION}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating parser.c")
add_library(tree-sitter-django src/parser.c)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c)
target_sources(tree-sitter-django PRIVATE src/scanner.c)
endif()
target_include_directories(tree-sitter-django
PRIVATE src
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/bindings/c>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_definitions(tree-sitter-django PRIVATE
$<$<BOOL:${TREE_SITTER_REUSE_ALLOCATOR}>:TREE_SITTER_REUSE_ALLOCATOR>
$<$<CONFIG:Debug>:TREE_SITTER_DEBUG>)
set_target_properties(tree-sitter-django
PROPERTIES
C_STANDARD 11
POSITION_INDEPENDENT_CODE ON
SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}"
DEFINE_SYMBOL "")
configure_file(bindings/c/tree-sitter-django.pc.in
"${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc" @ONLY)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bindings/c/tree_sitter"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING PATTERN "*.h")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(TARGETS tree-sitter-django
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
file(GLOB QUERIES queries/*.scm)
install(FILES ${QUERIES}
DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/django")
add_custom_target(ts-test "${TREE_SITTER_CLI}" test
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "tree-sitter test")
Generated
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "tree-sitter-django"
description = "Django grammar for tree-sitter"
version = "0.1.0"
authors = ["Daniel Hanson"]
license = "MIT"
readme = "README.md"
keywords = ["incremental", "parsing", "tree-sitter", "django"]
categories = ["parser-implementations", "parsing", "text-editors"]
repository = "https://github.com/danhanson/tree-sitter-django"
edition = "2021"
autoexamples = false
build = "bindings/rust/build.rs"
include = [
"bindings/rust/*",
"grammar.js",
"queries/*",
"src/*",
"tree-sitter.json",
"/LICENSE",
]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter-language = "0.1"
[build-dependencies]
cc = "1.2"
[dev-dependencies]
tree-sitter = "0.26.12"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Daniel Hanson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Generated
+116
View File
@@ -0,0 +1,116 @@
LANGUAGE_NAME := tree-sitter-django
HOMEPAGE_URL := https://github.com/danhanson/tree-sitter-django
VERSION := 0.1.0
DESCRIPTION := Django grammar for tree-sitter
# repository
SRC_DIR := src
TS ?= tree-sitter
# install directory layout
PREFIX ?= /usr/local
DATADIR ?= $(PREFIX)/share
INCLUDEDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
BINDIR ?= $(PREFIX)/bin
PCLIBDIR ?= $(LIBDIR)/pkgconfig
# source/object files
PARSER := $(SRC_DIR)/parser.c
EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c))
OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS))
# flags
ARFLAGS ?= rcs
override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
# ABI versioning
SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER))
SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION)))
# OS-specific bits
MACHINE := $(shell $(CC) -dumpmachine)
ifneq ($(findstring darwin,$(MACHINE)),)
SOEXT = dylib
SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT)
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT)
LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks
else ifneq ($(findstring mingw32,$(MACHINE)),)
SOEXT = dll
LINKSHARED += -s -shared -Wl,--out-implib,lib$(LANGUAGE_NAME).dll.a
else
SOEXT = so
SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR)
SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR)
LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER)
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
endif
endif
all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
lib$(LANGUAGE_NAME).a: $(OBJS)
$(AR) $(ARFLAGS) $@ $^
lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
$(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
ifneq ($(STRIP),)
$(STRIP) $@
endif
ifneq ($(findstring mingw32,$(MACHINE)),)
lib$(LANGUAGE_NAME).dll.a: lib$(LANGUAGE_NAME).$(SOEXT)
endif
$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \
-e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \
-e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \
-e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
$(SRC_DIR)/grammar.json: grammar.js
$(TS) generate --no-parser $^
$(PARSER): $(SRC_DIR)/grammar.json
$(TS) generate $^
install: all
install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
install -m644 bindings/c/tree_sitter/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
ifneq ($(findstring mingw32,$(MACHINE)),)
install -d '$(DESTDIR)$(BINDIR)'
install -m755 lib$(LANGUAGE_NAME).dll '$(DESTDIR)$(BINDIR)'/lib$(LANGUAGE_NAME).dll
install -m755 lib$(LANGUAGE_NAME).dll.a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).dll.a
else
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) lib$(LANGUAGE_NAME).$(SOEXT)
endif
ifneq ($(wildcard queries/*.scm),)
install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django
endif
uninstall:
$(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
'$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
$(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django
clean:
$(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a
test:
$(TS) test
.PHONY: all install uninstall clean test
Generated
+41
View File
@@ -0,0 +1,41 @@
// swift-tools-version:5.3
import Foundation
import PackageDescription
var sources = ["src/parser.c"]
if FileManager.default.fileExists(atPath: "src/scanner.c") {
sources.append("src/scanner.c")
}
let package = Package(
name: "TreeSitterDjango",
products: [
.library(name: "TreeSitterDjango", targets: ["TreeSitterDjango"]),
],
dependencies: [
.package(url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.10.0"),
],
targets: [
.target(
name: "TreeSitterDjango",
dependencies: [],
path: ".",
sources: sources,
resources: [
.copy("queries")
],
publicHeadersPath: "bindings/swift",
cSettings: [.headerSearchPath("src")]
),
.testTarget(
name: "TreeSitterDjangoTests",
dependencies: [
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
"TreeSitterDjango",
],
path: "bindings/swift/TreeSitterDjangoTests"
)
],
cLanguageStandard: .c11
)
+342 -1
View File
@@ -1,6 +1,75 @@
# tree-sitter-django # tree-sitter-django
A [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for the Django Template Language (DTL) — the `{% %}` tags and `{{ }}` variables used in Django templates. A [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for the [Django Template Language (DTL)](https://docs.djangoproject.com/en/6.1/ref/templates/language/#the-django-template-language) — the `{% %}` tags, `{{ }}` variables and `{# #}` comments used in Django templates.
## Coverage
Every tag and filter Django ships as a builtin is modelled — 28 tags and 57 filters as of Django 6.1 —
and so is every tag and filter of the five libraries below. Arguments are part of the grammar rather than
something a linter checks later, so a builtin written with the wrong ones is a parse error: `{{ x|length:2 }}`
and `{% now %}` do not parse, because `length` takes no argument and `now` requires one.
The target is Django 6.1, including the builtins Django 6 added (`{% partial %}`, `{% partialdef %}`,
`{% querystring %}`, `{% csp_nonce_attr %}`). Rather than being written from the documentation, the grammar
is checked against a real Django — `Pipfile.lock` pins the version — by enumerating `Engine.default_builtins`
for the tag and filter lists and their arities, and by running candidate snippets through both
`django.template.Template()` and this parser to compare what each accepts. Where the two deliberately
disagree is listed under [Divergences](#divergences-from-django).
## Using it
`tree-sitter-django` is [on npm](https://www.npmjs.com/package/tree-sitter-django), and the other language
bindings are in place. What is here today:
- **`src/parser.c` and `src/scanner.c` are generated and committed**, so any tree-sitter host can compile
them directly without running the CLI. The scanner is required: block-name matching
(`{% block a %}…{% endblock a %}`), the repeated-argument guards and the missing-tag markers live in it.
- **`tree-sitter-django.wasm`** is for a `web-tree-sitter` host or an editor that loads Wasm grammars. It
is not checked in: `npm run parser-build` makes it, and a release ships it both in the npm package (the
`tree-sitter-django/tree-sitter-django.wasm` export) and as an asset on the GitHub release.
- **`queries/`** holds the highlight, locals, tags, injection and error queries an editor loads, plus three files
meant for a linter. See [Queries](#queries).
- **`tree-sitter.json`** names the grammar `django`, scopes it `source.django`, and points at the query
files, which is what a tree-sitter host reads to wire all of the above together.
- **`bindings/`** holds a binding per language, generated by `tree-sitter init` from the `bindings` and
`metadata` sections of `tree-sitter.json`. See [Bindings](#bindings).
## Bindings
| Language | Build from a checkout | Entry point |
| -------- | ------------------------------------------------------------- | ------------------------------------------------------ |
| Node | `npm install`, which uses a prebuilt binary when there is one | `bindings/node/index.js` (ESM), typed by `index.d.ts` |
| Rust | `cargo build` | `bindings/rust/lib.rs`, `Cargo.toml` |
| Python | `pip install .` | `bindings/python/tree_sitter_django`, `pyproject.toml` |
| Go | `go test ./bindings/go` | `bindings/go/binding.go`, `go.mod` |
| Swift | `swift build` | `bindings/swift/TreeSitterDjango`, `Package.swift` |
| C / C++ | `make` (static, shared and a pkg-config file) or `cmake` | `bindings/c/tree_sitter/tree-sitter-django.h` |
```js
import Parser from "tree-sitter";
import django from "tree-sitter-django";
const parser = new Parser();
parser.setLanguage(django);
```
To use the parser on a website, the wasm needs to be served then loaded.
```js
import { Language, Parser } from "web-tree-sitter";
await Parser.init();
const wasm = "my-resources-path/tree-sitter-django.wasm";
const parser = new Parser();
parser.setLanguage(await Language.load(wasm));
```
```python
import tree_sitter, tree_sitter_django
language = tree_sitter.Language(tree_sitter_django.language())
tree = tree_sitter.Parser(language).parse(b"{% if x %}hi {{ y|upper }}{% endif %}")
```
## Development ## Development
@@ -11,3 +80,275 @@ npm run parser-test # run the corpus tests in test/corpus
npm run parser-build # build the WASM binary npm run parser-build # build the WASM binary
npm run playground # interactively explore the grammar npm run playground # interactively explore the grammar
``` ```
`src/parser.c`, `src/grammar.json` and `src/node-types.json` are generated **and committed**, so a change
to `grammar.js` or `src/scanner.c` is only half made until `parser-generate` has run and the result is
committed alongside it. `tree-sitter test` compiles the parser itself, so a stale `src/` shows up as tests
that pass against the wrong grammar.
Pull requests run the corpus tests and every binding through `.github/workflows/ci.yml`. A release is a
version-bump commit on `main`: raise the version in `package.json` and in `tree-sitter.json`'s `metadata`,
run `npm run parser-generate`, and push. The workflow tags `v<version>`, attaches
`tree-sitter-django.wasm` to the GitHub release, and stages the npm publish with provenance and prebuilt
binaries for Linux, macOS and Windows. Staging is not publishing: npm holds the tarball until a maintainer
runs `npm stage approve <stage-id>` with 2FA, and the workflow's job summary carries that command. Running
the workflow by hand stages the current version if npm does not have it yet, which is how a release that
failed part way through is retried.
## Queries
Five of the files in `queries/` are query files an editor loads:
- **`highlights.scm`** — syntax highlighting. Tag and filter names are listed one by one, so a name the
grammar knows is captured as `@function` and a name it is only tolerating as `@function.call`.
- **`locals.scm`** — scopes, definitions and references, so an editor can resolve a variable to the tag
that bound it. A definition is a `variable:` field; the scopes are the blocks whose bindings do not
outlive them (`{% with %}`, `{% for %}`, `{% empty %}`, `{% block %}`, `{% partialdef %}`,
`{% blocktranslate %}`).
- **`tags.scm`** — the symbol index behind `tree-sitter tag`. A `{% partialdef %}` and the names a
`{% load %}` brings in are definitions; every tag and filter used is a reference.
- **`injections.scm`** — marks template text and `{% verbatim %}` bodies as another language's content, so
the HTML around the tags can be parsed by its own grammar. It names no language, leaving the editor to
supply one.
- **`errors.scm`** — what to report as a problem: `(ERROR)` as `@error.syntax`, tree-sitter's own
`(MISSING)` as `@error.missing`, and `@error.missing_tag` for the marker placed where a group's required
tag is missing. An unterminated `{% if a %}x` parses as an `if_group` ending in a zero-width
`missing_endif_tag` rather than as an `ERROR`, so completion and the scope queries keep working while
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_tag`, captured as `@error.unexpected_tag`. Words a tag does not take, such as the `x` in
`{% else x %}`, are an `unexpected_argument` inside that tag, captured as `@error.unexpected_argument`. A `{{` whose `}}` is missing closes with a zero-width
`missing_variable_close`, captured as `@error.missing_variable_close`, so the rest of the template still parses.
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:
- **`libraries.scm`** — which library each tag or filter is registered in, and what each `{% load %}`
brings in. The grammar never requires the load, so reporting a missing one is a linter's job; a query
has no ordering predicate to compare the positions itself.
- **`exports.scm`** — a name a tag binds in the scope _around_ it rather than the scope it opens. Only
`{% blocktranslate %}`'s `asvar` does that, and a locals query cannot say so: it places a definition in
the innermost scope containing it and offers no way out again.
- **`conditionals.scm`** — which bodies render on only some passes, as `@conditional.group` /
`@conditional.branch` / `@conditional.default`, so a checker can tell that
`{% if a %}{% now "Y" as n %}{% endif %}{{ n }}` may reach `{{ n }}` with nothing bound. A group is
exhaustive exactly when one of its branches is a default. A locals query has no notion of a path not
taken.
`tree-sitter test` loads every file in the directory, so a pattern naming a node that no longer exists
fails the suite rather than quietly matching nothing.
## Django libraries
Five of Django's template libraries are not builtins: their names have to be `{% load %}`ed, or preloaded
through the engine's `OPTIONS: {"builtins": [...]}`. Every tag and filter all five register is modelled,
with the same argument and arity rules as a builtin:
| library | tags | filters |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `i18n` | `trans`, `translate`, `blocktrans`, `blocktranslate`, `language`, `get_available_languages`, `get_current_language`, `get_current_language_bidi`, `get_language_info`, `get_language_info_list` | `language_bidi`, `language_name`, `language_name_local`, `language_name_translated` |
| `l10n` | `localize` | `localize`, `unlocalize` |
| `static` | `static`, `get_static_prefix`, `get_media_prefix` | — |
| `cache` | `cache` | — |
| `tz` | `localtime`, `timezone`, `get_current_timezone` | `localtime`, `timezone`, `utc` |
**The `{% load %}` is never required**, because an engine may have preloaded the library. Whether one is
present is a question about the order of nodes rather than about how the template parses, so it is left to
a tool: `queries/libraries.scm` marks each name with the library it came from and each `{% load %}` with
what it brings in, and the tool joins the two. Such a tool needs to know that loading is positional
(`{% trans "x" %}{% load i18n %}` is an error), that it is per file rather than inherited through
`{% extends %}` or `{% include %}`, and that `{% load trans from i18n %}` brings in only the names it lists.
Because these names are modelled, they win over the fallbacks below: a project that registers its own tag
under one of them with a different signature gets a parse error. They are common enough that shadowing
them is treated as the mistake.
If a project insists on shadowing one of these tags, it may use a sub-grammar that replaces the rule with
their own. See [Adding rules for your own tags and filters](###adding-rules-for-your-own-tags-and-filters).
## Custom tags and filters
A library's tags and filters are registered in Python, so the grammar cannot know their names, their
arity, or whether a tag opens a body. Two fallback rules accept them:
- **Tags.** Any tag the grammar does not recognise parses as `custom_tag`, with the shape `simple_tag` and
`inclusion_tag` give: positional filter expressions, then `name=value` keyword arguments, then an
optional `as name`. So `{% mytag a b|upper "s" k=1 as out %}` parses, with the name on the `tag_name:` field.
A keyword argument written twice is refused.
- **Filters.** An unknown filter parses as `(filter filter_name: (identifier))` with at most one `:argument`. No arity check for unknown filters is possible.
Both are captured as `@function.call` rather than `@function`, so an editor can distinguish a name the
grammar knows from one it is only tolerating.
What the fallbacks cannot do:
- **A block tag is not nested.** `{% mytag %}…{% endmytag %}` is two sibling `custom_tag`s; pairing them
would mean guessing that an unknown tag opens a body.
- **A tag registered with `@register.tag` may parse anything**, since it receives the raw token, so one
whose arguments are not the `simple_tag` shape — `{% mytag <<>> %}` — is an error here and not in Django.
- **A misspelled name cannot be caught.** `{{ x|lenght }}` is indistinguishable from a filter some library
registered. Cross-referencing names against `{% load %}` needs the project's Python, so it belongs in a
linter; `queries/libraries.scm` and the `load_tag` 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 `unexpected_tag`, reported by `errors.scm`, rather than a call to a custom tag of that name.
### Adding rules for your own tags and filters
For a project whose custom tags deserve real rules rather than the fallbacks, use this grammar as a base
grammar. `grammar(base, {...})` merges your rules into it, and a rule that redefines one the base already
has receives it as `previous`. A tag is an alternative of `template_tag`; a filter is an
alternative of `filter`.
```js
import django from "tree-sitter-django/grammar";
export default grammar(django, {
name: "djangox",
conflicts: ($, previous) => [...previous, [$.loop_block]],
rules: {
template_tag: ($, previous) => choice(previous, $.map_tag, $.loop_group),
// {% map items over rows %}
map_tag: ($) =>
seq(
$.simpleTagOpen,
field("tag_name", "map"),
$._sep,
$.filtered_value,
$._sep,
"over",
$._sep,
$.filtered_value,
$.simpleTagClose,
),
// {% loop x %}…{% endloop %}
loop_tag: ($) =>
seq(
$.startTagOpen,
field("tag_name", "loop"),
$._sep,
$.identifier,
$.startTagClose,
),
loop_block: ($) => seq($.loop_tag, optional($.template)),
endloop_tag: ($) =>
seq($.endTagOpen, field("tag_name", "endloop"), $.endTagClose),
loop_group: ($) =>
seq(
$.loop_block,
choice($.endloop_tag, alias($._missing_tag, $.missing_endloop_tag)),
),
// {{ value|shout:"!" }}
filter: ($, previous) =>
choice(
previous,
seq(field("filter_name", "shout"), ":", field("argument", $.value)),
),
},
});
```
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`/`tag`/`simpleTag` helpers the base
grammar is built from are private to it, but the separator is the hidden rule `$._sep`, and it and the
tag utilities (`simpleTagOpen` and the rest) are rules, so an extending grammar uses them as they are.
- **Every tag begins with one of the opens**: `$.simpleTagOpen`, with `$.simpleTagClose` at its end, for a
tag outside any group such as `map_tag`, and `startTagOpen`, `followTagOpen`, `repeatTagOpen` or
`endTagOpen` 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` 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 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 `loop_tag` ending in `$.startTagClose`, a `loop_block` of that tag and the body, an
`endloop_tag` built from `$.endTagOpen` and `$.endTagClose`, and a `loop_group` of the block and the
end tag. A middle tag uses `followTagOpen`/`followTagClose` if the group holds it at most once, and
`repeatTagOpen`/`repeatTagClose` if it may repeat; the scanner trusts that choice. The block needs a
`conflicts` entry, as every block in the base grammar does. The generator offers an associativity
instead; taking it silently discards the parse in which the body continues.
- **End tags are not reserved**, a stray `{% endmap %}` parses as a `custom_tag` rather than an error — `reserved` takes no
`previous`, so the `tag_name` list cannot be added to a name at a time. Instead you may drop the `custom_tag` rule to avoid parsing end tags as custom tags. While the fallback
is there, stray words in one of your end tags (`{% endloop extra %}`) read as a `custom_tag` too, which
leaves your group with a missing-tag marker at the end of input instead of an `unexpected_argument`.
- **Your groups get missing-tag markers.** The scanner knows no group by name — a group is closed by `end`
followed by its opening tag's name — so the `alias($._missing_tag, $.missing_endloop_tag)` choice
above is all it takes: an unclosed `{% loop x %}`, or one left open inside an `{% if %}` that
`{% endif %}` closes, holds a `missing_endloop_tag`. 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:
```c
#define tree_sitter_django_external_scanner_create tree_sitter_djangox_external_scanner_create
#define tree_sitter_django_external_scanner_destroy tree_sitter_djangox_external_scanner_destroy
#define tree_sitter_django_external_scanner_scan tree_sitter_djangox_external_scanner_scan
#define tree_sitter_django_external_scanner_serialize tree_sitter_djangox_external_scanner_serialize
#define tree_sitter_django_external_scanner_deserialize tree_sitter_djangox_external_scanner_deserialize
#include "../node_modules/tree-sitter-django/src/scanner.c"
```
The base's `tree_sitter/parser.h` then resolves to your own generated copy, and the externals keep the
order they have here, so the scanner's token enum stays valid. `tree-sitter build` takes no include
path, hence the relative `#include`; the package also exports `tree-sitter-django/src/scanner.c`, so a
build step can hand the compiler an absolute path from `import.meta.resolve` where a package manager's
layout makes the relative one unreliable.
**Turning the fallbacks off.** A grammar that models every tag and filter its project uses probably does
not want `custom_tag` and `_custom_filter` accepting unrecognized names as they allow typos to parse. `previous` is the base rule whose `members` are filterable. We can filter out these rules to make the grammar more strict:
```js
template_tag: ($, previous) =>
choice(
...previous.members.filter((m) => m.name !== "custom_tag"),
$.map_tag,
),
filter: ($, previous) =>
choice(
...previous.members.filter((m) => m.name !== "_custom_filter"),
seq(field("name", "shout"), ":", field("argument", $.value)),
),
```
`{% mytag %}` and `{{ x|unknown }}` are then errors, while the builtins, the libraries and your own rules
carry on as before.
## Divergences from Django
The grammar aims to accept what Django accepts, with one deliberate exception: where Django takes an
input without complaint but the input is almost certainly a mistake. It reads the mistake and silently
renders nothing or quietly ignores half of what was written. This grammar tries to report such mistakes
as errors instead. Every case below is input Django itself accepts:
- **Number-shaped names.** Django has no number token: a word is a literal if `int()` or `float()` parses
it and a variable lookup otherwise, so `0x1f`, `1a`, `1.`, `1.2.3` and `1__0` look up a variable of that
name and render nothing. (`1.` really is a lookup there and not a float — `Variable` rejects a trailing
dot after parsing it — while `1.e5` is a float, and both are treated that way here.)
- **An attribute of `True`, `False` or `None`.** Every Django context is seeded with those three names, so
`{{ True.x }}` is an ordinary two-part lookup that can never resolve.
- **A keyword argument written twice.** `{% with a=1 a=2 %}`, and the same in `{% include %}` and
`{% blocktranslate %}`, where Django builds a dict and lets the last value win.
- **Anything after a `{% static %}` path.** `StaticNode.handle_token` looks for `as` two words from the end
and ignores the rest, so `{% static "a" junk %}` and `{% static "a" as url junk %}` render as if the
extra words were not there.
- **A `{% cache %}` argument meant as something other than the fragment name.** Django takes the raw word
in that position, so `{% cache 500 using="c" %}` names a fragment called `using="c"` rather than choosing
a cache, and `{% cache 500 sidebar.name %}` names one with a dot in it.
In the other direction the grammar is more permissive only where it cannot know better, which is what the
fallbacks above are for.
## License
MIT — see [LICENSE](LICENSE).
Generated
+35
View File
@@ -0,0 +1,35 @@
{
"targets": [
{
"target_name": "tree_sitter_django_binding",
"dependencies": [
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
],
"include_dirs": [
"src",
],
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
],
"variables": {
"has_scanner": "<!(node -p \"fs.existsSync('src/scanner.c')\")"
},
"conditions": [
["has_scanner=='true'", {
"sources+": ["src/scanner.c"],
}],
["OS!='win'", {
"cflags_c": [
"-std=c11",
],
}, { # OS == "win"
"cflags_c": [
"/std:c11",
"/utf-8",
],
}],
],
}
]
}
+10
View File
@@ -0,0 +1,10 @@
prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: tree-sitter-django
Description: @PROJECT_DESCRIPTION@
URL: @PROJECT_HOMEPAGE_URL@
Version: @PROJECT_VERSION@
Libs: -L${libdir} -ltree-sitter-django
Cflags: -I${includedir}
+16
View File
@@ -0,0 +1,16 @@
#ifndef TREE_SITTER_DJANGO_H_
#define TREE_SITTER_DJANGO_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_django(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_DJANGO_H_
+15
View File
@@ -0,0 +1,15 @@
package tree_sitter_django
// #cgo CFLAGS: -std=c11 -fPIC
// #include "../../src/parser.c"
// #if __has_include("../../src/scanner.c")
// #include "../../src/scanner.c"
// #endif
import "C"
import "unsafe"
// Get the tree-sitter Language for this grammar.
func Language() unsafe.Pointer {
return unsafe.Pointer(C.tree_sitter_django())
}
+15
View File
@@ -0,0 +1,15 @@
package tree_sitter_django_test
import (
"testing"
tree_sitter "github.com/tree-sitter/go-tree-sitter"
tree_sitter_django "github.com/danhanson/tree-sitter-django/bindings/go"
)
func TestCanLoadGrammar(t *testing.T) {
language := tree_sitter.NewLanguage(tree_sitter_django.Language())
if language == nil {
t.Errorf("Error loading Django Parser grammar")
}
}
+19
View File
@@ -0,0 +1,19 @@
#include <napi.h>
typedef struct TSLanguage TSLanguage;
extern "C" TSLanguage *tree_sitter_django();
// "tree-sitter", "language" hashed with BLAKE2
const napi_type_tag LANGUAGE_TYPE_TAG = {
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
};
Napi::Object Init(Napi::Env env, Napi::Object exports) {
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_django());
language.TypeTag(&LANGUAGE_TYPE_TAG);
exports["language"] = language;
return exports;
}
NODE_API_MODULE(tree_sitter_django_binding, Init)
+11
View File
@@ -0,0 +1,11 @@
import assert from "node:assert";
import { test } from "node:test";
import Parser from "tree-sitter";
test("can load grammar", () => {
const parser = new Parser();
assert.doesNotReject(async () => {
const { default: language } = await import("./index.js");
parser.setLanguage(language);
});
});
Generated Vendored
+60
View File
@@ -0,0 +1,60 @@
type BaseNode = {
type: string;
named: boolean;
};
type ChildNode = {
multiple: boolean;
required: boolean;
types: BaseNode[];
};
type NodeInfo =
| (BaseNode & {
subtypes: BaseNode[];
})
| (BaseNode & {
fields: { [name: string]: ChildNode };
children: ChildNode[];
});
/**
* The tree-sitter language object for this grammar.
*
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language}
*
* @example
* import Parser from "tree-sitter";
* import django from "tree-sitter-django";
*
* const parser = new Parser();
* parser.setLanguage(django);
*/
declare const binding: {
/**
* The inner language object.
* @private
*/
language: unknown;
/**
* The content of the `node-types.json` file for this grammar.
*
* @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types}
*/
nodeTypeInfo: NodeInfo[];
/** The syntax highlighting query for this grammar. */
HIGHLIGHTS_QUERY?: string;
/** The language injection query for this grammar. */
INJECTIONS_QUERY?: string;
/** The local variable query for this grammar. */
LOCALS_QUERY?: string;
/** The symbol tagging query for this grammar. */
TAGS_QUERY?: string;
};
export default binding;
+37
View File
@@ -0,0 +1,37 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL("../..", import.meta.url));
const binding = typeof process.versions.bun === "string"
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-django.node`)
: (await import("node-gyp-build")).default(root);
try {
const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } });
binding.nodeTypeInfo = nodeTypes.default;
} catch { }
const queries = [
["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`],
["INJECTIONS_QUERY", `${root}/queries/injections.scm`],
["LOCALS_QUERY", `${root}/queries/locals.scm`],
["TAGS_QUERY", `${root}/queries/tags.scm`],
];
for (const [prop, path] of queries) {
Object.defineProperty(binding, prop, {
configurable: true,
enumerable: true,
get() {
delete binding[prop];
try {
binding[prop] = readFileSync(path, "utf8");
} catch { }
return binding[prop];
}
});
}
export default binding;
+12
View File
@@ -0,0 +1,12 @@
from unittest import TestCase
from tree_sitter import Language, Parser
import tree_sitter_django
class TestLanguage(TestCase):
def test_can_load_grammar(self):
try:
Parser(Language(tree_sitter_django.language()))
except Exception:
self.fail("Error loading Django Parser grammar")
+43
View File
@@ -0,0 +1,43 @@
"""Django grammar for tree-sitter"""
from importlib.resources import files as _files
from ._binding import language
def _get_query(name, file):
try:
query = _files(f"{__package__}") / file
globals()[name] = query.read_text()
except FileNotFoundError:
globals()[name] = None
return globals()[name]
def __getattr__(name):
if name == "HIGHLIGHTS_QUERY":
return _get_query("HIGHLIGHTS_QUERY", "queries/highlights.scm")
if name == "INJECTIONS_QUERY":
return _get_query("INJECTIONS_QUERY", "queries/injections.scm")
if name == "LOCALS_QUERY":
return _get_query("LOCALS_QUERY", "queries/locals.scm")
if name == "TAGS_QUERY":
return _get_query("TAGS_QUERY", "queries/tags.scm")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"language",
"HIGHLIGHTS_QUERY",
"INJECTIONS_QUERY",
"LOCALS_QUERY",
"TAGS_QUERY",
]
def __dir__():
return sorted(__all__ + [
"__all__", "__builtins__", "__cached__", "__doc__", "__file__",
"__loader__", "__name__", "__package__", "__path__", "__spec__",
])
+17
View File
@@ -0,0 +1,17 @@
from typing import Final
from typing_extensions import CapsuleType
HIGHLIGHTS_QUERY: Final[str] | None
"""The syntax highlighting query for this grammar."""
INJECTIONS_QUERY: Final[str] | None
"""The language injection query for this grammar."""
LOCALS_QUERY: Final[str] | None
"""The local variable query for this grammar."""
TAGS_QUERY: Final[str] | None
"""The symbol tagging query for this grammar."""
def language() -> CapsuleType:
"""The tree-sitter language function for this grammar."""
+35
View File
@@ -0,0 +1,35 @@
#include <Python.h>
typedef struct TSLanguage TSLanguage;
TSLanguage *tree_sitter_django(void);
static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) {
return PyCapsule_New(tree_sitter_django(), "tree_sitter.Language", NULL);
}
static struct PyModuleDef_Slot slots[] = {
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
static PyMethodDef methods[] = {
{"language", _binding_language, METH_NOARGS,
"Get the tree-sitter language for this grammar."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "_binding",
.m_doc = NULL,
.m_size = 0,
.m_methods = methods,
.m_slots = slots,
};
PyMODINIT_FUNC PyInit__binding(void) {
return PyModuleDef_Init(&module);
}
View File
Whitespace-only changes.
+56
View File
@@ -0,0 +1,56 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.std("c11").include(src_dir);
#[cfg(target_env = "msvc")]
c_config.flag("-utf-8");
if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" {
let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else {
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate");
};
let Ok(wasm_src) =
std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from)
else {
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate");
};
c_config.include(&wasm_headers);
c_config.files([
wasm_src.join("stdio.c"),
wasm_src.join("stdlib.c"),
wasm_src.join("string.c"),
]);
}
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
let scanner_path = src_dir.join("scanner.c");
if scanner_path.exists() {
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
}
c_config.compile("tree-sitter-django");
println!("cargo:rustc-check-cfg=cfg(with_highlights_query)");
if !"queries/highlights.scm".is_empty() && std::path::Path::new("queries/highlights.scm").exists() {
println!("cargo:rustc-cfg=with_highlights_query");
}
println!("cargo:rustc-check-cfg=cfg(with_injections_query)");
if !"queries/injections.scm".is_empty() && std::path::Path::new("queries/injections.scm").exists() {
println!("cargo:rustc-cfg=with_injections_query");
}
println!("cargo:rustc-check-cfg=cfg(with_locals_query)");
if !"queries/locals.scm".is_empty() && std::path::Path::new("queries/locals.scm").exists() {
println!("cargo:rustc-cfg=with_locals_query");
}
println!("cargo:rustc-check-cfg=cfg(with_tags_query)");
if !"queries/tags.scm".is_empty() && std::path::Path::new("queries/tags.scm").exists() {
println!("cargo:rustc-cfg=with_tags_query");
}
}
+60
View File
@@ -0,0 +1,60 @@
//! This crate provides Django Parser language support for the [tree-sitter] parsing library.
//!
//! Typically, you will use the [`LANGUAGE`] constant to add this language to a
//! tree-sitter [`Parser`], and then use the parser to parse some code:
//!
//! ```
//! let code = r#"
//! "#;
//! let mut parser = tree_sitter::Parser::new();
//! let language = tree_sitter_django::LANGUAGE;
//! parser
//! .set_language(&language.into())
//! .expect("Error loading Django Parser parser");
//! let tree = parser.parse(code, None).unwrap();
//! assert!(!tree.root_node().has_error());
//! ```
//!
//! [`Parser`]: https://docs.rs/tree-sitter/0.26.12/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter_language::LanguageFn;
extern "C" {
fn tree_sitter_django() -> *const ();
}
/// The tree-sitter [`LanguageFn`] for this grammar.
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_django) };
/// The content of the [`node-types.json`] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
#[cfg(with_highlights_query)]
/// The syntax highlighting query for this grammar.
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
#[cfg(with_injections_query)]
/// The language injection query for this grammar.
pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
#[cfg(with_locals_query)]
/// The local variable query for this grammar.
pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
#[cfg(with_tags_query)]
/// The symbol tagging query for this grammar.
pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&super::LANGUAGE.into())
.expect("Error loading Django Parser parser");
}
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef TREE_SITTER_DJANGO_H_
#define TREE_SITTER_DJANGO_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_django(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_DJANGO_H_
@@ -0,0 +1,12 @@
import XCTest
import SwiftTreeSitter
import TreeSitterDjango
final class TreeSitterDjangoTests: XCTestCase {
func testCanLoadGrammar() throws {
let parser = Parser()
let language = Language(language: tree_sitter_django())
XCTAssertNoThrow(try parser.setLanguage(language),
"Error loading Django Parser grammar")
}
}
Generated
+7
View File
@@ -0,0 +1,7 @@
module github.com/danhanson/tree-sitter-django
go 1.22
require github.com/tree-sitter/go-tree-sitter v0.24.0
require github.com/mattn/go-pointer v0.0.1 // indirect
Generated
+36
View File
@@ -0,0 +1,36 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tree-sitter/go-tree-sitter v0.24.0 h1:kRZb6aBNfcI/u0Qh8XEt3zjNVnmxTisDBN+kXK0xRYQ=
github.com/tree-sitter/go-tree-sitter v0.24.0/go.mod h1:x681iFVoLMEwOSIHA1chaLkXlroXEN7WY+VHGFaoDbk=
github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb h1:A8425heRM8mylnv4H58FPUiH+aYivyitre0PzxrfmWs=
github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb/go.mod h1:dOF6gtQiF9UwNh995T5OphYmtIypkjsp3ap7r9AN/iA=
github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148 h1:AfFPZwtwGN01BW1jDdqBVqscTwetvMpydqYZz57RSlc=
github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148/go.mod h1:Bh6U3viD57rFXRYIQ+kmiYtr+1Bx0AceypDLJJSyi9s=
github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33 h1:TwqSV3qLp3tKSqirGLRHnjFk9Tc2oy57LIl+FQ4GjI4=
github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33/go.mod h1:CvCKCt3v04Ufos1zZnNCelBDeCGRpPucaN8QczoUsN4=
github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012 h1:Xvxck3tE5FW7F7bTS97iNM2ADMyCMJztVqn5HYKdJGo=
github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012/go.mod h1:T40D0O1cPvUU/+AmiXVXy1cncYQT6wem4Z0g4SfAYvY=
github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0 h1:c46K6uh5Dz00zJeU9BfjXdb8I+E4RkUdfnWJpQADXFo=
github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0/go.mod h1:hcNt/kOJHcIcuMvouE7LJcYdeFUFbVpBJ6d4wmOA+tU=
github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495 h1:jrt4qbJVEFs4H93/ITxygHc6u0TGqAkkate7TQ4wFSA=
github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495/go.mod h1:oyaR7fLnRV0hT9z6qwE9GkaeTom/hTDwK3H2idcOJFc=
github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5 h1:om4X9AVg3asL8gxNJDcz4e/Wp+VpQj1PY3uJXKr6EOg=
github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5/go.mod h1:nNqgPoV/h9uYWk6kYEFdEAhNVOacpfpRW5SFmdaP4tU=
github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5 h1:pfV3G3k7NCKqKk8THBmyuh2zA33lgYHS3GVrzRR8ry4=
github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5/go.mod h1:GbMKRjLfk0H+PI7nLi1Sx5lHf5wCpLz9al8tQYSxpEk=
github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1 h1:ZXZMDwE+IhUtGug4Brv6NjJWUU3rfkZBKpemf6RY8/g=
github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1/go.mod h1:UKCLuYnJ312Mei+3cyTmGOHzn0YAnaPRECgJmHtzrqs=
github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb h1:EXEM82lFM7JjJb6qiKZXkpIDaCcbV2obNn82ghwj9lw=
github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb/go.mod h1:lXCF1nGG5Dr4J3BTS0ObN4xJCCICiSu/b+Xe/VqMV7g=
github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d h1:fcYCvoXdcP1uRQYXqJHRy6Hec+uKScQdKVtMwK9JeCI=
github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d/go.mod h1:T1nShQ4v5AJtozZ8YyAS4uzUtDAJj/iv4YfwXSbUHzg=
github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447 h1:o9alBu1J/WjrcTKEthYtXmdkDc5OVXD+PqlvnEZ0Lzc=
github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447/go.mod h1:1Oh95COkkTn6Ezp0vcMbvfhRP5gLeqqljR0BYnBzWvc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+722 -223
View File
File diff suppressed because it is too large. Load diff
+340
View File
@@ -7,16 +7,29 @@
"": { "": {
"name": "tree-sitter-django", "name": "tree-sitter-django",
"version": "0.1.0", "version": "0.1.0",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"node-addon-api": "^8.3.1",
"node-gyp-build": "^4.8.4",
"web-tree-sitter": "^0.26.12" "web-tree-sitter": "^0.26.12"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^9", "eslint": "^9",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prebuildify": "^6.0.1",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"tree-sitter": "^0.25.1",
"tree-sitter-cli": "^0.26.12", "tree-sitter-cli": "^0.26.12",
"typescript": "^5" "typescript": "^5"
},
"peerDependencies": {
"tree-sitter": "^0.25.0"
},
"peerDependenciesMeta": {
"tree-sitter": {
"optional": true
}
} }
}, },
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
@@ -313,6 +326,39 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.18", "version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
@@ -324,6 +370,31 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -351,6 +422,13 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true,
"license": "ISC"
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -418,6 +496,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -673,6 +761,13 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true,
"license": "MIT"
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -709,6 +804,27 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -746,6 +862,13 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/is-extglob": { "node_modules/is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -880,6 +1003,23 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true,
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -894,6 +1034,62 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-abi": {
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
"integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "8.9.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz",
"integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/npm-run-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -977,6 +1173,24 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/prebuildify": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"mkdirp-classic": "^0.5.3",
"node-abi": "^3.3.0",
"npm-run-path": "^3.1.0",
"pump": "^3.0.0",
"tar-fs": "^2.1.0"
},
"bin": {
"prebuildify": "bin.js"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -1003,6 +1217,17 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -1013,6 +1238,21 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/resolve-from": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -1023,6 +1263,40 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -1046,6 +1320,16 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/strip-json-comments": { "node_modules/strip-json-comments": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -1072,6 +1356,48 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/tar-fs": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tree-sitter": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.25.1.tgz",
"integrity": "sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^8.5.0",
"node-gyp-build": "^4.8.4"
}
},
"node_modules/tree-sitter-cli": { "node_modules/tree-sitter-cli": {
"version": "0.26.12", "version": "0.26.12",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.26.12.tgz", "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.26.12.tgz",
@@ -1123,6 +1449,13 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/web-tree-sitter": { "node_modules/web-tree-sitter": {
"version": "0.26.12", "version": "0.26.12",
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.12.tgz", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.12.tgz",
@@ -1155,6 +1488,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
Generated
+52 -3
View File
@@ -1,9 +1,19 @@
{ {
"name": "tree-sitter-django", "name": "tree-sitter-django",
"version": "0.1.0", "version": "0.1.2",
"private": true,
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"exports": {
".": {
"types": "./bindings/node/index.d.ts",
"default": "./bindings/node/index.js"
},
"./grammar": "./grammar.js",
"./tree-sitter-django.wasm": "./tree-sitter-django.wasm",
"./src/scanner.c": "./src/scanner.c",
"./queries/*": "./queries/*",
"./package.json": "./package.json"
},
"scripts": { "scripts": {
"lint": "eslint", "lint": "eslint",
"lint:fix": "eslint --fix", "lint:fix": "eslint --fix",
@@ -14,16 +24,55 @@
"parser-build": "tree-sitter build --wasm", "parser-build": "tree-sitter build --wasm",
"highlight": "tree-sitter highlight", "highlight": "tree-sitter highlight",
"tag": "tree-sitter tag", "tag": "tree-sitter tag",
"playground": "tree-sitter playground" "playground": "tree-sitter playground",
"install": "node-gyp-build",
"prebuildify": "prebuildify --napi --strip --tag-libc",
"binding-test": "node --test bindings/node/*_test.js"
}, },
"dependencies": { "dependencies": {
"node-addon-api": "^8.3.1",
"node-gyp-build": "^4.8.4",
"web-tree-sitter": "^0.26.12" "web-tree-sitter": "^0.26.12"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^9", "eslint": "^9",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prebuildify": "^6.0.1",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"tree-sitter": "^0.25.1",
"tree-sitter-cli": "^0.26.12", "tree-sitter-cli": "^0.26.12",
"typescript": "^5" "typescript": "^5"
},
"description": "Django grammar for tree-sitter",
"repository": {
"type": "git",
"url": "git+https://github.com/danhanson/tree-sitter-django.git"
},
"author": "Daniel Hanson (https://github.com/danhanson)",
"keywords": [
"incremental",
"parsing",
"tree-sitter",
"django"
],
"main": "bindings/node",
"types": "bindings/node",
"files": [
"grammar.js",
"tree-sitter.json",
"binding.gyp",
"prebuilds/**",
"bindings/node/*",
"queries/*",
"src/**",
"*.wasm"
],
"peerDependencies": {
"tree-sitter": "^0.25.0"
},
"peerDependenciesMeta": {
"tree-sitter": {
"optional": true
}
} }
} }
+29
View File
@@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=62.4.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "tree-sitter-django"
description = "Django grammar for tree-sitter"
version = "0.1.0"
keywords = ["incremental", "parsing", "tree-sitter", "django"]
classifiers = [
"Intended Audience :: Developers",
"Topic :: Software Development :: Compilers",
"Topic :: Text Processing :: Linguistic",
"Typing :: Typed",
]
authors = [{ name = "Daniel Hanson" }]
requires-python = ">=3.10"
license.text = "MIT"
readme = "README.md"
[project.urls]
Homepage = "https://github.com/danhanson/tree-sitter-django"
[project.optional-dependencies]
core = ["tree-sitter~=0.24"]
[tool.cibuildwheel]
build = "cp310-*"
build-frontend = "build"
+76
View File
@@ -0,0 +1,76 @@
; 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.
+47
View File
@@ -0,0 +1,47 @@
; Everything in a template that an editor should report as a problem.
;
; A group whose required tag is missing parses without an ERROR: the scanner
; supplies a zero-width marker where the tag belongs, so the group, its blocks
; and the scopes they open stay intact for completion and the other queries.
; Such a tree has no error of its own (ts_node_has_error is false), so a tool
; has to run this query to find them.
;
; @error.syntax input the grammar could not parse
; @error.missing a token that tree-sitter's error recovery inserted
; @error.missing_tag expected tag block is missing
;
; The markers are listed one by one, as MISSING_TAGS in grammar.js: the
; generator drops a supertype whose members are aliases, so there is no single
; node type to match them all. A new tag group, or a new kind of marker, is
; added here.
(ERROR) @error.syntax
(MISSING) @error.missing
[
(missing_endautoescape_tag)
(missing_endblock_tag)
(missing_endblocktrans_tag)
(missing_endblocktranslate_tag)
(missing_endcache_tag)
(missing_endcomment_tag)
(missing_endfilter_tag)
(missing_endfor_tag)
(missing_endif_tag)
(missing_endifchanged_tag)
(missing_endlanguage_tag)
(missing_endlocalize_tag)
(missing_endlocaltime_tag)
(missing_endpartialdef_tag)
(missing_endspaceless_tag)
(missing_endtimezone_tag)
(missing_endverbatim_tag)
(missing_endwith_tag)
(missing_plural_tag)] @error.missing_tag
(unexpected_tag) @error.unexpected_tag
(unexpected_argument) @error.unexpected_argument
(missing_variable_close) @error.missing_variable_close
+30
View File
@@ -0,0 +1,30 @@
; Names a tag binds in the scope *around* it rather than in the scope it opens.
;
; queries/locals.scm cannot express this: a locals query places a definition in
; the innermost @local.scope whose span contains it, and there is no way to let
; one out again. A tool reads this file alongside locals.scm and binds
; @export.name in the nearest scope enclosing @export.from, not inside it.
;
; Only blocktranslate does this. Its asvar target is written inside the opening
; tag — which is inside the scope, because the "with" and "count" bindings in
; that same tag do belong to the block — but it holds the rendered text and is
; read after the block:
;
; {% blocktranslate with bar=foo asvar var %}…{% endblocktranslate %}
; {{ var }}
;
; It carries an "asvar" field rather than "variable" so locals.scm leaves it
; alone. Every other "as name" clause is written in a tag that opens no scope,
; so locals.scm already places those correctly and none of them belong here.
;
; asvar and the "with" bindings may be written in either order, so no single
; scope span could have separated them; that is why this is a separate file
; rather than a narrower scope in locals.scm.
(blocktrans_block
(blocktrans_tag
asvar: (identifier) @export.name)) @export.from
(blocktranslate_block
(blocktranslate_tag
asvar: (identifier) @export.name)) @export.from
+10 -8
View File
@@ -4,13 +4,13 @@
variable: (identifier) @variable.parameter variable: (identifier) @variable.parameter
(block_group (block_tag
(push_block) @type) (push_block) @type)
(partialdef_group (partialdef_tag
(push_partial) @type) (push_partial) @type)
(verbatim_group (verbatim_tag
(push_verbatim) @type) (push_verbatim) @type)
(comment_content) @comment (comment_content) @comment
@@ -41,11 +41,13 @@ variable: (identifier) @variable.parameter
(number) @number (number) @number
(string) @string (string) @string
(boolean) @constant.builtin
(none) @constant.builtin
(library (library
(identifier) @module) (identifier) @module)
tag: [ tag_name: [
("autoescape") ("autoescape")
("endautoescape") ("endautoescape")
("block") ("block")
@@ -118,7 +120,7 @@ tag: [
("endwith")] @function ("endwith")] @function
(filter (filter
name: [ filter_name: [
("add") ("add")
("addslashes") ("addslashes")
("capfirst") ("capfirst")
@@ -188,10 +190,10 @@ tag: [
; a filter or tag a "load" brought in, whose name the grammar does not know ; a filter or tag a "load" brought in, whose name the grammar does not know
(filter (filter
name: (identifier) @function.call) filter_name: (identifier) @function.call)
(custom_tag (custom_tag
tag: (identifier) @function.call) tag_name: (identifier) @function.call)
(load (load_tag
(identifier) @function) (identifier) @function)
+40 -39
View File
@@ -15,80 +15,81 @@
; heads, so that the capture is both the name to look up and the range to ; heads, so that the capture is both the name to look up and the range to
; report. A paired tag names the opening tag only. ; report. A paired tag names the opening tag only.
(static (static_tag
tag: ("static") @library.static) tag_name: ("static") @library.static)
(get_static_prefix (get_static_prefix_tag
tag: ("get_static_prefix") @library.static) tag_name: ("get_static_prefix") @library.static)
(get_media_prefix (get_media_prefix_tag
tag: ("get_media_prefix") @library.static) tag_name: ("get_media_prefix") @library.static)
(blocktranslate_group (blocktrans_tag
tag: [ tag_name: ("blocktrans") @library.i18n)
("blocktrans")
("blocktranslate")] @library.i18n)
(translate (blocktranslate_tag
tag: [ tag_name: ("blocktranslate") @library.i18n)
(translate_tag
tag_name: [
("trans") ("trans")
("translate")] @library.i18n) ("translate")] @library.i18n)
(language_group (language_tag
tag: ("language") @library.i18n) tag_name: ("language") @library.i18n)
(get_available_languages (get_available_languages_tag
tag: ("get_available_languages") @library.i18n) tag_name: ("get_available_languages") @library.i18n)
(get_current_language (get_current_language_tag
tag: ("get_current_language") @library.i18n) tag_name: ("get_current_language") @library.i18n)
(get_current_language_bidi (get_current_language_bidi_tag
tag: ("get_current_language_bidi") @library.i18n) tag_name: ("get_current_language_bidi") @library.i18n)
(get_language_info (get_language_info_tag
tag: ("get_language_info") @library.i18n) tag_name: ("get_language_info") @library.i18n)
(get_language_info_list (get_language_info_list_tag
tag: ("get_language_info_list") @library.i18n) tag_name: ("get_language_info_list") @library.i18n)
(filter (filter
name: [ filter_name: [
("language_bidi") ("language_bidi")
("language_name") ("language_name")
("language_name_local") ("language_name_local")
("language_name_translated")] @library.i18n) ("language_name_translated")] @library.i18n)
(cache_group (cache_tag
tag: ("cache") @library.cache) tag_name: ("cache") @library.cache)
(localize_group (localize_tag
tag: ("localize") @library.l10n) tag_name: ("localize") @library.l10n)
(filter (filter
name: [ filter_name: [
("localize") ("localize")
("unlocalize")] @library.l10n) ("unlocalize")] @library.l10n)
(localtime_group (localtime_tag
tag: ("localtime") @library.tz) tag_name: ("localtime") @library.tz)
(timezone_group (timezone_tag
tag: ("timezone") @library.tz) tag_name: ("timezone") @library.tz)
(get_current_timezone (get_current_timezone_tag
tag: ("get_current_timezone") @library.tz) tag_name: ("get_current_timezone") @library.tz)
(filter (filter
name: [ filter_name: [
("localtime") ("localtime")
("timezone") ("timezone")
("utc")] @library.tz) ("utc")] @library.tz)
; "{% load static %}" makes every name in the library available; "{% load ; "{% load static %}" makes every name in the library available; "{% load
; static from staticfiles %}" makes only the names listed available. ; static from staticfiles %}" makes only the names listed available.
(load (load_tag
(library) @load.library) (library) @load.library)
(load (load_tag
(identifier) @load.name) (identifier) @load.name)
+22 -2
View File
@@ -3,5 +3,25 @@
variable: (identifier) @local.definition variable: (identifier) @local.definition
(with_group) @local.scope ; The tags whose Node pushes a context, so that what they bind is not visible
(for_scope) @local.scope ; after them. Verified by rendering: "{% block b %}{% now "Y" as n %}{% endblock %}{{ n }}"
; is empty, while the same around an "{% if %}" is not, because if does not push.
(with_block) @local.scope
(for_block) @local.scope
(block_block) @local.scope
(partialdef_block) @local.scope
; ForNode renders nodelist_empty inside the same context.push() as the loop
; body, so "{% empty %}" is part of the loop's scope even though it is a
; separate block: "{% for x in ys %}a{% empty %}{% now "Y" as n %}{% endfor %}{{ n }}"
; is empty. queries/conditionals.scm names both blocks as branches of the
; same group and relies on this.
(empty_block) @local.scope
; blocktranslate pushes a context for its "with" and "count" bindings. Its
; asvar target is not one of them: it is written in the same tag but assigned
; outside it, and carries an "asvar" field rather than "variable" so that this
; scope does not swallow it. A tool that wants it must define it in the scope
; around the block, which a locals query has no way to say.
(blocktrans_block) @local.scope
(blocktranslate_block) @local.scope
+14 -9
View File
@@ -1,15 +1,20 @@
(load (load_tag
(identifier) @name) @definition.function (identifier) @name) @definition.function
(partialdef_group (push_partial) @name) @definition.method (partialdef_tag (push_partial) @name) @definition.method
(partial (identifier) @name) @reference.call (partial_tag (identifier) @name) @reference.call
(template_block_groups ; a tag that opens no body names itself; a group is named by the first tag of
[ ; its first block, so neither its end tag nor the tags opening its later blocks
; are references of their own
(template_tag
tag_name: _ @name) @reference.call
(template_tag
.
(_ (_
tag: _ @name) @reference.call .
(for_group (_
(for_scope tag_name: _ @name))) @reference.call
tag: "for" @reference.call))])
(filter) @reference.call (filter) @reference.call
Generated
+78
View File
@@ -0,0 +1,78 @@
from os import path
from sysconfig import get_config_var
from setuptools import Extension, find_packages, setup
from setuptools.command.build import build
from setuptools.command.build_ext import build_ext
from setuptools.command.egg_info import egg_info
from wheel.bdist_wheel import bdist_wheel
class Build(build):
def run(self):
if path.isdir("queries"):
dest = path.join(self.build_lib, "tree_sitter_django", "queries")
self.copy_tree("queries", dest)
super().run()
class BuildExt(build_ext):
def build_extension(self, ext: Extension):
if self.compiler.compiler_type != "msvc":
ext.extra_compile_args = ["-std=c11", "-fvisibility=hidden"]
else:
ext.extra_compile_args = ["/std:c11", "/utf-8"]
if path.exists("src/scanner.c"):
ext.sources.append("src/scanner.c")
if ext.py_limited_api:
ext.define_macros.append(("Py_LIMITED_API", "0x030A0000"))
super().build_extension(ext)
class BdistWheel(bdist_wheel):
def get_tag(self):
python, abi, platform = super().get_tag()
if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"):
python, abi = "cp310", "abi3"
return python, abi, platform
class EggInfo(egg_info):
def find_sources(self):
super().find_sources()
self.filelist.recursive_include("queries", "*.scm")
self.filelist.include("src/tree_sitter/*.h")
self.filelist.include("src/*.c")
setup(
packages=find_packages("bindings/python"),
package_dir={"": "bindings/python"},
package_data={
"tree_sitter_django": ["*.pyi", "py.typed"],
"tree_sitter_django.queries": ["*.scm"],
},
ext_package="tree_sitter_django",
ext_modules=[
Extension(
name="_binding",
sources=[
"bindings/python/tree_sitter_django/binding.c",
"src/parser.c",
],
define_macros=[
("PY_SSIZE_T_CLEAN", None),
("TREE_SITTER_HIDE_SYMBOLS", None),
],
include_dirs=["src"],
py_limited_api=not get_config_var("Py_GIL_DISABLED"),
)
],
cmdclass={
"build": Build,
"build_ext": BuildExt,
"bdist_wheel": BdistWheel,
"egg_info": EggInfo,
},
zip_safe=False
)
+3072 -2926
View File
File diff suppressed because it is too large. Load diff
+2325 -547
View File
File diff suppressed because it is too large. Load diff
Generated
+55440 -105586
View File
File diff suppressed because it is too large. Load diff
+664 -302
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -17,6 +17,9 @@ Autoescape
(template (template
(content) (content)
(autoescape_group (autoescape_group
(autoescape_block
(autoescape_tag)
(template (template
(content))) (content)))
(endautoescape_tag))
(content)) (content))
+48 -41
View File
@@ -17,10 +17,13 @@ Block
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content)) (content)))
(pop_block)) (endblock_tag
(pop_block)))
(content)) (content))
================== ==================
@@ -42,10 +45,13 @@ Block Matching End Tag
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content)) (content)))
(pop_block)) (endblock_tag
(pop_block)))
(content)) (content))
================== ==================
@@ -69,33 +75,22 @@ Nested Blocks
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content)) (content)))
(pop_block)) (endblock_tag
(content)) (pop_block)))
(pop_block)) (content)))
(content)) (endblock_tag
(pop_block)))
---
(template
(content)
(block_group
(push_block)
(template
(content)
(block_group
(push_block)
(template
(content))
(pop_block))
(content))
(pop_block))
(content)) (content))
================== ==================
@@ -119,16 +114,22 @@ Nested Blocks End Tag
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content)) (content)))
(pop_block)) (endblock_tag
(content)) (pop_block)))
(pop_block)) (content)))
(endblock_tag
(pop_block)))
(content)) (content))
================== ==================
@@ -152,14 +153,20 @@ Nested Blocks Mixed Tags
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content) (content)
(block_group (block_group
(push_block) (block_block
(block_tag
name: (push_block))
(template (template
(content)) (content)))
(pop_block)) (endblock_tag
(content)) (pop_block)))
(pop_block)) (content)))
(endblock_tag
(pop_block)))
(content)) (content))
+279 -21
View File
@@ -9,7 +9,10 @@ Blocktrans
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag)
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -23,7 +26,10 @@ Blocktranslate
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktranslate_block
(blocktranslate_tag)
(content)) (content))
(endblocktranslate_tag))
(content)) (content))
================== ==================
@@ -36,7 +42,10 @@ Blocktrans Empty
(template (template
(content) (content)
(blocktranslate_group) (blocktranslate_group
(blocktrans_block
(blocktrans_tag))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -50,12 +59,15 @@ Blocktrans With A Placeholder
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag)
(content) (content)
(template_variable (template_variable
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))))) (identifier))))))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -69,7 +81,10 @@ Blocktrans Trimmed
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag)
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -83,12 +98,15 @@ Blocktrans With
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -102,6 +120,8 @@ Blocktrans With Several Bindings
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
@@ -111,8 +131,9 @@ Blocktrans With Several Bindings
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -126,14 +147,17 @@ Blocktrans With A Filtered Binding
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter))) (filter))))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -147,11 +171,14 @@ Blocktrans With Context
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(string)))) (string)))))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -165,8 +192,11 @@ Blocktrans With Asvar
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
variable: (identifier) (blocktrans_block
(blocktrans_tag
asvar: (identifier))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -180,13 +210,66 @@ Blocktrans With Count And Plural
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(content) (content)
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content)) (content))
==================
Blocktrans With An Empty Singular
==================
{% blocktrans count n=x %}{% plural %}many{% endblocktrans %}
---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content))
==================
Blocktranslate With An Empty Plural
==================
{% blocktranslate count n=x %}one{% plural %}{% endblocktranslate %}
---
(template
(content)
(blocktranslate_group
(blocktranslate_block
(blocktranslate_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(plural_block
(plural_tag)))
(endblocktranslate_tag))
(content)) (content))
================== ==================
@@ -200,6 +283,8 @@ Blocktrans With Every Option
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
@@ -214,9 +299,12 @@ Blocktrans With Every Option
(value (value
(literal (literal
(string)))) (string))))
variable: (identifier) asvar: (identifier))
(content) (content)
(content)) (plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -230,8 +318,11 @@ Blocktrans With Its Options Reordered
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
variable: (identifier) (blocktrans_block
(blocktrans_tag
asvar: (identifier))
(content)) (content))
(endblocktrans_tag))
(content)) (content))
================== ==================
@@ -256,13 +347,26 @@ Blocktranslate Closed By The Other Spelling
================== ==================
Blocktrans With Count And No Plural Blocktrans With Count And No Plural
:error
================== ==================
{% blocktrans count n=x %}one{% endblocktrans %} {% blocktrans count n=x %}one{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(missing_plural_tag))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Plural And No Count Blocktrans With Plural And No Count
@@ -276,13 +380,29 @@ Blocktrans With Plural And No Count
================== ==================
Blocktrans Counting Two Bindings Blocktrans Counting Two Bindings
:error
================== ==================
{% blocktrans count n=x c=d %}one{% plural %}many{% endblocktrans %} {% blocktrans count n=x c=d %}one{% plural %}many{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content)
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Nothing To Bind Blocktrans With Nothing To Bind
@@ -296,13 +416,21 @@ Blocktrans With Nothing To Bind
================== ==================
Blocktrans With An Unknown Option Blocktrans With An Unknown Option
:error
================== ==================
{% blocktrans bogus %}hi{% endblocktrans %} {% blocktrans bogus %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Asvar And No Name Blocktrans With Asvar And No Name
@@ -346,82 +474,183 @@ 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_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(plural_block
(plural_tag)
(content)))
(missing_endblocktrans_tag))
(unexpected_tag)
(content)
(unexpected_tag)
(content))
================== ==================
Blocktrans With An Argument On Plural Blocktrans With An Argument On Plural
:error
================== ==================
{% blocktrans count n=x %}one{% plural extra %}many{% endblocktrans %} {% blocktrans count n=x %}one{% plural extra %}many{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(plural_block
(plural_tag
(unexpected_argument))
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Trimmed Twice Blocktrans With Trimmed Twice
:error
================== ==================
{% blocktrans trimmed trimmed %}hi{% endblocktrans %} {% blocktrans trimmed trimmed %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With With Twice Blocktrans With With Twice
:error
================== ==================
{% blocktrans with a=b with c=d %}hi{% endblocktrans %} {% blocktrans with a=b with c=d %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Context Twice Blocktrans With Context Twice
:error
================== ==================
{% blocktrans context "a" context "b" %}hi{% endblocktrans %} {% blocktrans context "a" context "b" %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Asvar Twice Blocktrans With Asvar Twice
:error
================== ==================
{% blocktrans asvar x asvar y %}hi{% endblocktrans %} {% blocktrans asvar x asvar y %}hi{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(unexpected_argument))
(content))
(endblocktrans_tag))
(content))
================== ==================
Blocktrans With Count Twice Blocktrans With Count Twice
:error
================== ==================
{% blocktrans count n=x count m=y %}a{% plural %}b{% endblocktrans %} {% blocktrans count n=x count m=y %}a{% plural %}b{% endblocktrans %}
--- ---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(content)
(plural_block
(plural_tag)
(content)))
(endblocktrans_tag))
(content))
================== ==================
Blocktranslate With Trimmed Twice Blocktranslate With Trimmed Twice
:error
================== ==================
{% blocktranslate trimmed trimmed %}hi{% endblocktranslate %} {% blocktranslate trimmed trimmed %}hi{% endblocktranslate %}
--- ---
(template
(content)
(blocktranslate_group
(blocktranslate_block
(blocktranslate_tag
(unexpected_argument))
(content))
(endblocktranslate_tag))
(content))
================== ==================
Blocktrans Repeating An Option Of The Tag Before It Blocktrans Repeating An Option Of The Tag Before It
@@ -434,7 +663,36 @@ Blocktrans Repeating An Option Of The Tag Before It
(template (template
(content) (content)
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag)
(content)) (content))
(endblocktrans_tag))
(blocktranslate_group (blocktranslate_group
(blocktrans_block
(blocktrans_tag)
(content)) (content))
(endblocktrans_tag))
(content))
==================
Blocktrans With A Binding Twice
==================
{% blocktrans with a=1 a=2 %}x{% endblocktrans %}
---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
(endblocktrans_tag))
(content)) (content))
+52 -9
View File
@@ -9,13 +9,16 @@ Cache
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(number)))) (number))))
(identifier) (identifier))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -29,13 +32,16 @@ Cache With A Variable Timeout
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier))))
(identifier) (identifier))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -49,6 +55,8 @@ Cache With A Filtered Timeout
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -58,9 +66,10 @@ Cache With A Filtered Timeout
argument: (value argument: (value
(literal (literal
(number)))))) (number))))))
(identifier) (identifier))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -74,13 +83,16 @@ Cache With A Quoted Fragment Name
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(number)))) (number))))
(string) (string))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -94,6 +106,8 @@ Cache Varying On Arguments
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -106,9 +120,10 @@ Cache Varying On Arguments
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -122,6 +137,8 @@ Cache Varying On A Filtered Argument
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -132,9 +149,10 @@ Cache Varying On A Filtered Argument
(variable_attribute (variable_attribute
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter))) (filter))))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -148,6 +166,8 @@ Cache Using A Named Cache
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -156,9 +176,10 @@ Cache Using A Named Cache
(filtered_value (filtered_value
(value (value
(literal (literal
(string)))) (string)))))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -172,6 +193,8 @@ Cache Using An Unquoted Cache Name
(template (template
(content) (content)
(cache_group (cache_group
(cache_block
(cache_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -180,9 +203,10 @@ Cache Using An Unquoted Cache Name
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content))) (content)))
(endcache_tag))
(content)) (content))
================== ==================
@@ -197,13 +221,31 @@ Cache Without A Fragment Name
================== ==================
Cache With Anything After Using Cache With Anything After Using
:error
================== ==================
{% cache 500 sidebar using="c" extra %}x{% endcache %} {% cache 500 sidebar using="c" extra %}x{% endcache %}
--- ---
(template
(content)
(cache_group
(cache_block
(cache_tag
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endcache_tag))
(content))
================== ==================
Cache With An Empty Using Cache With An Empty Using
@@ -233,3 +275,4 @@ Cache With Using As Its Fragment Name
{% cache 500 using="c" %}x{% endcache %} {% cache 500 using="c" %}x{% endcache %}
--- ---
+14 -2
View File
@@ -11,7 +11,10 @@ This is ignored
(template (template
(content) (content)
(comment_group (comment_group
(comment_block
(comment_tag)
(comment_content)) (comment_content))
(endcomment_tag))
(content)) (content))
================== ==================
@@ -24,7 +27,10 @@ Comment Empty
(template (template
(content) (content)
(comment_group) (comment_group
(comment_block
(comment_tag))
(endcomment_tag))
(content)) (content))
================== ==================
@@ -40,8 +46,11 @@ This is ignored
(template (template
(content) (content)
(comment_group (comment_group
(string) (comment_block
(comment_tag
(string))
(comment_content)) (comment_content))
(endcomment_tag))
(content)) (content))
================== ==================
@@ -57,5 +66,8 @@ Contains {% if x %} tag-like text
(template (template
(content) (content)
(comment_group (comment_group
(comment_block
(comment_tag)
(comment_content)) (comment_content))
(endcomment_tag))
(content)) (content))
+43 -9
View File
@@ -8,7 +8,7 @@ Csp Nonce Attr
(template (template
(content) (content)
(csp_nonce_attr) (csp_nonce_attr_tag)
(content)) (content))
================== ==================
@@ -21,7 +21,7 @@ Csp Nonce Attr With Argument And As
(template (template
(content) (content)
(csp_nonce_attr (csp_nonce_attr_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -39,7 +39,7 @@ Csp Nonce Attr With Media Keyword
(template (template
(content) (content)
(csp_nonce_attr (csp_nonce_attr_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -48,34 +48,68 @@ Csp Nonce Attr With Media Keyword
================== ==================
Csp Nonce Attr Takes One Argument Csp Nonce Attr Takes One Argument
:error
================== ==================
{% csp_nonce_attr "a" "b" %} {% csp_nonce_attr "a" "b" %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Csp Nonce Attr Rejects Unknown Keyword Csp Nonce Attr Rejects Unknown Keyword
:error
================== ==================
{% csp_nonce_attr other="b" %} {% csp_nonce_attr other="b" %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(unexpected_argument))
(content))
================== ==================
Csp Nonce Attr Rejects Repeated Keyword Csp Nonce Attr Rejects Repeated Keyword
:error
================== ==================
{% csp_nonce_attr media=1 media=2 %} {% csp_nonce_attr media=1 media=2 %}
--- ---
(ERROR) (template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
==================
Csp Nonce Attr With Media Twice
==================
{% csp_nonce_attr media="a" media="b" %}
---
(template
(content)
(csp_nonce_attr_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
+1 -1
View File
@@ -10,5 +10,5 @@ Csrf Token
(template (template
(content) (content)
(csrf_token) (csrf_token_tag)
(content)) (content))
+36 -19
View File
@@ -9,7 +9,7 @@ Custom Tag
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier)) tag_name: (identifier))
(content)) (content))
================== ==================
@@ -23,7 +23,7 @@ Custom Tag With Arguments
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -49,7 +49,7 @@ Custom Tag With Keyword Arguments
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -72,7 +72,7 @@ Custom Tag With As
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -91,7 +91,7 @@ Custom Tag With Filtered Argument
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -111,10 +111,10 @@ Custom Tag Does Not Nest
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier)) tag_name: (identifier))
(content) (content)
(custom_tag (custom_tag
tag: (identifier)) tag_name: (identifier))
(content)) (content))
================== ==================
@@ -144,7 +144,7 @@ Custom Filter
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
name: (identifier))))) filter_name: (identifier)))))
(content)) (content))
================== ==================
@@ -164,7 +164,7 @@ Custom Filter With Argument
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
name: (identifier) filter_name: (identifier)
argument: (value argument: (value
(literal (literal
(string))))))) (string)))))))
@@ -187,7 +187,7 @@ Custom Filter In A Chain With A Builtin
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
name: (identifier)) filter_name: (identifier))
(filter)))) (filter))))
(content)) (content))
@@ -201,14 +201,14 @@ Custom Filter In A Tag
(template (template
(content) (content)
(firstof (firstof_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
name: (identifier))))) filter_name: (identifier)))))
(content)) (content))
================== ==================
@@ -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_tag)
(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_tag)
(content))
================== ==================
A Name Reserved For A Tag Is Still A Variable A Name Reserved For A Tag Is Still A Variable
@@ -278,7 +285,7 @@ A Name Reserved For A Tag Is Still An Argument
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -287,13 +294,23 @@ A Name Reserved For A Tag Is Still An Argument
================== ==================
Custom Tag With A Keyword Argument Twice Custom Tag With A Keyword Argument Twice
:error
================== ==================
{% mytag a=1 a=2 %} {% mytag a=1 a=2 %}
--- ---
(template
(content)
(custom_tag
(identifier)
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Custom Tag With Keyword Arguments And As Custom Tag With Keyword Arguments And As
@@ -306,7 +323,7 @@ Custom Tag With Keyword Arguments And As
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(identifier) (identifier)
(filtered_value (filtered_value
(value (value
@@ -331,14 +348,14 @@ Custom Tag Repeating A Keyword Of The Tag Before It
(template (template
(content) (content)
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(identifier) (identifier)
(filtered_value (filtered_value
(value (value
(literal (literal
(number))))) (number)))))
(custom_tag (custom_tag
tag: (identifier) tag_name: (identifier)
(identifier) (identifier)
(filtered_value (filtered_value
(value (value
+5 -5
View File
@@ -11,7 +11,7 @@ Cycle Basic
(template (template
(content) (content)
(cycle (cycle_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -35,7 +35,7 @@ Cycle With As
(template (template
(content) (content)
(cycle (cycle_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -60,7 +60,7 @@ Cycle With As And Silent
(template (template
(content) (content)
(cycle (cycle_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -85,7 +85,7 @@ Cycle Referencing Existing Variable
(template (template
(content) (content)
(cycle (cycle_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -136,7 +136,7 @@ Cycle Spanning Lines
(template (template
(content) (content)
(cycle (cycle_tag
(filtered_value (filtered_value
(value (value
(literal (literal
+1 -1
View File
@@ -10,5 +10,5 @@ Debug
(template (template
(content) (content)
(debug) (debug_tag)
(content)) (content))
+2 -2
View File
@@ -9,7 +9,7 @@ Extends With String
(template (template
(content) (content)
(extends (extends_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -27,7 +27,7 @@ Extends With Variable
(template (template
(content) (content)
(extends (extends_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
+12 -4
View File
@@ -13,10 +13,13 @@ hello
(template (template
(content) (content)
(filter_group (filter_group
(filter_block
(filter_tag
(filter_expression (filter_expression
(filter)) (filter)))
(template (template
(content))) (content)))
(endfilter_tag))
(content)) (content))
================== ==================
@@ -34,11 +37,14 @@ HELLO WORLD
(template (template
(content) (content)
(filter_group (filter_group
(filter_block
(filter_tag
(filter_expression (filter_expression
(filter) (filter)
(filter)) (filter)))
(template (template
(content))) (content)))
(endfilter_tag))
(content)) (content))
================== ==================
@@ -57,10 +63,12 @@ HELLO WORLD
(template (template
(content) (content)
(filter_group (filter_group
(filter_block
(filter_tag
(filter_expression (filter_expression
(filter) (filter)
(filter)) (filter)))
(template (template
(content))) (content)))
(endfilter_tag))
(content)) (content))
+2 -2
View File
@@ -8,7 +8,7 @@ Firstof Basic
(template (template
(content) (content)
(firstof (firstof_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -33,7 +33,7 @@ Firstof With As
(template (template
(content) (content)
(firstof (firstof_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
+748 -8
View File
@@ -13,12 +13,13 @@ For Basic
(template (template
(content) (content)
(for_group (for_group
(for_scope (for_block
(for_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content) (content)
(template_variable (template_variable
@@ -26,7 +27,8 @@ For Basic
(value (value
(variable_attribute (variable_attribute
(identifier))))) (identifier)))))
(content)))) (content)))
(endfor_tag))
(content)) (content))
================== ==================
@@ -44,13 +46,14 @@ For With Multiple Variables
(template (template
(content) (content)
(for_group (for_group
(for_scope (for_block
(for_tag
variable: (identifier) variable: (identifier)
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content) (content)
(template_variable (template_variable
@@ -58,7 +61,8 @@ For With Multiple Variables
(value (value
(variable_attribute (variable_attribute
(identifier))))) (identifier)))))
(content)))) (content)))
(endfor_tag))
(content)) (content))
================== ==================
@@ -78,12 +82,13 @@ For With Empty
(template (template
(content) (content)
(for_group (for_group
(for_scope (for_block
(for_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content) (content)
(template_variable (template_variable
@@ -92,6 +97,741 @@ For With Empty
(variable_attribute (variable_attribute
(identifier))))) (identifier)))))
(content))) (content)))
(empty_block
(empty_tag)
(template (template
(content))) (content)))
(endfor_tag))
(content))
==================
For Does Not Take An Else
==================
{% for a in b %}x{% else %}y{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(unexpected_tag)
(content)))
(endfor_tag))
(content))
==================
For With Empty Twice
==================
{% for a in b %}x{% empty %}y{% empty %}z{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)
(unexpected_tag)
(content)))
(endfor_tag))
(content))
==================
For With An Empty Clause And No Body
==================
{% for a in b %}{% empty %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))))
(empty_block
(empty_tag))
(endfor_tag))
(content))
==================
For With A Nested If In Its Empty Clause
==================
{% for a in b %}x{% empty %}{% if q %}y{% endif %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))))
(endfor_tag))
(content))
==================
For With A Tag In Its Body
==================
{% for x in y %}
{% if a %}b{% endif %}
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content)))
(endfor_tag))
(content))
==================
For With A Nested For
==================
{% for x in y %}
{% for z in x %}a{% endfor %}
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(endfor_tag))
(content)))
(endfor_tag))
(content))
==================
For With A Nested With
==================
{% for a in b %}{% with c=1 %}y{% endwith %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(endwith_tag))))
(endfor_tag))
(content))
==================
For With A Cycle In Its Body
==================
{% for a in b %}{% cycle 1 2 %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(cycle_tag
(filtered_value
(value
(literal
(number))))
(filtered_value
(value
(literal
(number)))))))
(endfor_tag))
(content))
==================
For With Multiple Variables And A Nested If
==================
{% for a, b in c %}{% if x %}y{% endif %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))))
(endfor_tag))
(content))
==================
For With Tags In Both Branches
==================
{% for x in y %}
{% if a %}b{% endif %}
{% empty %}
{% with c=1 %}d{% endwith %}
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content)))
(empty_block
(empty_tag)
(template
(content)
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(endwith_tag))
(content)))
(endfor_tag))
(content))
==================
For With A Named Block In Its Body
==================
{% for x in y %}
{% block b %}c{% endblock b %}
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(block_group
(block_block
(block_tag
name: (push_block))
(template
(content)))
(endblock_tag
(pop_block)))
(content)))
(endfor_tag))
(content))
==================
For Nested In A Tag
==================
{% if a %}
{% for x in y %}{% if b %}c{% endif %}{% endfor %}
{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))))
(endfor_tag))
(content)))
(endif_tag))
(content))
==================
For With An Empty Body
==================
{% for x in y %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier))))))
(endfor_tag))
(content))
==================
For Reversed
==================
{% for x in y reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(endfor_tag))
(content))
==================
For With Multiple Variables Reversed
==================
{% for k,v in y reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(endfor_tag))
(content))
==================
For With A Filtered Sequence Reversed
==================
{% for x in y|slice:":2" reversed %}
a
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter
argument: (value
(literal
(string)))))))
(template
(content)))
(endfor_tag))
(content))
==================
For Reversed With Empty
==================
{% for x in y reversed %}
a
{% empty %}
b
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)))
(endfor_tag))
(content))
==================
For Reversed With A Tag In Its Body
==================
{% for x in y reversed %}
{% if a %}b{% endif %}
{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content)))
(endfor_tag))
(content))
==================
For With Reversed Twice
==================
{% for x in y reversed reversed %}a{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(template
(content)))
(endfor_tag))
(content))
==================
For With Reversed Before In
:error
==================
{% for x reversed in y %}a{% endfor %}
---
==================
For Over A Variable Named Reversed
==================
{% for x in reversed reversed %}a{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(endfor_tag))
(content))
==================
For Over An Attribute Of A Variable Named Reversed
==================
{% for x in reversed.items %}a{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)
(attribute)))))
(template
(content)))
(endfor_tag))
(content))
==================
For Over A Filtered Variable Named Reversed
==================
{% for x in reversed|last %}a{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
variable: (identifier)
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter))))
(template
(content)))
(endfor_tag))
(content))
==================
For With Reversed As Its Whole Sequence
:error
==================
{% for x in reversed %}a{% endfor %}
---
==================
For With Reversed Three Times
==================
{% for x in reversed reversed reversed %}a{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))
(unexpected_argument))
(template
(content)))
(endfor_tag))
(content)) (content))
+111 -20
View File
@@ -8,7 +8,7 @@ Trans
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -25,7 +25,7 @@ Translate
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -42,7 +42,7 @@ Trans With A Variable
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -59,7 +59,7 @@ Trans With A Filtered Message
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -78,7 +78,7 @@ Trans Noop
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -95,7 +95,7 @@ Trans With As
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -113,7 +113,7 @@ Trans With Context
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -134,7 +134,7 @@ Trans With Every Option
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -156,7 +156,7 @@ Trans With Its Options Reordered
(template (template
(content) (content)
(translate (translate_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -180,13 +180,21 @@ Trans Without A Message
================== ==================
Trans With An Option Twice Trans With An Option Twice
:error
================== ==================
{% trans "hi" noop noop %} {% trans "hi" noop noop %}
--- ---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Trans With As Twice Trans With As Twice
@@ -200,13 +208,21 @@ Trans With As Twice
================== ==================
Trans With An Unknown Option Trans With An Unknown Option
:error
================== ==================
{% trans "hi" bogus %} {% trans "hi" bogus %}
--- ---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Trans With Context And No Value Trans With Context And No Value
@@ -228,7 +244,7 @@ Get Available Languages
(template (template
(content) (content)
(get_available_languages (get_available_languages_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
@@ -252,7 +268,7 @@ Get Current Language
(template (template
(content) (content)
(get_current_language (get_current_language_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
@@ -266,7 +282,7 @@ Get Current Language Bidi
(template (template
(content) (content)
(get_current_language_bidi (get_current_language_bidi_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
@@ -280,7 +296,7 @@ Get Language Info
(template (template
(content) (content)
(get_language_info (get_language_info_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -298,7 +314,7 @@ Get Language Info For A Variable
(template (template
(content) (content)
(get_language_info (get_language_info_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -336,7 +352,7 @@ Get Language Info List
(template (template
(content) (content)
(get_language_info_list (get_language_info_list_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -355,12 +371,15 @@ Language
(template (template
(content) (content)
(language_group (language_group
(language_block
(language_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(string)))) (string)))))
(template (template
(content))) (content)))
(endlanguage_tag))
(content)) (content))
================== ==================
@@ -374,12 +393,15 @@ Language With A Variable
(template (template
(content) (content)
(language_group (language_group
(language_block
(language_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content))) (content)))
(endlanguage_tag))
(content)) (content))
================== ==================
@@ -394,13 +416,26 @@ Language Without An Argument
================== ==================
Language With Two Arguments Language With Two Arguments
:error
================== ==================
{% language "a" "b" %}x{% endlanguage %} {% language "a" "b" %}x{% endlanguage %}
--- ---
(template
(content)
(language_group
(language_block
(language_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endlanguage_tag))
(content))
================== ==================
Language Name Filter Language Name Filter
@@ -486,3 +521,59 @@ Language Name Filter With An Argument
{{ "de"|language_name:1 }} {{ "de"|language_name:1 }}
--- ---
==================
Translate With A Context Named After Noop
:error
==================
{% trans "hi" context noop %}
---
==================
Translate With A Context From An Attribute Of Noop
==================
{% trans "hi" context noop.x %}
---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(filtered_value
(value
(variable_attribute
(identifier)
(attribute)))))
(content))
==================
Translate With A Filtered Context Named After Noop
==================
{% trans "hi" context noop|upper %}
---
(template
(content)
(translate_tag
(filtered_value
(value
(literal
(string))))
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter))))
(content))
+366 -19
View File
@@ -11,14 +11,17 @@ Hi
(template (template
(content) (content)
(if_group (if_group
(if_block
(if_tag
(predicate (predicate
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier) (identifier)
(attribute))))) (attribute))))))
(template (template
(content))) (content)))
(endif_tag))
(content)) (content))
================== ==================
@@ -34,13 +37,16 @@ working
(template (template
(content) (content)
(if_group (if_group
(if_block
(if_tag
(predicate (predicate
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier))))) (identifier))))))
(template (template
(content))) (content)))
(endif_tag))
(content)) (content))
================== ==================
@@ -60,6 +66,8 @@ other
(template (template
(content) (content)
(if_group (if_group
(if_block
(if_tag
(predicate (predicate
(filtered_value (filtered_value
(value (value
@@ -69,23 +77,175 @@ other
(filtered_value (filtered_value
(value (value
(literal (literal
(number))))) (number))))))
(template
(content))
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number)))))
(template
(content))
(template (template
(content))) (content)))
(elif_block
(elif_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)))
(endif_tag))
(content))
==================
If With Elif
==================
{% if x %}a{% elif y %}b{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(elif_block
(elif_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If With Else
==================
{% if x %}a{% else %}b{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)))
(endif_tag))
(content))
==================
If With An Elif Missing Its Condition
==================
{% if x %}a{% elif %}b{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)
(unexpected_tag)
(content)))
(endif_tag))
(content))
==================
If With An Elif After An Else
==================
{% if x %}a{% else %}b{% elif y %}c{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)
(unexpected_tag)
(content)))
(elif_block
(elif_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If With Two Elses
==================
{% if x %}a{% else %}b{% else %}c{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)))
(endif_tag))
(content)) (content))
================== ==================
@@ -101,6 +261,8 @@ yes
(template (template
(content) (content)
(if_group (if_group
(if_block
(if_tag
(predicate (predicate
(filtered_value (filtered_value
(value (value
@@ -137,9 +299,10 @@ yes
(filtered_value (filtered_value
(value (value
(literal (literal
(number))))) (number))))))
(template (template
(content))) (content)))
(endif_tag))
(content)) (content))
================== ==================
@@ -151,4 +314,188 @@ If Operator Adjacent To Value
--- ---
(ERROR) ==================
If Is Not None
==================
{% if status is not None %}yes{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(none))))))
(template
(content)))
(endif_tag))
(content))
==================
If Is
==================
{% if x is y %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If Is Not
==================
{% if x is not y %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If Is Not Not
==================
{% if x is not not y %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If With A Repeated Not
==================
{% if not not x %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If Not Before A Name Beginning With In
==================
{% if not in_stock %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
If Is Before A Name Beginning With Not
==================
{% if x is nothing %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
+70 -11
View File
@@ -11,8 +11,11 @@ content
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template (template
(content))) (content)))
(endifchanged_tag))
(content)) (content))
================== ==================
@@ -28,6 +31,8 @@ content
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(ifchanged_block
(ifchanged_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -35,9 +40,10 @@ content
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content))) (content)))
(endifchanged_tag))
(content)) (content))
================== ==================
@@ -55,10 +61,15 @@ unchanged
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(template (ifchanged_block
(content)) (ifchanged_tag)
(template (template
(content))) (content)))
(else_block
(else_tag)
(template
(content)))
(endifchanged_tag))
(content)) (content))
================== ==================
@@ -76,6 +87,8 @@ unchanged
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(ifchanged_block
(ifchanged_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -85,11 +98,14 @@ unchanged
(variable_attribute (variable_attribute
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter))) (filter))))
(template
(content))
(template (template
(content))) (content)))
(else_block
(else_tag)
(template
(content)))
(endifchanged_tag))
(content)) (content))
================== ==================
@@ -105,8 +121,13 @@ content
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template (template
(content))) (content)))
(else_block
(else_tag))
(endifchanged_tag))
(content)) (content))
================== ==================
@@ -128,21 +149,30 @@ c
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template (template
(content)) (content)))
(else_block
(else_tag)
(template (template
(content) (content)
(ifchanged_group (ifchanged_group
(template (ifchanged_block
(content)) (ifchanged_tag)
(template (template
(content))) (content)))
(else_block
(else_tag)
(template
(content))) (content)))
(endifchanged_tag))
(content)))
(endifchanged_tag))
(content)) (content))
================== ==================
Ifchanged With Two Elses Ifchanged With Two Elses
:error
================== ==================
{% ifchanged %} {% ifchanged %}
@@ -155,10 +185,24 @@ c
--- ---
(template
(content)
(ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)))
(endifchanged_tag))
(content))
================== ==================
Ifchanged With An Argument On Else Ifchanged With An Argument On Else
:error
================== ==================
{% ifchanged %} {% ifchanged %}
@@ -168,3 +212,18 @@ b
{% endifchanged %} {% endifchanged %}
--- ---
(template
(content)
(ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template
(content)))
(else_block
(else_tag
(unexpected_argument))
(template
(content)))
(endifchanged_tag))
(content))
+54 -20
View File
@@ -8,7 +8,7 @@ Include With String
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -25,7 +25,7 @@ Include With Variable
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -42,7 +42,7 @@ Include With A Filtered Name
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -61,7 +61,7 @@ Include Only
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -78,7 +78,7 @@ Include With Context
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -100,7 +100,7 @@ Include With Several Bindings
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -127,7 +127,7 @@ Include With Context And Only
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -149,7 +149,7 @@ Include With Only Before With
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -162,7 +162,7 @@ Include With Only Before With
(content)) (content))
================== ==================
Include With A Repeated Binding Include With A Binding Twice
================== ==================
{% include "t.html" with a=1 a=2 %} {% include "t.html" with a=1 a=2 %}
@@ -171,21 +171,17 @@ Include With A Repeated Binding
(template (template
(content) (content)
(include (include_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(string)))) (string))))
variable: (identifier) (identifier)
(filtered_value (filtered_value
(value (value
(literal (literal
(number)))) (number))))
variable: (identifier) (unexpected_argument))
(filtered_value
(value
(literal
(number)))))
(content)) (content))
================== ==================
@@ -210,33 +206,62 @@ Include With Nothing To Bind
================== ==================
Include With With Twice Include With With Twice
:error
================== ==================
{% include "t.html" with a=1 with b=2 %} {% include "t.html" with a=1 with b=2 %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Include With Only Twice Include With Only Twice
:error
================== ==================
{% include "t.html" only only %} {% include "t.html" only only %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Include With An Unknown Option Include With An Unknown Option
:error
================== ==================
{% include "t.html" bogus %} {% include "t.html" bogus %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Include With A Legacy Binding Include With A Legacy Binding
@@ -250,9 +275,18 @@ Include With A Legacy Binding
================== ==================
Include With A Second Name Include With A Second Name
:error
================== ==================
{% include "t.html" "u.html" %} {% include "t.html" "u.html" %}
--- ---
(template
(content)
(include_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
+48 -4
View File
@@ -9,8 +9,11 @@ Localize
(template (template
(content) (content)
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(content))) (content)))
(endlocalize_tag))
(content)) (content))
================== ==================
@@ -24,8 +27,11 @@ Localize On
(template (template
(content) (content)
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(content))) (content)))
(endlocalize_tag))
(content)) (content))
================== ==================
@@ -39,8 +45,11 @@ Localize Off
(template (template
(content) (content)
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(content))) (content)))
(endlocalize_tag))
(content)) (content))
================== ==================
@@ -54,8 +63,11 @@ Localize Hugging Its Delimiters
(template (template
(content) (content)
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(content))) (content)))
(endlocalize_tag))
(content)) (content))
================== ==================
@@ -69,41 +81,72 @@ Localize Nested
(template (template
(content) (content)
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(localize_group (localize_group
(localize_block
(localize_tag)
(template (template
(content))))) (content)))
(endlocalize_tag))))
(endlocalize_tag))
(content)) (content))
================== ==================
Localize With Another Argument Localize With Another Argument
:error
================== ==================
{% localize maybe %}x{% endlocalize %} {% localize maybe %}x{% endlocalize %}
--- ---
(template
(content)
(localize_group
(localize_block
(localize_tag
(unexpected_argument))
(template
(content)))
(endlocalize_tag))
(content))
================== ==================
Localize With Two Arguments Localize With Two Arguments
:error
================== ==================
{% localize on off %}x{% endlocalize %} {% localize on off %}x{% endlocalize %}
--- ---
(template
(content)
(localize_group
(localize_block
(localize_tag
(unexpected_argument))
(template
(content)))
(endlocalize_tag))
(content))
================== ==================
Localize Unclosed Localize Unclosed
:error
================== ==================
{% localize %} {% localize %}
--- ---
(template
(content)
(localize_group
(localize_block
(localize_tag)
(template
(content)))
(missing_endlocalize_tag)))
================== ==================
Localize Filter Localize Filter
@@ -161,3 +204,4 @@ Unlocalize Filter With An Argument
{{ x|unlocalize:1 }} {{ x|unlocalize:1 }}
--- ---
+4 -4
View File
@@ -8,7 +8,7 @@ Load Single Library
(template (template
(content) (content)
(load (load_tag
(library (library
(identifier))) (identifier)))
(content)) (content))
@@ -23,7 +23,7 @@ Load Multiple Libraries
(template (template
(content) (content)
(load (load_tag
(library (library
(identifier)) (identifier))
(library (library
@@ -40,7 +40,7 @@ Load Dotted Library
(template (template
(content) (content)
(load (load_tag
(library (library
(identifier) (identifier)
(identifier))) (identifier)))
@@ -56,7 +56,7 @@ Load Names From Library
(template (template
(content) (content)
(load (load_tag
(identifier) (identifier)
(identifier) (identifier)
(library (library
+2 -2
View File
@@ -8,7 +8,7 @@ Lorem Words
(template (template
(content) (content)
(lorem (lorem_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -25,7 +25,7 @@ Lorem Paragraphs Random
(template (template
(content) (content)
(lorem (lorem_tag
(filtered_value (filtered_value
(value (value
(literal (literal
+992
View File
@@ -0,0 +1,992 @@
==================
Missing Endif At End Of Input
==================
{% if a %}x
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(missing_endif_tag)))
==================
Missing Endfor After Empty
==================
{% for x in y %}a{% empty %}b
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)))
(missing_endfor_tag)))
==================
Missing Endblock At End Of Input
==================
{% block content %}x
---
(template
(content)
(block_group
(block_block
(block_tag
(push_block))
(template
(content)))
(missing_endblock_tag)))
==================
Missing Endpartialdef At End Of Input
==================
{% partialdef p %}x
---
(template
(content)
(partialdef_group
(partialdef_block
(partialdef_tag
(push_partial))
(template
(content)))
(missing_endpartialdef_tag)))
==================
Missing Endverbatim With An Empty Body
==================
{% verbatim %}
---
(template
(content)
(verbatim_group
(verbatim_block
(verbatim_tag
(push_verbatim))
(verbatim_content))
(missing_endverbatim_tag)))
==================
Missing Endverbatim After A Tag In Its Text
==================
{% verbatim %}{% endif %}x
---
(template
(content)
(verbatim_group
(verbatim_block
(verbatim_tag
(push_verbatim))
(verbatim_content))
(missing_endverbatim_tag)))
==================
Missing Endcomment At End Of Input
==================
{% comment %}x
---
(template
(content)
(comment_group
(comment_block
(comment_tag)
(comment_content))
(missing_endcomment_tag)))
==================
Missing Endif Before An Outer End Tag
==================
{% for x in y %}{% if a %}{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))))
(missing_endif_tag))))
(endfor_tag))
(content))
==================
Unseen Outer Else Is Not A Missing Tag
==================
{% if a %}{% for x in y %}{% else %}b{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(unexpected_tag)
(content)))
(missing_endfor_tag))))
(endif_tag))
(content))
==================
Missing Endwith Before An Outer Endblock
==================
{% block a %}{% with b=1 %}{% endblock a %}
---
(template
(content)
(block_group
(block_block
(block_tag
(push_block))
(template
(with_group
(with_block
(with_tag
(identifier)
(filtered_value
(value
(literal
(number))))))
(missing_endwith_tag))))
(endblock_tag
(pop_block)))
(content))
==================
Missing Two End Tags At End Of Input
==================
{% for x in y %}{% if a %}
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(missing_endif_tag))))
(missing_endfor_tag)))
==================
Missing Endblock Before An Outer Name
==================
{% block a %}{% block b %}{% endblock a %}
---
(template
(content)
(block_group
(block_block
(block_tag
(push_block))
(template
(block_group
(block_block
(block_tag
(push_block)))
(missing_endblock_tag))))
(endblock_tag
(pop_block)))
(content))
==================
Missing Endpartialdef Before An Outer Name
==================
{% partialdef a %}{% partialdef b %}{% endpartialdef a %}
---
(template
(content)
(partialdef_group
(partialdef_block
(partialdef_tag
(push_partial))
(template
(partialdef_group
(partialdef_block
(partialdef_tag
(push_partial)))
(missing_endpartialdef_tag))))
(endpartialdef_tag
(pop_partial)))
(content))
==================
Missing Plural At End Of Input
==================
{% blocktrans count n=x %}one
---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(missing_plural_tag))
(missing_endblocktrans_tag)))
==================
Missing Plural Before Endblocktranslate
==================
{% blocktranslate count n=x %}one{% endblocktranslate %}
---
(template
(content)
(blocktranslate_group
(blocktranslate_block
(blocktranslate_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(missing_plural_tag))
(endblocktranslate_tag))
(content))
==================
Missing Endblocktrans After Plural
==================
{% blocktrans count n=x %}one{% plural %}many
---
(template
(content)
(blocktranslate_group
(blocktrans_block
(blocktrans_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(content)
(plural_block
(plural_tag)
(content)))
(missing_endblocktrans_tag)))
==================
Missing Endfor Before A Tag Without Spaces
==================
{% if a %}{% for x in y %}{%endif%}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))))
(missing_endfor_tag))))
(endif_tag))
(content))
==================
Missing Endfor Before A Tag Across Lines
==================
{% if a %}{% for x in y %}{%
endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier))))))
(missing_endfor_tag))))
(endif_tag))
(content))
==================
Missing Endif After A Custom End Tag
==================
{% if a %}{% endmytag %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(custom_tag
(identifier))
(content)))
(missing_endif_tag)))
==================
Second Else After A Nested Else Is Unexpected
==================
{% if a %}{% if b %}x{% else %}y{% else %}z{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)))
(endif_tag))
(content)))
(missing_endif_tag)))
==================
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_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)
(unexpected_tag)
(content)))
(endfor_tag))
(content)))
(missing_endfor_tag)))
==================
Else Of An If Nested In An Else
==================
{% if a %}x{% else %}{% if b %}y{% else %}z{% endif %}{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)))
(endif_tag))))
(endif_tag))
(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_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(elif_block
(elif_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(elif_block
(elif_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))))
(endif_tag))
(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_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)))
(endfor_tag))))
(endfor_tag))
(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_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template
(content)))
(else_block
(else_tag)
(template
(content)))
(endifchanged_tag))))
(endif_tag))
(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_block
(ifchanged_tag)
(template
(content)))
(else_block
(else_tag)
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)))
(endif_tag))))
(endifchanged_tag))
(content))
==================
Third Else Is Unexpected Too
==================
{% if a %}w{% else %}x{% else %}y{% else %}z{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)
(unexpected_tag)
(content)))
(endif_tag))
(content))
==================
Second And Third Empty Are Unexpected
==================
{% for x in y %}a{% empty %}b{% empty %}c{% empty %}d{% endfor %}
---
(template
(content)
(for_group
(for_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(content)))
(empty_block
(empty_tag)
(template
(content)
(unexpected_tag)
(content)
(unexpected_tag)
(content)))
(endfor_tag))
(content))
==================
Second And Third Else Of An Ifchanged Are Unexpected
==================
{% ifchanged %}a{% else %}b{% else %}c{% else %}d{% endifchanged %}
---
(template
(content)
(ifchanged_group
(ifchanged_block
(ifchanged_tag)
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)
(unexpected_tag)
(content)))
(endifchanged_tag))
(content))
==================
Second Else Inside A Block Is Unexpected
==================
{% block a %}{% if b %}x{% else %}y{% else %}z{% endif %}{% endblock %}
---
(template
(content)
(block_group
(block_block
(block_tag
(push_block))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)))
(endif_tag))))
(endblock_tag
(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_block
(for_tag
(identifier)
(filtered_value
(value
(variable_attribute
(identifier)))))
(template
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(else_block
(else_tag)
(template
(content)
(unexpected_tag)
(content)))
(endif_tag))))
(endfor_tag))
(content))
==================
Stray End Tag After A Missing One
==================
{% if a %}{% endfor %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(unexpected_tag)
(content)))
(missing_endif_tag)))
==================
Endblock Naming No Open Block
==================
{% block a %}{% block b %}{% endblock c %}
---
(template
(content)
(block_group
(block_block
(block_tag
(push_block))
(template
(block_group
(block_block
(block_tag
(push_block))
(template
(unexpected_tag
(unexpected_argument))
(content)))
(missing_endblock_tag))))
(missing_endblock_tag)))
==================
Second Else Is Not A Missing Tag
==================
{% if a %}{% else %}{% else %}{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier)))))))
(else_block
(else_tag)
(template
(unexpected_tag)))
(endif_tag))
(content))
==================
Plural Without Count Is Not A Missing Tag
:error
==================
{% blocktrans %}one{% plural %}many{% endblocktrans %}
---
+2 -2
View File
@@ -8,7 +8,7 @@ Now Basic
(template (template
(content) (content)
(now (now_tag
(string)) (string))
(content)) (content))
@@ -22,7 +22,7 @@ Now With As
(template (template
(content) (content)
(now (now_tag
(string) (string)
variable: (identifier)) variable: (identifier))
(content)) (content))
+1 -1
View File
@@ -8,6 +8,6 @@ Partial Basic
(template (template
(content) (content)
(partial (partial_tag
(identifier)) (identifier))
(content)) (content))
+12 -6
View File
@@ -13,10 +13,13 @@ Partialdef Basic
(template (template
(content) (content)
(partialdef_group (partialdef_group
(push_partial) (partialdef_block
(partialdef_tag
(push_partial))
(template (template
(content)) (content)))
(pop_partial)) (endpartialdef_tag
(pop_partial)))
(content)) (content))
================== ==================
@@ -34,8 +37,11 @@ Partialdef Inline
(template (template
(content) (content)
(partialdef_group (partialdef_group
(push_partial) (partialdef_block
(partialdef_tag
(push_partial))
(template (template
(content)) (content)))
(pop_partial)) (endpartialdef_tag
(pop_partial)))
(content)) (content))
+31 -7
View File
@@ -8,7 +8,7 @@ Querystring Empty
(template (template
(content) (content)
(query_string) (querystring_tag)
(content)) (content))
================== ==================
@@ -21,7 +21,7 @@ Querystring With Key Value
(template (template
(content) (content)
(query_string (querystring_tag
(identifier) (identifier)
(filtered_value (filtered_value
(value (value
@@ -39,7 +39,7 @@ Querystring With Identifier And Key Value
(template (template
(content) (content)
(query_string (querystring_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -61,7 +61,7 @@ Querystring With Expression Argument And As
(template (template
(content) (content)
(query_string (querystring_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -85,7 +85,7 @@ Querystring With Two Keys
(template (template
(content) (content)
(query_string (querystring_tag
(identifier) (identifier)
(filtered_value (filtered_value
(value (value
@@ -100,19 +100,43 @@ Querystring With Two Keys
================== ==================
Querystring With A Key Twice Querystring With A Key Twice
:error
================== ==================
{% querystring page=2 page=3 %} {% querystring page=2 page=3 %}
--- ---
(template
(content)
(querystring_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
================== ==================
Querystring With A Key Repeated Later Querystring With A Key Repeated Later
:error
================== ==================
{% querystring a=1 b=2 a=3 %} {% querystring a=1 b=2 a=3 %}
--- ---
(template
(content)
(querystring_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(content))
+1 -1
View File
@@ -8,7 +8,7 @@ Regroup Basic
(template (template
(content) (content)
(regroup (regroup_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
+2 -2
View File
@@ -8,7 +8,7 @@ Resetcycle Bare
(template (template
(content) (content)
(reset_cycle) (resetcycle_tag)
(content)) (content))
================== ==================
@@ -21,6 +21,6 @@ Resetcycle With Name
(template (template
(content) (content)
(reset_cycle (resetcycle_tag
(identifier)) (identifier))
(content)) (content))
+3
View File
@@ -13,6 +13,9 @@ Spaceless Basic
(template (template
(content) (content)
(spaceless_group (spaceless_group
(spaceless_block
(spaceless_tag)
(template (template
(content))) (content)))
(endspaceless_tag))
(content)) (content))
+33 -12
View File
@@ -8,7 +8,7 @@ Static
(template (template
(content) (content)
(static (static_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -25,7 +25,7 @@ Static With A Variable Path
(template (template
(content) (content)
(static (static_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
@@ -42,7 +42,7 @@ Static With A Filtered Path
(template (template
(content) (content)
(static (static_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -61,7 +61,7 @@ Static With As
(template (template
(content) (content)
(static (static_tag
(filtered_value (filtered_value
(value (value
(literal (literal
@@ -81,23 +81,40 @@ Static Without A Path
================== ==================
Static With A Second Path Static With A Second Path
:error
================== ==================
{% static "a.css" "b.css" %} {% static "a.css" "b.css" %}
--- ---
(template
(content)
(static_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(content))
================== ==================
Static With Anything After As Static With Anything After As
:error
================== ==================
{% static "a.css" as url junk %} {% static "a.css" as url junk %}
--- ---
(template
(content)
(static_tag
(filtered_value
(value
(literal
(string))))
(identifier)
(unexpected_argument))
(content))
================== ==================
Get Static Prefix Get Static Prefix
@@ -109,7 +126,7 @@ Get Static Prefix
(template (template
(content) (content)
(get_static_prefix) (get_static_prefix_tag)
(content)) (content))
================== ==================
@@ -122,7 +139,7 @@ Get Static Prefix With As
(template (template
(content) (content)
(get_static_prefix (get_static_prefix_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
@@ -136,19 +153,23 @@ Get Media Prefix With As
(template (template
(content) (content)
(get_media_prefix (get_media_prefix_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
================== ==================
Get Static Prefix Without As Get Static Prefix Without As
:error
================== ==================
{% get_static_prefix prefix %} {% get_static_prefix prefix %}
--- ---
(template
(content)
(get_static_prefix_tag
(unexpected_argument))
(content))
================== ==================
Get Static Prefix Without A Variable Get Static Prefix Without A Variable
@@ -170,10 +191,10 @@ Load Names The Library, Not The Tag
(template (template
(content) (content)
(load (load_tag
(library (library
(identifier))) (identifier)))
(static (static_tag
(filtered_value (filtered_value
(value (value
(literal (literal
+326 -6
View File
@@ -174,7 +174,7 @@ Template Variable With Filter Argument
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
(value argument: (value
(literal (literal
(string))))))) (string)))))))
(content)) (content))
@@ -196,7 +196,7 @@ Template Variable Number With Filter Argument
(number))) (number)))
(filter_expression (filter_expression
(filter (filter
(value argument: (value
(literal (literal
(number))))))) (number)))))))
(content)) (content))
@@ -210,7 +210,6 @@ Template Variable Literal Adjacent To Name
--- ---
(ERROR)
================== ==================
Template Variable With Escaped String Template Variable With Escaped String
@@ -272,7 +271,6 @@ Template Variable Attribute Beginning With Underscore
--- ---
(ERROR)
================== ==================
Template Variable Translated String Template Variable Translated String
@@ -321,7 +319,6 @@ Template Variable Translated String With Spaces
--- ---
(ERROR)
================== ==================
Template Variable Spanning Lines Template Variable Spanning Lines
@@ -341,7 +338,7 @@ Template Variable Spanning Lines
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter (filter
(value argument: (value
(literal (literal
(number))))))) (number)))))))
(content)) (content))
@@ -365,3 +362,326 @@ Template Variable With Spaced Filter
(filter)))) (filter))))
(content)) (content))
==================
Template Variable True
==================
{{ True }}
---
(template
(content)
(template_variable
(filtered_value
(value
(literal
(boolean)))))
(content))
==================
Template Variable False
==================
{{ False }}
---
(template
(content)
(template_variable
(filtered_value
(value
(literal
(boolean)))))
(content))
==================
Template Variable None
==================
{{ None }}
---
(template
(content)
(template_variable
(filtered_value
(value
(literal
(none)))))
(content))
==================
Template Variable Boolean As Filter Argument
==================
{{ value|default:True }}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier)))
(filter_expression
(filter
argument: (value
(literal
(boolean)))))))
(content))
==================
Template Variable Name Beginning With A Literal
==================
{{ Truest }}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier)))))
(content))
==================
Template Variable Attribute Of A Literal
:error
==================
{{ None.x }}
---
==================
Template Variable Trailing Dot With An Exponent
==================
{{ 1.e5 }}
---
(template
(content)
(template_variable
(filtered_value
(value
(literal
(number)))))
(content))
==================
Template Variable Trailing Dot
:error
==================
{{ 1. }}
---
==================
Template Variable Signed Trailing Dot
:error
==================
{{ -1. }}
---
==================
Braces That Open Nothing Are Text
==================
{% if x %}Hello {} world{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
A Brace Before A Tag Is Text
==================
Hello {{% if x %}a{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
A Brace Before A Comment Is Text
==================
a{{# c #}
---
(template
(content)
(template_comment)
(content))
==================
A Brace At The End Is Text
==================
a{
---
(template
(content))
==================
A Brace Before The End Tag Of A Group Is Text
==================
{% if x %}Hello {{% endif %}
---
(template
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
Three Braces Open A Variable
:error
==================
a{{{ x }}
---
==================
A Filter Pipe With No Filter Stays In Its Variable
==================
{{ oops| }}
{% if x == 1 %}
hello
{% endif %}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier))))
(ERROR))
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))
(binaryOperator)
(filtered_value
(value
(literal
(number))))))
(template
(content)))
(endif_tag))
(content))
==================
A Variable Missing Its Close Ends Where It Cannot Continue
==================
{{ hello }{% if display %}body {% endif %}
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier))))
(missing_variable_close))
(content)
(if_group
(if_block
(if_tag
(predicate
(filtered_value
(value
(variable_attribute
(identifier))))))
(template
(content)))
(endif_tag))
(content))
==================
A Variable Missing Its Close At The End
==================
{{ hello
---
(template
(content)
(template_variable
(filtered_value
(value
(variable_attribute
(identifier))))
(missing_variable_close)))
+2 -2
View File
@@ -8,7 +8,7 @@ Templatetag Openblock
(template (template
(content) (content)
(template_tag_block) (templatetag_tag)
(content)) (content))
================== ==================
@@ -21,5 +21,5 @@ Templatetag Closevariable
(template (template
(content) (content)
(template_tag_block) (templatetag_tag)
(content)) (content))
+63 -8
View File
@@ -9,8 +9,11 @@ Localtime
(template (template
(content) (content)
(localtime_group (localtime_group
(localtime_block
(localtime_tag)
(template (template
(content))) (content)))
(endlocaltime_tag))
(content)) (content))
================== ==================
@@ -24,8 +27,11 @@ Localtime On
(template (template
(content) (content)
(localtime_group (localtime_group
(localtime_block
(localtime_tag)
(template (template
(content))) (content)))
(endlocaltime_tag))
(content)) (content))
================== ==================
@@ -39,29 +45,50 @@ Localtime Off
(template (template
(content) (content)
(localtime_group (localtime_group
(localtime_block
(localtime_tag)
(template (template
(content))) (content)))
(endlocaltime_tag))
(content)) (content))
================== ==================
Localtime With Another Argument Localtime With Another Argument
:error
================== ==================
{% localtime maybe %}x{% endlocaltime %} {% localtime maybe %}x{% endlocaltime %}
--- ---
(template
(content)
(localtime_group
(localtime_block
(localtime_tag
(unexpected_argument))
(template
(content)))
(endlocaltime_tag))
(content))
================== ==================
Localtime With Two Arguments Localtime With Two Arguments
:error
================== ==================
{% localtime on off %}x{% endlocaltime %} {% localtime on off %}x{% endlocaltime %}
--- ---
(template
(content)
(localtime_group
(localtime_block
(localtime_tag
(unexpected_argument))
(template
(content)))
(endlocaltime_tag))
(content))
================== ==================
Timezone Timezone
@@ -74,12 +101,15 @@ Timezone
(template (template
(content) (content)
(timezone_group (timezone_group
(timezone_block
(timezone_tag
(filtered_value (filtered_value
(value (value
(literal (literal
(string)))) (string)))))
(template (template
(content))) (content)))
(endtimezone_tag))
(content)) (content))
================== ==================
@@ -93,12 +123,15 @@ Timezone With A Variable
(template (template
(content) (content)
(timezone_group (timezone_group
(timezone_block
(timezone_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier)))) (identifier)))))
(template (template
(content))) (content)))
(endtimezone_tag))
(content)) (content))
================== ==================
@@ -112,14 +145,17 @@ Timezone With A Filtered Argument
(template (template
(content) (content)
(timezone_group (timezone_group
(timezone_block
(timezone_tag
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier))) (identifier)))
(filter_expression (filter_expression
(filter))) (filter))))
(template (template
(content))) (content)))
(endtimezone_tag))
(content)) (content))
================== ==================
@@ -134,13 +170,26 @@ Timezone Without An Argument
================== ==================
Timezone With Two Arguments Timezone With Two Arguments
:error
================== ==================
{% timezone "a" "b" %}x{% endtimezone %} {% timezone "a" "b" %}x{% endtimezone %}
--- ---
(template
(content)
(timezone_group
(timezone_block
(timezone_tag
(filtered_value
(value
(literal
(string))))
(unexpected_argument))
(template
(content)))
(endtimezone_tag))
(content))
================== ==================
Get Current Timezone Get Current Timezone
@@ -152,7 +201,7 @@ Get Current Timezone
(template (template
(content) (content)
(get_current_timezone (get_current_timezone_tag
variable: (identifier)) variable: (identifier))
(content)) (content))
@@ -168,13 +217,18 @@ Get Current Timezone Without As
================== ==================
Get Current Timezone With Anything After As Get Current Timezone With Anything After As
:error
================== ==================
{% get_current_timezone as tz junk %} {% get_current_timezone as tz junk %}
--- ---
(template
(content)
(get_current_timezone_tag
(identifier)
(unexpected_argument))
(content))
================== ==================
Localtime Filter Localtime Filter
@@ -254,3 +308,4 @@ Localtime Filter With An Argument
{{ x|localtime:1 }} {{ x|localtime:1 }}
--- ---
+4 -4
View File
@@ -8,7 +8,7 @@ Url Basic
(template (template
(content) (content)
(url_block (url_tag
(string)) (string))
(content)) (content))
@@ -22,7 +22,7 @@ Url With Positional Args
(template (template
(content) (content)
(url_block (url_tag
(string) (string)
(filtered_value (filtered_value
(value (value
@@ -44,7 +44,7 @@ Url With Keyword Args
(template (template
(content) (content)
(url_block (url_tag
(string) (string)
(identifier) (identifier)
(filtered_value (filtered_value
@@ -63,7 +63,7 @@ Url With As
(template (template
(content) (content)
(url_block (url_tag
(string) (string)
variable: (identifier)) variable: (identifier))
(content)) (content))
+29 -14
View File
@@ -9,9 +9,12 @@ Verbatim Basic
(template (template
(content) (content)
(verbatim_group (verbatim_group
(push_verbatim) (verbatim_block
(verbatim_content) (verbatim_tag
(pop_verbatim)) (push_verbatim))
(verbatim_content))
(endverbatim_tag
(pop_verbatim)))
(content)) (content))
================== ==================
@@ -25,8 +28,11 @@ Verbatim Empty
(template (template
(content) (content)
(verbatim_group (verbatim_group
(push_verbatim) (verbatim_block
(pop_verbatim)) (verbatim_tag
(push_verbatim)))
(endverbatim_tag
(pop_verbatim)))
(content)) (content))
================== ==================
@@ -43,9 +49,12 @@ Verbatim Ignores Tags
(template (template
(content) (content)
(verbatim_group (verbatim_group
(push_verbatim) (verbatim_block
(verbatim_content) (verbatim_tag
(pop_verbatim)) (push_verbatim))
(verbatim_content))
(endverbatim_tag
(pop_verbatim)))
(content)) (content))
================== ==================
@@ -59,9 +68,12 @@ Verbatim With Name
(template (template
(content) (content)
(verbatim_group (verbatim_group
(push_verbatim) (verbatim_block
(verbatim_content) (verbatim_tag
(pop_verbatim)) (push_verbatim))
(verbatim_content))
(endverbatim_tag
(pop_verbatim)))
(content)) (content))
================== ==================
@@ -77,7 +89,10 @@ Avoid template rendering via the {% verbatim %}{% endverbatim %} block.
(template (template
(content) (content)
(verbatim_group (verbatim_group
(push_verbatim) (verbatim_block
(verbatim_content) (verbatim_tag
(pop_verbatim)) (push_verbatim))
(verbatim_content))
(endverbatim_tag
(pop_verbatim)))
(content)) (content))
+143 -2
View File
@@ -11,12 +11,14 @@ With Basic
(template (template
(content) (content)
(with_group (with_group
(with_block
(with_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
(variable_attribute (variable_attribute
(identifier) (identifier)
(attribute)))) (attribute)))))
(template (template
(content) (content)
(template_variable (template_variable
@@ -25,6 +27,7 @@ With Basic
(variable_attribute (variable_attribute
(identifier))))) (identifier)))))
(content))) (content)))
(endwith_tag))
(content)) (content))
================== ==================
@@ -40,6 +43,8 @@ With Multiple Variables
(template (template
(content) (content)
(with_group (with_group
(with_block
(with_tag
variable: (identifier) variable: (identifier)
(filtered_value (filtered_value
(value (value
@@ -49,7 +54,7 @@ With Multiple Variables
(filtered_value (filtered_value
(value (value
(literal (literal
(number)))) (number)))))
(template (template
(content) (content)
(template_variable (template_variable
@@ -58,4 +63,140 @@ With Multiple Variables
(variable_attribute (variable_attribute
(identifier))))) (identifier)))))
(content))) (content)))
(endwith_tag))
(content))
==================
With Does Not Take An Else
==================
{% with x=1 %}a{% else %}b{% endwith %}
---
(template
(content)
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)
(unexpected_tag)
(content)))
(endwith_tag))
(content))
==================
With A Binding Twice
==================
{% with a=1 a=2 %}x{% endwith %}
---
(template
(content)
(with_group
(with_block
(with_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(template
(content)))
(endwith_tag))
(content))
==================
With A Binding Repeated Later
==================
{% with a=1 b=2 a=3 %}x{% endwith %}
---
(template
(content)
(with_group
(with_block
(with_tag
(identifier)
(filtered_value
(value
(literal
(number))))
(identifier)
(filtered_value
(value
(literal
(number))))
(unexpected_argument))
(template
(content)))
(endwith_tag))
(content))
==================
With Repeating A Binding Of The Tag Before It
==================
{% with a=1 %}x{% endwith %}{% with a=2 %}y{% endwith %}
---
(template
(content)
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(endwith_tag))
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(endwith_tag))
(content))
==================
With A Binding Named After A Literal
==================
{% with True=1 %}a{% endwith %}
---
(template
(content)
(with_group
(with_block
(with_tag
variable: (identifier)
(filtered_value
(value
(literal
(number)))))
(template
(content)))
(endwith_tag))
(content)) (content))
+20 -9
View File
@@ -6,21 +6,22 @@
"camelcase": "django", "camelcase": "django",
"title": "Django Parser", "title": "Django Parser",
"scope": "source.django", "scope": "source.django",
"file-types": ["html", "txt", "md"], "file-types": [
"injection-regex": "^django$", "html",
"class-name": "TreeSitterDjango", "txt",
"md"
],
"highlights": "queries/highlights.scm", "highlights": "queries/highlights.scm",
"injections": "queries/injections.scm",
"locals": "queries/locals.scm", "locals": "queries/locals.scm",
"injections": "queries/injections.scm" "injection-regex": "^django$",
"class-name": "TreeSitterDjango"
} }
], ],
"bindings": {
"node": true
},
"metadata": { "metadata": {
"version": "0.0.1", "version": "0.1.2",
"description": "DJango grammar for tree-sitter",
"license": "MIT", "license": "MIT",
"description": "Django grammar for tree-sitter",
"authors": [ "authors": [
{ {
"name": "Daniel Hanson", "name": "Daniel Hanson",
@@ -30,5 +31,15 @@
"links": { "links": {
"repository": "https://github.com/danhanson/tree-sitter-django" "repository": "https://github.com/danhanson/tree-sitter-django"
} }
},
"bindings": {
"c": true,
"go": true,
"java": false,
"node": true,
"python": true,
"rust": true,
"swift": true,
"zig": false
} }
} }