Compare commits
172 commits
windows_do
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6739742fb6 | ||
|
|
d251226a3c | ||
|
|
ae8184b8b9 | ||
|
|
470ecf8996 | ||
|
|
0cdb6bef7b | ||
|
|
cd603fa981 | ||
|
|
b12009a746 | ||
|
|
9f9a0bc410 | ||
|
|
5d290a2a75 | ||
|
|
5808350bfe | ||
|
|
e64e74d5ed | ||
|
|
1a88b26a10 | ||
|
|
6c05cdfb0c | ||
|
|
aefae11c0d | ||
|
|
630fa52717 | ||
|
|
eea85f4eff | ||
|
|
cd6672701b | ||
|
|
f4ca3d95ca | ||
|
|
17e3c7a5c5 | ||
|
|
dd60d5cff0 | ||
|
|
f1288ea5c9 | ||
|
|
47ae060966 | ||
|
|
a1893b4420 | ||
|
|
999e041d49 | ||
|
|
0d4d854809 | ||
|
|
93d793d249 | ||
|
|
82486d4b0a | ||
|
|
5d9605a91e | ||
|
|
5293dd683e | ||
|
|
62effdf128 | ||
|
|
8e4f21aba0 | ||
|
|
5208299bbb | ||
|
|
ba7350c7ee | ||
|
|
f96d518ebf | ||
|
|
d5b82fbbab | ||
|
|
a7d8c0cbb2 | ||
|
|
24007727d4 | ||
|
|
6aa63a7213 | ||
|
|
eacb95c85d | ||
|
|
6967640571 | ||
|
|
4ac2d5d276 | ||
|
|
642b56d9af | ||
|
|
0574fcf256 | ||
|
|
98de2bc1a8 | ||
|
|
cd4b6e2ef9 | ||
|
|
8caecbc13f | ||
|
|
1b654ae35d | ||
|
|
3bd44afcaa | ||
|
|
8b8199775f | ||
|
|
744e556f7e | ||
|
|
8a3dcc6155 | ||
|
|
b0afbf3762 | ||
|
|
974be3bb30 | ||
|
|
d861e2bcd9 | ||
|
|
b9c2d1dc89 | ||
|
|
8ca17d1bb1 | ||
|
|
3182efeccc | ||
|
|
bec7c3272b | ||
|
|
e6bfed33ee | ||
|
|
053b264502 | ||
|
|
a8f25fa441 | ||
|
|
f450ce4f6e | ||
|
|
3ff8edf9e8 | ||
|
|
6b6040961c | ||
|
|
888f57657d | ||
|
|
be8fe690d8 | ||
|
|
c0b1710f8a | ||
|
|
7d3feeae9a | ||
|
|
3f85f65e3f | ||
|
|
df8b62fc50 | ||
|
|
14b4708018 | ||
|
|
dcef0cc0ee | ||
|
|
c1a0f48781 | ||
|
|
f6d17fdb04 | ||
|
|
829733a35e | ||
|
|
d64b863030 | ||
|
|
882aa867eb | ||
|
|
de92a9b4c9 | ||
|
|
5880df47e2 | ||
|
|
e92a7803eb | ||
|
|
0d656de98b | ||
|
|
b095968dff | ||
|
|
d592b16ac0 | ||
|
|
320c0865e9 | ||
|
|
60635e0729 | ||
|
|
120f74723e | ||
|
|
02508d5570 | ||
|
|
42e7e9c3e7 | ||
|
|
55b9a25c84 | ||
|
|
877782a8a4 | ||
|
|
0e1f715ef1 | ||
|
|
f3012a999d | ||
|
|
3072d35ed5 | ||
|
|
57e3a7b2ca | ||
|
|
0df2916920 | ||
|
|
61c21aa408 | ||
|
|
7eb23d9f3c | ||
|
|
db2d221ae9 | ||
|
|
67cb3cb881 | ||
|
|
12a31536e1 | ||
|
|
7657cc9d35 | ||
|
|
13ff3935ac | ||
|
|
361287fb56 | ||
|
|
13d4db8bb4 | ||
|
|
419a5a7305 | ||
|
|
c7b5f89392 | ||
|
|
d546e28abf | ||
|
|
86e2fd2337 | ||
|
|
ff255a2354 | ||
|
|
fe67521b3d | ||
|
|
f02d7e7e33 | ||
|
|
6a8676f335 | ||
|
|
944386d25f | ||
|
|
ef03a3f8fe | ||
|
|
18a5243933 | ||
|
|
8444cc3deb | ||
|
|
097c2d4f05 | ||
|
|
b8f52210f9 | ||
|
|
ecc787e221 | ||
|
|
6188010f53 | ||
|
|
70cde4a110 | ||
|
|
77363a65c2 | ||
|
|
605e580063 | ||
|
|
a2f2b16acb | ||
|
|
87d778a1c6 | ||
|
|
e344837e35 | ||
|
|
bdee2c2dd3 | ||
|
|
da5926d6f5 | ||
|
|
b3bc7701cd | ||
|
|
262f1782cc | ||
|
|
00d172bf9f | ||
|
|
ae54350c76 | ||
|
|
3355825a68 | ||
|
|
7d0e029e37 | ||
|
|
0f5ccc4aba | ||
|
|
0cf6e7c507 | ||
|
|
1dc4804b6e | ||
|
|
c5b22a1dc6 | ||
|
|
92efd26380 | ||
|
|
24c8feba3e | ||
|
|
122493b717 | ||
|
|
4edcca9850 | ||
|
|
be0c44f871 | ||
|
|
35b1356e96 | ||
|
|
443acf080a | ||
|
|
00e394f0f1 | ||
|
|
341665824c | ||
|
|
bd02be25d5 | ||
|
|
12a6400c63 | ||
|
|
d86e1b4f5e | ||
|
|
422866a437 | ||
|
|
5f7806f99e | ||
|
|
a9bce7c18a | ||
|
|
335bfabc60 | ||
|
|
e1b424c191 | ||
|
|
ea9c318afb | ||
|
|
9d66dbc28f | ||
|
|
8c22426223 | ||
|
|
90ee433c9b | ||
|
|
f26bd44a43 | ||
|
|
021d9c447d | ||
|
|
ce56465197 | ||
|
|
b0cdab85fe | ||
|
|
47c9256976 | ||
|
|
cf89840460 | ||
|
|
e3294c3faf | ||
|
|
95ab17e444 | ||
|
|
9b914885f1 | ||
|
|
92678f0fc5 | ||
|
|
a1640e4fe4 | ||
|
|
1be51c2129 | ||
|
|
6214f95e7e |
164 changed files with 8013 additions and 4529 deletions
|
|
@ -1,6 +1,2 @@
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --"
|
xtask = "run --package xtask --"
|
||||||
|
|
||||||
[env]
|
|
||||||
# See: https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993
|
|
||||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
|
||||||
|
|
|
||||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
|
|
@ -4,6 +4,8 @@ updates:
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 3
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "build(deps)"
|
prefix: "build(deps)"
|
||||||
labels:
|
labels:
|
||||||
|
|
@ -12,10 +14,16 @@ updates:
|
||||||
groups:
|
groups:
|
||||||
cargo:
|
cargo:
|
||||||
patterns: ["*"]
|
patterns: ["*"]
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 3
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "ci"
|
prefix: "ci"
|
||||||
labels:
|
labels:
|
||||||
|
|
@ -24,13 +32,17 @@ updates:
|
||||||
groups:
|
groups:
|
||||||
actions:
|
actions:
|
||||||
patterns: ["*"]
|
patterns: ["*"]
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
|
versioning-strategy: increase
|
||||||
directories:
|
directories:
|
||||||
- "/crates/npm"
|
- "/crates/npm"
|
||||||
- "/crates/eslint"
|
- "/crates/eslint"
|
||||||
- "/lib/binding_web"
|
- "/lib/binding_web"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 3
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "build(deps)"
|
prefix: "build(deps)"
|
||||||
labels:
|
labels:
|
||||||
|
|
|
||||||
25
.github/scripts/wasm_stdlib.js
vendored
Normal file
25
.github/scripts/wasm_stdlib.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
module.exports = async ({ github, context, core }) => {
|
||||||
|
if (context.eventName !== 'pull_request') return;
|
||||||
|
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
const { data: files } = await github.rest.pulls.listFiles({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const changedFiles = files.map(file => file.filename);
|
||||||
|
|
||||||
|
const wasmStdLibSrc = 'crates/language/wasm/';
|
||||||
|
const dirChanged = changedFiles.some(file => file.startsWith(wasmStdLibSrc));
|
||||||
|
|
||||||
|
if (!dirChanged) return;
|
||||||
|
|
||||||
|
const wasmStdLibHeader = 'lib/src/wasm/wasm-stdlib.h';
|
||||||
|
const requiredChanged = changedFiles.includes(wasmStdLibHeader);
|
||||||
|
|
||||||
|
if (!requiredChanged) core.setFailed(`Changes detected in ${wasmStdLibSrc} but ${wasmStdLibHeader} was not modified.`);
|
||||||
|
};
|
||||||
4
.github/workflows/backport.yml
vendored
4
.github/workflows/backport.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Create app token
|
- name: Create app token
|
||||||
uses: actions/create-github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
private-key: ${{ secrets.BACKPORT_KEY }}
|
private-key: ${{ secrets.BACKPORT_KEY }}
|
||||||
|
|
||||||
- name: Create backport PR
|
- name: Create backport PR
|
||||||
uses: korthout/backport-action@v3
|
uses: korthout/backport-action@v4
|
||||||
with:
|
with:
|
||||||
pull_title: "${pull_title}"
|
pull_title: "${pull_title}"
|
||||||
label_pattern: "^ci:backport ([^ ]+)$"
|
label_pattern: "^ci:backport ([^ ]+)$"
|
||||||
|
|
|
||||||
2
.github/workflows/bindgen.yml
vendored
2
.github/workflows/bindgen.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up stable Rust toolchain
|
- name: Set up stable Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
|
||||||
40
.github/workflows/build.yml
vendored
40
.github/workflows/build.yml
vendored
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
- { platform: windows-x64 , target: x86_64-pc-windows-msvc , os: windows-2025 }
|
- { platform: windows-x64 , target: x86_64-pc-windows-msvc , os: windows-2025 }
|
||||||
- { platform: windows-x86 , target: i686-pc-windows-msvc , os: windows-2025 }
|
- { platform: windows-x86 , target: i686-pc-windows-msvc , os: windows-2025 }
|
||||||
- { platform: macos-arm64 , target: aarch64-apple-darwin , os: macos-15 }
|
- { platform: macos-arm64 , target: aarch64-apple-darwin , os: macos-15 }
|
||||||
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-13 }
|
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-15-intel }
|
||||||
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-24.04 }
|
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-24.04 }
|
||||||
|
|
||||||
# Extra features
|
# Extra features
|
||||||
|
|
@ -68,27 +68,25 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up environment
|
- name: Set up cross-compilation
|
||||||
|
if: matrix.cross
|
||||||
run: |
|
run: |
|
||||||
printf 'EMSCRIPTEN_VERSION=%s\n' "$(<crates/loader/emscripten-version)" >> $GITHUB_ENV
|
for target in armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu powerpc64-unknown-linux-gnu; do
|
||||||
|
camel_target=${target//-/_}; target_cc=${target/-unknown/}
|
||||||
|
printf 'CC_%s=%s\n' "$camel_target" "${target_cc/v7/}-gcc"
|
||||||
|
printf 'AR_%s=%s\n' "$camel_target" "${target_cc/v7/}-ar"
|
||||||
|
printf 'CARGO_TARGET_%s_LINKER=%s\n' "${camel_target^^}" "${target_cc/v7/}-gcc"
|
||||||
|
done >> $GITHUB_ENV
|
||||||
|
{
|
||||||
|
printf 'CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUNNER=qemu-arm -L /usr/arm-linux-gnueabihf\n'
|
||||||
|
printf 'CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc64 -L /usr/powerpc64-linux-gnu\n'
|
||||||
|
} >> $GITHUB_ENV
|
||||||
|
|
||||||
if [[ '${{ matrix.platform }}' =~ ^windows ]]; then
|
- name: Get emscripten version
|
||||||
# Prevent race condition (see #2041)
|
if: contains(matrix.features, 'wasm')
|
||||||
printf 'RUST_TEST_THREADS=1\n' >> $GITHUB_ENV
|
run: printf 'EMSCRIPTEN_VERSION=%s\n' "$(<crates/loader/emscripten-version)" >> $GITHUB_ENV
|
||||||
elif [[ '${{ matrix.cross }}' == true ]]; then
|
|
||||||
for target in armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu powerpc64-unknown-linux-gnu; do
|
|
||||||
camel_target=${target//-/_}; target_cc=${target/-unknown/}
|
|
||||||
printf 'CC_%s=%s\n' "$camel_target" "${target_cc/v7/}-gcc"
|
|
||||||
printf 'AR_%s=%s\n' "$camel_target" "${target_cc/v7/}-ar"
|
|
||||||
printf 'CARGO_TARGET_%s_LINKER=%s\n' "${camel_target^^}" "${target_cc/v7/}-gcc"
|
|
||||||
done >> $GITHUB_ENV
|
|
||||||
{
|
|
||||||
printf 'CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUNNER=qemu-arm -L /usr/arm-linux-gnueabihf\n'
|
|
||||||
printf 'CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc64 -L /usr/powerpc64-linux-gnu\n'
|
|
||||||
} >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install Emscripten
|
- name: Install Emscripten
|
||||||
if: contains(matrix.features, 'wasm')
|
if: contains(matrix.features, 'wasm')
|
||||||
|
|
@ -280,7 +278,7 @@ jobs:
|
||||||
|
|
||||||
- name: Upload CLI artifact
|
- name: Upload CLI artifact
|
||||||
if: "!matrix.no-run"
|
if: "!matrix.no-run"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: tree-sitter.${{ matrix.platform }}
|
name: tree-sitter.${{ matrix.platform }}
|
||||||
path: target/${{ matrix.target }}/release/tree-sitter${{ contains(matrix.target, 'windows') && '.exe' || '' }}
|
path: target/${{ matrix.target }}/release/tree-sitter${{ contains(matrix.target, 'windows') && '.exe' || '' }}
|
||||||
|
|
@ -289,7 +287,7 @@ jobs:
|
||||||
|
|
||||||
- name: Upload Wasm artifacts
|
- name: Upload Wasm artifacts
|
||||||
if: matrix.platform == 'linux-x64'
|
if: matrix.platform == 'linux-x64'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: tree-sitter.wasm
|
name: tree-sitter.wasm
|
||||||
path: |
|
path: |
|
||||||
|
|
|
||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up stable Rust toolchain
|
- name: Set up stable Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
@ -44,3 +44,6 @@ jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
|
|
||||||
|
check-wasm-stdlib:
|
||||||
|
uses: ./.github/workflows/wasm_stdlib.yml
|
||||||
|
|
|
||||||
5
.github/workflows/docs.yml
vendored
5
.github/workflows/docs.yml
vendored
|
|
@ -3,6 +3,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths: [docs/**]
|
paths: [docs/**]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-docs:
|
deploy-docs:
|
||||||
|
|
@ -15,7 +16,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
@ -25,7 +26,7 @@ jobs:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
jq_expr='.assets[] | select(.name | contains("x86_64-unknown-linux-gnu")) | .browser_download_url'
|
jq_expr='.assets[] | select(.name | contains("x86_64-unknown-linux-gnu")) | .browser_download_url'
|
||||||
url=$(gh api repos/rust-lang/mdbook/releases/latest --jq "$jq_expr")
|
url=$(gh api repos/rust-lang/mdbook/releases/tags/v0.4.52 --jq "$jq_expr")
|
||||||
mkdir mdbook
|
mkdir mdbook
|
||||||
curl -sSL "$url" | tar -xz -C mdbook
|
curl -sSL "$url" | tar -xz -C mdbook
|
||||||
printf '%s/mdbook\n' "$PWD" >> "$GITHUB_PATH"
|
printf '%s/mdbook\n' "$PWD" >> "$GITHUB_PATH"
|
||||||
|
|
|
||||||
4
.github/workflows/nvim_ts.yml
vendored
4
.github/workflows/nvim_ts.yml
vendored
|
|
@ -28,9 +28,9 @@ jobs:
|
||||||
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim-win64\\bin\\nvim.exe' || 'nvim' }}
|
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim-win64\\bin\\nvim.exe' || 'nvim' }}
|
||||||
NVIM_TS_DIR: nvim-treesitter
|
NVIM_TS_DIR: nvim-treesitter
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: nvim-treesitter/nvim-treesitter
|
repository: nvim-treesitter/nvim-treesitter
|
||||||
path: ${{ env.NVIM_TS_DIR }}
|
path: ${{ env.NVIM_TS_DIR }}
|
||||||
|
|
|
||||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
|
|
@ -17,13 +17,15 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
permissions:
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Download build artifacts
|
- name: Download build artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
|
|
@ -47,9 +49,16 @@ jobs:
|
||||||
rm -rf artifacts
|
rm -rf artifacts
|
||||||
ls -l target/
|
ls -l target/
|
||||||
|
|
||||||
|
- name: Generate attestations
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
target/tree-sitter-*.gz
|
||||||
|
target/web-tree-sitter.tar.gz
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
run: |-
|
run: |-
|
||||||
gh release create ${{ github.ref_name }} \
|
gh release create $GITHUB_REF_NAME \
|
||||||
target/tree-sitter-*.gz \
|
target/tree-sitter-*.gz \
|
||||||
target/web-tree-sitter.tar.gz
|
target/web-tree-sitter.tar.gz
|
||||||
env:
|
env:
|
||||||
|
|
@ -58,35 +67,47 @@ jobs:
|
||||||
crates_io:
|
crates_io:
|
||||||
name: Publish packages to Crates.io
|
name: Publish packages to Crates.io
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
environment: crates
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
needs: release
|
needs: release
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Set up registry token
|
||||||
|
id: auth
|
||||||
|
uses: rust-lang/crates-io-auth-action@v1
|
||||||
|
|
||||||
- name: Publish crates to Crates.io
|
- name: Publish crates to Crates.io
|
||||||
uses: katyo/publish-crates@v2
|
uses: katyo/publish-crates@v2
|
||||||
with:
|
with:
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ steps.auth.outputs.token }}
|
||||||
|
|
||||||
npm:
|
npm:
|
||||||
name: Publish packages to npmjs.com
|
name: Publish packages to npmjs.com
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
environment: npm
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
needs: release
|
needs: release
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
directory: [cli/npm, lib/binding_web]
|
directory: [crates/cli/npm, lib/binding_web]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
registry-url: https://registry.npmjs.org
|
registry-url: https://registry.npmjs.org
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
|
|
@ -106,5 +127,3 @@ jobs:
|
||||||
- name: Publish to npmjs.com
|
- name: Publish to npmjs.com
|
||||||
working-directory: ${{ matrix.directory }}
|
working-directory: ${{ matrix.directory }}
|
||||||
run: npm publish
|
run: npm publish
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
|
||||||
4
.github/workflows/response.yml
vendored
4
.github/workflows/response.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout script
|
- name: Checkout script
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/scripts/close_unresponsive.js
|
sparse-checkout: .github/scripts/close_unresponsive.js
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout script
|
- name: Checkout script
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/scripts/remove_response_label.js
|
sparse-checkout: .github/scripts/remove_response_label.js
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
|
||||||
2
.github/workflows/reviewers_remove.yml
vendored
2
.github/workflows/reviewers_remove.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout script
|
- name: Checkout script
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/scripts/reviewers_remove.js
|
sparse-checkout: .github/scripts/reviewers_remove.js
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
|
||||||
2
.github/workflows/sanitize.yml
vendored
2
.github/workflows/sanitize.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
TREE_SITTER: ${{ github.workspace }}/target/release/tree-sitter
|
TREE_SITTER: ${{ github.workspace }}/target/release/tree-sitter
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install UBSAN library
|
- name: Install UBSAN library
|
||||||
run: sudo apt-get update -y && sudo apt-get install -y libubsan1
|
run: sudo apt-get update -y && sudo apt-get install -y libubsan1
|
||||||
|
|
|
||||||
2
.github/workflows/spam.yml
vendored
2
.github/workflows/spam.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
if: github.event.label.name == 'spam'
|
if: github.event.label.name == 'spam'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout script
|
- name: Checkout script
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/scripts/close_spam.js
|
sparse-checkout: .github/scripts/close_spam.js
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
|
||||||
2
.github/workflows/wasm_exports.yml
vendored
2
.github/workflows/wasm_exports.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up stable Rust toolchain
|
- name: Set up stable Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
|
||||||
19
.github/workflows/wasm_stdlib.yml
vendored
Normal file
19
.github/workflows/wasm_stdlib.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
name: Check Wasm Stdlib build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Check directory changes
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const scriptPath = `${process.env.GITHUB_WORKSPACE}/.github/scripts/wasm_stdlib.js`;
|
||||||
|
const script = require(scriptPath);
|
||||||
|
return script({ github, context, core });
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
project(tree-sitter
|
project(tree-sitter
|
||||||
VERSION "0.26.0"
|
VERSION "0.27.0"
|
||||||
DESCRIPTION "An incremental parsing system for programming tools"
|
DESCRIPTION "An incremental parsing system for programming tools"
|
||||||
HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/"
|
HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/"
|
||||||
LANGUAGES C)
|
LANGUAGES C)
|
||||||
|
|
@ -81,7 +81,7 @@ set_target_properties(tree-sitter
|
||||||
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
||||||
DEFINE_SYMBOL "")
|
DEFINE_SYMBOL "")
|
||||||
|
|
||||||
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE)
|
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE _BSD_SOURCE _DARWIN_C_SOURCE)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
|
|
||||||
907
Cargo.lock
generated
907
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
37
Cargo.toml
37
Cargo.toml
|
|
@ -20,7 +20,7 @@ authors = [
|
||||||
"Amaan Qureshi <amaanq12@gmail.com>",
|
"Amaan Qureshi <amaanq12@gmail.com>",
|
||||||
]
|
]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.84"
|
rust-version = "1.85"
|
||||||
homepage = "https://tree-sitter.github.io/tree-sitter"
|
homepage = "https://tree-sitter.github.io/tree-sitter"
|
||||||
repository = "https://github.com/tree-sitter/tree-sitter"
|
repository = "https://github.com/tree-sitter/tree-sitter"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
@ -103,11 +103,11 @@ codegen-units = 256
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
ansi_colours = "1.2.3"
|
ansi_colours = "1.2.3"
|
||||||
anstyle = "1.0.11"
|
anstyle = "1.0.13"
|
||||||
anyhow = "1.0.99"
|
anyhow = "1.0.100"
|
||||||
bstr = "1.12.0"
|
bstr = "1.12.0"
|
||||||
cc = "1.2.37"
|
cc = "1.2.53"
|
||||||
clap = { version = "4.5.45", features = [
|
clap = { version = "4.5.54", features = [
|
||||||
"cargo",
|
"cargo",
|
||||||
"derive",
|
"derive",
|
||||||
"env",
|
"env",
|
||||||
|
|
@ -115,46 +115,49 @@ clap = { version = "4.5.45", features = [
|
||||||
"string",
|
"string",
|
||||||
"unstable-styles",
|
"unstable-styles",
|
||||||
] }
|
] }
|
||||||
clap_complete = "4.5.57"
|
clap_complete = "4.5.65"
|
||||||
clap_complete_nushell = "4.5.8"
|
clap_complete_nushell = "4.5.10"
|
||||||
|
crc32fast = "1.5.0"
|
||||||
ctor = "0.2.9"
|
ctor = "0.2.9"
|
||||||
ctrlc = { version = "3.5.0", features = ["termination"] }
|
ctrlc = { version = "3.5.0", features = ["termination"] }
|
||||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||||
etcetera = "0.10.0"
|
etcetera = "0.11.0"
|
||||||
fs4 = "0.12.0"
|
fs4 = "0.12.0"
|
||||||
glob = "0.3.3"
|
glob = "0.3.3"
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
html-escape = "0.2.13"
|
html-escape = "0.2.13"
|
||||||
indexmap = "2.11.1"
|
indexmap = "2.12.1"
|
||||||
indoc = "2.0.6"
|
indoc = "2.0.6"
|
||||||
libloading = "0.8.8"
|
libloading = "0.9.0"
|
||||||
log = { version = "0.4.28", features = ["std"] }
|
log = { version = "0.4.28", features = ["std"] }
|
||||||
memchr = "2.7.5"
|
memchr = "2.7.6"
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.11.2"
|
regex = "1.11.3"
|
||||||
regex-syntax = "0.8.6"
|
regex-syntax = "0.8.6"
|
||||||
rustc-hash = "2.1.1"
|
rustc-hash = "2.1.1"
|
||||||
|
schemars = "1.0.5"
|
||||||
semver = { version = "1.0.27", features = ["serde"] }
|
semver = { version = "1.0.27", features = ["serde"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.145", features = ["preserve_order"] }
|
serde_json = { version = "1.0.149", features = ["preserve_order"] }
|
||||||
similar = "2.7.0"
|
similar = "2.7.0"
|
||||||
smallbitvec = "2.6.0"
|
smallbitvec = "2.6.0"
|
||||||
streaming-iterator = "0.1.9"
|
streaming-iterator = "0.1.9"
|
||||||
tempfile = "3.22.0"
|
tempfile = "3.23.0"
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.17"
|
||||||
tiny_http = "0.12.0"
|
tiny_http = "0.12.0"
|
||||||
topological-sort = "0.2.2"
|
topological-sort = "0.2.2"
|
||||||
unindent = "0.2.4"
|
unindent = "0.2.4"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
wasmparser = "0.229.0"
|
wasmparser = "0.243.0"
|
||||||
webbrowser = "1.0.5"
|
webbrowser = "1.0.5"
|
||||||
|
|
||||||
tree-sitter = { version = "0.27.0", path = "./lib" }
|
tree-sitter = { version = "0.27.0", path = "./lib" }
|
||||||
tree-sitter-generate = { version = "0.27.0", path = "./crates/generate" }
|
tree-sitter-generate = { version = "0.27.0", path = "./crates/generate" }
|
||||||
tree-sitter-language = { path = "./crates/language" }
|
|
||||||
tree-sitter-loader = { version = "0.27.0", path = "./crates/loader" }
|
tree-sitter-loader = { version = "0.27.0", path = "./crates/loader" }
|
||||||
tree-sitter-config = { version = "0.27.0", path = "./crates/config" }
|
tree-sitter-config = { version = "0.27.0", path = "./crates/config" }
|
||||||
tree-sitter-highlight = { version = "0.27.0", path = "./crates/highlight" }
|
tree-sitter-highlight = { version = "0.27.0", path = "./crates/highlight" }
|
||||||
tree-sitter-tags = { version = "0.27.0", path = "./crates/tags" }
|
tree-sitter-tags = { version = "0.27.0", path = "./crates/tags" }
|
||||||
|
|
||||||
|
tree-sitter-language = { version = "0.1", path = "./crates/language" }
|
||||||
|
|
|
||||||
4
Makefile
4
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION := 0.26.0
|
VERSION := 0.27.0
|
||||||
DESCRIPTION := An incremental parsing system for programming tools
|
DESCRIPTION := An incremental parsing system for programming tools
|
||||||
HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
|
HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ OBJ := $(SRC:.c=.o)
|
||||||
ARFLAGS := rcs
|
ARFLAGS := rcs
|
||||||
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
|
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
|
||||||
override CFLAGS += -std=c11 -fPIC -fvisibility=hidden
|
override CFLAGS += -std=c11 -fPIC -fvisibility=hidden
|
||||||
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE
|
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_DARWIN_C_SOURCE
|
||||||
override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include
|
override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include
|
||||||
|
|
||||||
# ABI versioning
|
# ABI versioning
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ let package = Package(
|
||||||
.headerSearchPath("src"),
|
.headerSearchPath("src"),
|
||||||
.define("_POSIX_C_SOURCE", to: "200112L"),
|
.define("_POSIX_C_SOURCE", to: "200112L"),
|
||||||
.define("_DEFAULT_SOURCE"),
|
.define("_DEFAULT_SOURCE"),
|
||||||
|
.define("_BSD_SOURCE"),
|
||||||
|
.define("_DARWIN_C_SOURCE"),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
cLanguageStandard: .c11
|
cLanguageStandard: .c11
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
|
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
|
||||||
lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
|
lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
|
||||||
|
lib.root_module.addCMacro("_BSD_SOURCE", "");
|
||||||
|
lib.root_module.addCMacro("_DARWIN_C_SOURCE", "");
|
||||||
|
|
||||||
if (wasm) {
|
if (wasm) {
|
||||||
if (b.lazyDependency(wasmtimeDep(target.result), .{})) |wasmtime| {
|
if (b.lazyDependency(wasmtimeDep(target.result), .{})) |wasmtime| {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
.{
|
.{
|
||||||
.name = .tree_sitter,
|
.name = .tree_sitter,
|
||||||
.fingerprint = 0x841224b447ac0d4f,
|
.fingerprint = 0x841224b447ac0d4f,
|
||||||
.version = "0.26.0",
|
.version = "0.27.0",
|
||||||
.minimum_zig_version = "0.14.1",
|
.minimum_zig_version = "0.14.1",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ bstr.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
clap_complete.workspace = true
|
clap_complete.workspace = true
|
||||||
clap_complete_nushell.workspace = true
|
clap_complete_nushell.workspace = true
|
||||||
|
crc32fast.workspace = true
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
ctrlc.workspace = true
|
ctrlc.workspace = true
|
||||||
dialoguer.workspace = true
|
dialoguer.workspace = true
|
||||||
|
|
@ -53,11 +54,13 @@ log.workspace = true
|
||||||
memchr.workspace = true
|
memchr.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
semver.workspace = true
|
semver.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
similar.workspace = true
|
similar.workspace = true
|
||||||
streaming-iterator.workspace = true
|
streaming-iterator.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
tiny_http.workspace = true
|
tiny_http.workspace = true
|
||||||
walkdir.workspace = true
|
walkdir.workspace = true
|
||||||
wasmparser.workspace = true
|
wasmparser.workspace = true
|
||||||
|
|
@ -72,7 +75,7 @@ tree-sitter-tags.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
encoding_rs = "0.8.35"
|
encoding_rs = "0.8.35"
|
||||||
widestring = "1.2.0"
|
widestring = "1.2.1"
|
||||||
tree_sitter_proc_macro = { path = "src/tests/proc_macro", package = "tree-sitter-tests-proc-macro" }
|
tree_sitter_proc_macro = { path = "src/tests/proc_macro", package = "tree-sitter-tests-proc-macro" }
|
||||||
|
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
[npmjs.com]: https://www.npmjs.org/package/tree-sitter-cli
|
[npmjs.com]: https://www.npmjs.org/package/tree-sitter-cli
|
||||||
[npmjs.com badge]: https://img.shields.io/npm/v/tree-sitter-cli.svg?color=%23BF4A4A
|
[npmjs.com badge]: https://img.shields.io/npm/v/tree-sitter-cli.svg?color=%23BF4A4A
|
||||||
|
|
||||||
The Tree-sitter CLI allows you to develop, test, and use Tree-sitter grammars from the command line. It works on `MacOS`, `Linux`, and `Windows`.
|
The Tree-sitter CLI allows you to develop, test, and use Tree-sitter grammars from the command line. It works on `MacOS`,
|
||||||
|
`Linux`, and `Windows`.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
|
@ -34,9 +35,11 @@ The `tree-sitter` binary itself has no dependencies, but specific commands have
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
* `generate` - The `tree-sitter generate` command will generate a Tree-sitter parser based on the grammar in the current working directory. See [the documentation] for more information.
|
* `generate` - The `tree-sitter generate` command will generate a Tree-sitter parser based on the grammar in the current
|
||||||
|
working directory. See [the documentation] for more information.
|
||||||
|
|
||||||
* `test` - The `tree-sitter test` command will run the unit tests for the Tree-sitter parser in the current working directory. See [the documentation] for more information.
|
* `test` - The `tree-sitter test` command will run the unit tests for the Tree-sitter parser in the current working directory.
|
||||||
|
See [the documentation] for more information.
|
||||||
|
|
||||||
* `parse` - The `tree-sitter parse` command will parse a file (or list of files) using Tree-sitter parsers.
|
* `parse` - The `tree-sitter parse` command will parse a file (or list of files) using Tree-sitter parsers.
|
||||||
|
|
||||||
|
|
|
||||||
6
crates/cli/eslint/package-lock.json
generated
6
crates/cli/eslint/package-lock.json
generated
|
|
@ -805,9 +805,9 @@
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
1
crates/cli/npm/dsl.d.ts
vendored
1
crates/cli/npm/dsl.d.ts
vendored
|
|
@ -29,6 +29,7 @@ type Rule =
|
||||||
| PrecRule
|
| PrecRule
|
||||||
| Repeat1Rule
|
| Repeat1Rule
|
||||||
| RepeatRule
|
| RepeatRule
|
||||||
|
| ReservedRule
|
||||||
| SeqRule
|
| SeqRule
|
||||||
| StringRule
|
| StringRule
|
||||||
| SymbolRule<string>
|
| SymbolRule<string>
|
||||||
|
|
|
||||||
4
crates/cli/npm/package-lock.json
generated
4
crates/cli/npm/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "tree-sitter-cli",
|
"name": "tree-sitter-cli",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tree-sitter-cli",
|
"name": "tree-sitter-cli",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tree-sitter-cli",
|
"name": "tree-sitter-cli",
|
||||||
"version": "0.26.0",
|
"version": "0.27.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Max Brunsfeld",
|
"name": "Max Brunsfeld",
|
||||||
"email": "maxbrunsfeld@gmail.com"
|
"email": "maxbrunsfeld@gmail.com"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::{
|
||||||
random::Rand,
|
random::Rand,
|
||||||
},
|
},
|
||||||
parse::perform_edit,
|
parse::perform_edit,
|
||||||
test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields, TestEntry},
|
test::{parse_tests, strip_sexp_fields, DiffKey, TestDiff, TestEntry},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static LOG_ENABLED: LazyLock<bool> = LazyLock::new(|| env::var("TREE_SITTER_LOG").is_ok());
|
pub static LOG_ENABLED: LazyLock<bool> = LazyLock::new(|| env::var("TREE_SITTER_LOG").is_ok());
|
||||||
|
|
@ -183,8 +183,8 @@ pub fn fuzz_language_corpus(
|
||||||
|
|
||||||
if actual_output != test.output {
|
if actual_output != test.output {
|
||||||
println!("Incorrect initial parse for {test_name}");
|
println!("Incorrect initial parse for {test_name}");
|
||||||
print_diff_key();
|
DiffKey::print();
|
||||||
print_diff(&actual_output, &test.output, true);
|
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||||
println!();
|
println!();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -276,8 +276,8 @@ pub fn fuzz_language_corpus(
|
||||||
|
|
||||||
if actual_output != test.output && !test.error {
|
if actual_output != test.output && !test.error {
|
||||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||||
print_diff_key();
|
DiffKey::print();
|
||||||
print_diff(&actual_output, &test.output, true);
|
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||||
println!();
|
println!();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,20 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use crc32fast::hash as crc32;
|
||||||
use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
|
use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::{formatdoc, indoc};
|
||||||
use log::warn;
|
use log::info;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use tree_sitter_generate::write_file;
|
use tree_sitter_generate::write_file;
|
||||||
use tree_sitter_loader::{Author, Bindings, Grammar, Links, Metadata, PathsJSON, TreeSitterJSON};
|
use tree_sitter_loader::{
|
||||||
|
Author, Bindings, Grammar, Links, Metadata, PathsJSON, TreeSitterJSON,
|
||||||
|
DEFAULT_HIGHLIGHTS_QUERY_FILE_NAME, DEFAULT_INJECTIONS_QUERY_FILE_NAME,
|
||||||
|
DEFAULT_LOCALS_QUERY_FILE_NAME, DEFAULT_TAGS_QUERY_FILE_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
|
const CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
const CLI_VERSION_PLACEHOLDER: &str = "CLI_VERSION";
|
const CLI_VERSION_PLACEHOLDER: &str = "CLI_VERSION";
|
||||||
|
|
@ -30,9 +36,12 @@ const PARSER_CLASS_NAME_PLACEHOLDER: &str = "PARSER_CLASS_NAME";
|
||||||
|
|
||||||
const PARSER_DESCRIPTION_PLACEHOLDER: &str = "PARSER_DESCRIPTION";
|
const PARSER_DESCRIPTION_PLACEHOLDER: &str = "PARSER_DESCRIPTION";
|
||||||
const PARSER_LICENSE_PLACEHOLDER: &str = "PARSER_LICENSE";
|
const PARSER_LICENSE_PLACEHOLDER: &str = "PARSER_LICENSE";
|
||||||
|
const PARSER_NS_PLACEHOLDER: &str = "PARSER_NS";
|
||||||
|
const PARSER_NS_CLEANED_PLACEHOLDER: &str = "PARSER_NS_CLEANED";
|
||||||
const PARSER_URL_PLACEHOLDER: &str = "PARSER_URL";
|
const PARSER_URL_PLACEHOLDER: &str = "PARSER_URL";
|
||||||
const PARSER_URL_STRIPPED_PLACEHOLDER: &str = "PARSER_URL_STRIPPED";
|
const PARSER_URL_STRIPPED_PLACEHOLDER: &str = "PARSER_URL_STRIPPED";
|
||||||
const PARSER_VERSION_PLACEHOLDER: &str = "PARSER_VERSION";
|
const PARSER_VERSION_PLACEHOLDER: &str = "PARSER_VERSION";
|
||||||
|
const PARSER_FINGERPRINT_PLACEHOLDER: &str = "PARSER_FINGERPRINT";
|
||||||
|
|
||||||
const AUTHOR_NAME_PLACEHOLDER: &str = "PARSER_AUTHOR_NAME";
|
const AUTHOR_NAME_PLACEHOLDER: &str = "PARSER_AUTHOR_NAME";
|
||||||
const AUTHOR_EMAIL_PLACEHOLDER: &str = "PARSER_AUTHOR_EMAIL";
|
const AUTHOR_EMAIL_PLACEHOLDER: &str = "PARSER_AUTHOR_EMAIL";
|
||||||
|
|
@ -51,12 +60,22 @@ const AUTHOR_BLOCK_RS: &str = "\nauthors = [";
|
||||||
const AUTHOR_NAME_PLACEHOLDER_RS: &str = "PARSER_AUTHOR_NAME";
|
const AUTHOR_NAME_PLACEHOLDER_RS: &str = "PARSER_AUTHOR_NAME";
|
||||||
const AUTHOR_EMAIL_PLACEHOLDER_RS: &str = " PARSER_AUTHOR_EMAIL";
|
const AUTHOR_EMAIL_PLACEHOLDER_RS: &str = " PARSER_AUTHOR_EMAIL";
|
||||||
|
|
||||||
|
const AUTHOR_BLOCK_JAVA: &str = "\n <developer>";
|
||||||
|
const AUTHOR_NAME_PLACEHOLDER_JAVA: &str = "\n <name>PARSER_AUTHOR_NAME</name>";
|
||||||
|
const AUTHOR_EMAIL_PLACEHOLDER_JAVA: &str = "\n <email>PARSER_AUTHOR_EMAIL</email>";
|
||||||
|
const AUTHOR_URL_PLACEHOLDER_JAVA: &str = "\n <url>PARSER_AUTHOR_URL</url>";
|
||||||
|
|
||||||
const AUTHOR_BLOCK_GRAMMAR: &str = "\n * @author ";
|
const AUTHOR_BLOCK_GRAMMAR: &str = "\n * @author ";
|
||||||
const AUTHOR_NAME_PLACEHOLDER_GRAMMAR: &str = "PARSER_AUTHOR_NAME";
|
const AUTHOR_NAME_PLACEHOLDER_GRAMMAR: &str = "PARSER_AUTHOR_NAME";
|
||||||
const AUTHOR_EMAIL_PLACEHOLDER_GRAMMAR: &str = " PARSER_AUTHOR_EMAIL";
|
const AUTHOR_EMAIL_PLACEHOLDER_GRAMMAR: &str = " PARSER_AUTHOR_EMAIL";
|
||||||
|
|
||||||
const FUNDING_URL_PLACEHOLDER: &str = "FUNDING_URL";
|
const FUNDING_URL_PLACEHOLDER: &str = "FUNDING_URL";
|
||||||
|
|
||||||
|
const HIGHLIGHTS_QUERY_PATH_PLACEHOLDER: &str = "HIGHLIGHTS_QUERY_PATH";
|
||||||
|
const INJECTIONS_QUERY_PATH_PLACEHOLDER: &str = "INJECTIONS_QUERY_PATH";
|
||||||
|
const LOCALS_QUERY_PATH_PLACEHOLDER: &str = "LOCALS_QUERY_PATH";
|
||||||
|
const TAGS_QUERY_PATH_PLACEHOLDER: &str = "TAGS_QUERY_PATH";
|
||||||
|
|
||||||
const GRAMMAR_JS_TEMPLATE: &str = include_str!("./templates/grammar.js");
|
const GRAMMAR_JS_TEMPLATE: &str = include_str!("./templates/grammar.js");
|
||||||
const PACKAGE_JSON_TEMPLATE: &str = include_str!("./templates/package.json");
|
const PACKAGE_JSON_TEMPLATE: &str = include_str!("./templates/package.json");
|
||||||
const GITIGNORE_TEMPLATE: &str = include_str!("./templates/gitignore");
|
const GITIGNORE_TEMPLATE: &str = include_str!("./templates/gitignore");
|
||||||
|
|
@ -95,12 +114,16 @@ const TEST_BINDING_PY_TEMPLATE: &str = include_str!("./templates/test_binding.py
|
||||||
const PACKAGE_SWIFT_TEMPLATE: &str = include_str!("./templates/package.swift");
|
const PACKAGE_SWIFT_TEMPLATE: &str = include_str!("./templates/package.swift");
|
||||||
const TESTS_SWIFT_TEMPLATE: &str = include_str!("./templates/tests.swift");
|
const TESTS_SWIFT_TEMPLATE: &str = include_str!("./templates/tests.swift");
|
||||||
|
|
||||||
|
const POM_XML_TEMPLATE: &str = include_str!("./templates/pom.xml");
|
||||||
|
const BINDING_JAVA_TEMPLATE: &str = include_str!("./templates/binding.java");
|
||||||
|
const TEST_JAVA_TEMPLATE: &str = include_str!("./templates/test.java");
|
||||||
|
|
||||||
const BUILD_ZIG_TEMPLATE: &str = include_str!("./templates/build.zig");
|
const BUILD_ZIG_TEMPLATE: &str = include_str!("./templates/build.zig");
|
||||||
const BUILD_ZIG_ZON_TEMPLATE: &str = include_str!("./templates/build.zig.zon");
|
const BUILD_ZIG_ZON_TEMPLATE: &str = include_str!("./templates/build.zig.zon");
|
||||||
const ROOT_ZIG_TEMPLATE: &str = include_str!("./templates/root.zig");
|
const ROOT_ZIG_TEMPLATE: &str = include_str!("./templates/root.zig");
|
||||||
const TEST_ZIG_TEMPLATE: &str = include_str!("./templates/test.zig");
|
const TEST_ZIG_TEMPLATE: &str = include_str!("./templates/test.zig");
|
||||||
|
|
||||||
const TREE_SITTER_JSON_SCHEMA: &str =
|
pub const TREE_SITTER_JSON_SCHEMA: &str =
|
||||||
"https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json";
|
"https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
|
@ -122,6 +145,7 @@ pub struct JsonConfigOpts {
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
|
pub namespace: Option<String>,
|
||||||
pub bindings: Bindings,
|
pub bindings: Bindings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +178,7 @@ impl JsonConfigOpts {
|
||||||
authors: Some(vec![Author {
|
authors: Some(vec![Author {
|
||||||
name: self.author,
|
name: self.author,
|
||||||
email: self.email,
|
email: self.email,
|
||||||
url: self.url.map(|url| url.to_string()),
|
url: self.url,
|
||||||
}]),
|
}]),
|
||||||
links: Some(Links {
|
links: Some(Links {
|
||||||
repository: self.repository.unwrap_or_else(|| {
|
repository: self.repository.unwrap_or_else(|| {
|
||||||
|
|
@ -162,7 +186,7 @@ impl JsonConfigOpts {
|
||||||
}),
|
}),
|
||||||
funding: self.funding,
|
funding: self.funding,
|
||||||
}),
|
}),
|
||||||
namespace: None,
|
namespace: self.namespace,
|
||||||
},
|
},
|
||||||
bindings: self.bindings,
|
bindings: self.bindings,
|
||||||
}
|
}
|
||||||
|
|
@ -185,6 +209,7 @@ impl Default for JsonConfigOpts {
|
||||||
author: String::new(),
|
author: String::new(),
|
||||||
email: None,
|
email: None,
|
||||||
url: None,
|
url: None,
|
||||||
|
namespace: None,
|
||||||
bindings: Bindings::default(),
|
bindings: Bindings::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +227,11 @@ struct GenerateOpts<'a> {
|
||||||
camel_parser_name: &'a str,
|
camel_parser_name: &'a str,
|
||||||
title_parser_name: &'a str,
|
title_parser_name: &'a str,
|
||||||
class_name: &'a str,
|
class_name: &'a str,
|
||||||
|
highlights_query_path: &'a str,
|
||||||
|
injections_query_path: &'a str,
|
||||||
|
locals_query_path: &'a str,
|
||||||
|
tags_query_path: &'a str,
|
||||||
|
namespace: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_grammar_files(
|
pub fn generate_grammar_files(
|
||||||
|
|
@ -252,6 +282,11 @@ pub fn generate_grammar_files(
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| format!("TreeSitter{}", language_name.to_upper_camel_case()));
|
.unwrap_or_else(|| format!("TreeSitter{}", language_name.to_upper_camel_case()));
|
||||||
|
|
||||||
|
let default_highlights_path = Path::new("queries").join(DEFAULT_HIGHLIGHTS_QUERY_FILE_NAME);
|
||||||
|
let default_injections_path = Path::new("queries").join(DEFAULT_INJECTIONS_QUERY_FILE_NAME);
|
||||||
|
let default_locals_path = Path::new("queries").join(DEFAULT_LOCALS_QUERY_FILE_NAME);
|
||||||
|
let default_tags_path = Path::new("queries").join(DEFAULT_TAGS_QUERY_FILE_NAME);
|
||||||
|
|
||||||
let generate_opts = GenerateOpts {
|
let generate_opts = GenerateOpts {
|
||||||
author_name: authors
|
author_name: authors
|
||||||
.map(|a| a.first().map(|a| a.name.as_str()))
|
.map(|a| a.first().map(|a| a.name.as_str()))
|
||||||
|
|
@ -278,6 +313,19 @@ pub fn generate_grammar_files(
|
||||||
camel_parser_name: &camel_name,
|
camel_parser_name: &camel_name,
|
||||||
title_parser_name: &title_name,
|
title_parser_name: &title_name,
|
||||||
class_name: &class_name,
|
class_name: &class_name,
|
||||||
|
highlights_query_path: tree_sitter_config.grammars[0]
|
||||||
|
.highlights
|
||||||
|
.to_variable_value(&default_highlights_path),
|
||||||
|
injections_query_path: tree_sitter_config.grammars[0]
|
||||||
|
.injections
|
||||||
|
.to_variable_value(&default_injections_path),
|
||||||
|
locals_query_path: tree_sitter_config.grammars[0]
|
||||||
|
.locals
|
||||||
|
.to_variable_value(&default_locals_path),
|
||||||
|
tags_query_path: tree_sitter_config.grammars[0]
|
||||||
|
.tags
|
||||||
|
.to_variable_value(&default_tags_path),
|
||||||
|
namespace: tree_sitter_config.metadata.namespace.as_deref(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create package.json
|
// Create package.json
|
||||||
|
|
@ -304,11 +352,11 @@ pub fn generate_grammar_files(
|
||||||
"tree-sitter-cli":"#},
|
"tree-sitter-cli":"#},
|
||||||
indoc! {r#"
|
indoc! {r#"
|
||||||
"prebuildify": "^6.0.1",
|
"prebuildify": "^6.0.1",
|
||||||
"tree-sitter": "^0.22.4",
|
"tree-sitter": "^0.25.0",
|
||||||
"tree-sitter-cli":"#},
|
"tree-sitter-cli":"#},
|
||||||
);
|
);
|
||||||
if !contents.contains("module") {
|
if !contents.contains("module") {
|
||||||
warn!("Updating package.json");
|
info!("Migrating package.json to ESM");
|
||||||
contents = contents.replace(
|
contents = contents.replace(
|
||||||
r#""repository":"#,
|
r#""repository":"#,
|
||||||
indoc! {r#"
|
indoc! {r#"
|
||||||
|
|
@ -330,6 +378,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if contents.contains("module.exports") {
|
if contents.contains("module.exports") {
|
||||||
|
info!("Migrating grammars.js to ESM");
|
||||||
contents = contents.replace("module.exports =", "export default");
|
contents = contents.replace("module.exports =", "export default");
|
||||||
write_file(path, contents)?;
|
write_file(path, contents)?;
|
||||||
}
|
}
|
||||||
|
|
@ -345,10 +394,16 @@ pub fn generate_grammar_files(
|
||||||
allow_update,
|
allow_update,
|
||||||
|path| generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("Zig artifacts") {
|
if !contents.contains("Zig artifacts") {
|
||||||
warn!("Replacing .gitignore");
|
info!("Adding zig entries to .gitignore");
|
||||||
generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts)?;
|
contents.push('\n');
|
||||||
|
contents.push_str(indoc! {"
|
||||||
|
# Zig artifacts
|
||||||
|
.zig-cache/
|
||||||
|
zig-cache/
|
||||||
|
zig-out/
|
||||||
|
"});
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
|
@ -361,8 +416,13 @@ pub fn generate_grammar_files(
|
||||||
|path| generate_file(path, GITATTRIBUTES_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, GITATTRIBUTES_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
contents = contents.replace("bindings/c/* ", "bindings/c/** ");
|
let c_bindings_entry = "bindings/c/* ";
|
||||||
|
if contents.contains(c_bindings_entry) {
|
||||||
|
info!("Updating c bindings entry in .gitattributes");
|
||||||
|
contents = contents.replace(c_bindings_entry, "bindings/c/** ");
|
||||||
|
}
|
||||||
if !contents.contains("Zig bindings") {
|
if !contents.contains("Zig bindings") {
|
||||||
|
info!("Adding zig entries to .gitattributes");
|
||||||
contents.push('\n');
|
contents.push('\n');
|
||||||
contents.push_str(indoc! {"
|
contents.push_str(indoc! {"
|
||||||
# Zig bindings
|
# Zig bindings
|
||||||
|
|
@ -385,8 +445,48 @@ pub fn generate_grammar_files(
|
||||||
// Generate Rust bindings
|
// Generate Rust bindings
|
||||||
if tree_sitter_config.bindings.rust {
|
if tree_sitter_config.bindings.rust {
|
||||||
missing_path(bindings_dir.join("rust"), create_dir)?.apply(|path| {
|
missing_path(bindings_dir.join("rust"), create_dir)?.apply(|path| {
|
||||||
missing_path(path.join("lib.rs"), |path| {
|
missing_path_else(path.join("lib.rs"), allow_update, |path| {
|
||||||
generate_file(path, LIB_RS_TEMPLATE, language_name, &generate_opts)
|
generate_file(path, LIB_RS_TEMPLATE, language_name, &generate_opts)
|
||||||
|
}, |path| {
|
||||||
|
let mut contents = fs::read_to_string(path)?;
|
||||||
|
if !contents.contains("#[cfg(with_highlights_query)]") {
|
||||||
|
info!("Updating query constants in bindings/rust/lib.rs");
|
||||||
|
let replacement = indoc! {r#"
|
||||||
|
#[cfg(with_highlights_query)]
|
||||||
|
/// The syntax highlighting query for this grammar.
|
||||||
|
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../HIGHLIGHTS_QUERY_PATH");
|
||||||
|
|
||||||
|
#[cfg(with_injections_query)]
|
||||||
|
/// The language injection query for this grammar.
|
||||||
|
pub const INJECTIONS_QUERY: &str = include_str!("../../INJECTIONS_QUERY_PATH");
|
||||||
|
|
||||||
|
#[cfg(with_locals_query)]
|
||||||
|
/// The local variable query for this grammar.
|
||||||
|
pub const LOCALS_QUERY: &str = include_str!("../../LOCALS_QUERY_PATH");
|
||||||
|
|
||||||
|
#[cfg(with_tags_query)]
|
||||||
|
/// The symbol tagging query for this grammar.
|
||||||
|
pub const TAGS_QUERY: &str = include_str!("../../TAGS_QUERY_PATH");
|
||||||
|
"#}
|
||||||
|
.replace("HIGHLIGHTS_QUERY_PATH", generate_opts.highlights_query_path)
|
||||||
|
.replace("INJECTIONS_QUERY_PATH", generate_opts.injections_query_path)
|
||||||
|
.replace("LOCALS_QUERY_PATH", generate_opts.locals_query_path)
|
||||||
|
.replace("TAGS_QUERY_PATH", generate_opts.tags_query_path);
|
||||||
|
contents = contents
|
||||||
|
.replace(
|
||||||
|
indoc! {r#"
|
||||||
|
// NOTE: uncomment these to include any queries that this grammar contains:
|
||||||
|
|
||||||
|
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
||||||
|
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
|
||||||
|
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
|
||||||
|
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
|
||||||
|
"#},
|
||||||
|
&replacement,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
write_file(path, contents)?;
|
||||||
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
missing_path_else(
|
missing_path_else(
|
||||||
|
|
@ -394,37 +494,76 @@ pub fn generate_grammar_files(
|
||||||
allow_update,
|
allow_update,
|
||||||
|path| generate_file(path, BUILD_RS_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, BUILD_RS_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let replacement = indoc!{r#"
|
|
||||||
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 indented_replacement = replacement
|
|
||||||
.lines()
|
|
||||||
.map(|line| if line.is_empty() { line.to_string() } else { format!(" {line}") })
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("wasm32-unknown-unknown") {
|
if !contents.contains("wasm32-unknown-unknown") {
|
||||||
contents = contents.replace(r#" c_config.flag("-utf-8");"#, &indented_replacement);
|
info!("Adding wasm32-unknown-unknown target to bindings/rust/build.rs");
|
||||||
|
let replacement = indoc!{r#"
|
||||||
|
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"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
"#}
|
||||||
|
.lines()
|
||||||
|
.map(|line| if line.is_empty() { line.to_string() } else { format!(" {line}") })
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
contents = contents.replace(r#" c_config.flag("-utf-8");"#, &replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Introduce configuration variables for dynamic query inclusion
|
||||||
|
if !contents.contains("with_highlights_query") {
|
||||||
|
info!("Adding support for dynamic query inclusion to bindings/rust/build.rs");
|
||||||
|
let replaced = indoc! {r#"
|
||||||
|
c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
|
||||||
|
}"#}
|
||||||
|
.replace("KEBAB_PARSER_NAME", &language_name.to_kebab_case());
|
||||||
|
|
||||||
|
let replacement = indoc! {r#"
|
||||||
|
c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
|
||||||
|
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_highlights_query)");
|
||||||
|
if !"HIGHLIGHTS_QUERY_PATH".is_empty() && std::path::Path::new("HIGHLIGHTS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_highlights_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_injections_query)");
|
||||||
|
if !"INJECTIONS_QUERY_PATH".is_empty() && std::path::Path::new("INJECTIONS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_injections_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_locals_query)");
|
||||||
|
if !"LOCALS_QUERY_PATH".is_empty() && std::path::Path::new("LOCALS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_locals_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_tags_query)");
|
||||||
|
if !"TAGS_QUERY_PATH".is_empty() && std::path::Path::new("TAGS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_tags_query");
|
||||||
|
}
|
||||||
|
}"#}
|
||||||
|
.replace("KEBAB_PARSER_NAME", &language_name.to_kebab_case())
|
||||||
|
.replace("HIGHLIGHTS_QUERY_PATH", generate_opts.highlights_query_path)
|
||||||
|
.replace("INJECTIONS_QUERY_PATH", generate_opts.injections_query_path)
|
||||||
|
.replace("LOCALS_QUERY_PATH", generate_opts.locals_query_path)
|
||||||
|
.replace("TAGS_QUERY_PATH", generate_opts.tags_query_path);
|
||||||
|
|
||||||
|
contents = contents.replace(
|
||||||
|
&replaced,
|
||||||
|
&replacement,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
write_file(path, contents)?;
|
write_file(path, contents)?;
|
||||||
|
|
@ -446,6 +585,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if contents.contains("\"LICENSE\"") {
|
if contents.contains("\"LICENSE\"") {
|
||||||
|
info!("Adding LICENSE entry to bindings/rust/Cargo.toml");
|
||||||
write_file(path, contents.replace("\"LICENSE\"", "\"/LICENSE\""))?;
|
write_file(path, contents.replace("\"LICENSE\"", "\"/LICENSE\""))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -465,17 +605,27 @@ pub fn generate_grammar_files(
|
||||||
|path| generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("new URL") {
|
if !contents.contains("Object.defineProperty") {
|
||||||
warn!("Replacing index.js");
|
info!("Replacing index.js");
|
||||||
generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?;
|
generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
missing_path(path.join("index.d.ts"), |path| {
|
missing_path_else(
|
||||||
generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts)
|
path.join("index.d.ts"),
|
||||||
})?;
|
allow_update,
|
||||||
|
|path| generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts),
|
||||||
|
|path| {
|
||||||
|
let contents = fs::read_to_string(path)?;
|
||||||
|
if !contents.contains("export default binding") {
|
||||||
|
info!("Replacing index.d.ts");
|
||||||
|
generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
missing_path_else(
|
missing_path_else(
|
||||||
path.join("binding_test.js"),
|
path.join("binding_test.js"),
|
||||||
|
|
@ -491,7 +641,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("import") {
|
if !contents.contains("import") {
|
||||||
warn!("Replacing binding_test.js");
|
info!("Replacing binding_test.js");
|
||||||
generate_file(
|
generate_file(
|
||||||
path,
|
path,
|
||||||
BINDING_TEST_JS_TEMPLATE,
|
BINDING_TEST_JS_TEMPLATE,
|
||||||
|
|
@ -514,6 +664,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if contents.contains("fs.exists(") {
|
if contents.contains("fs.exists(") {
|
||||||
|
info!("Replacing `fs.exists` calls in binding.gyp");
|
||||||
write_file(path, contents.replace("fs.exists(", "fs.existsSync("))?;
|
write_file(path, contents.replace("fs.exists(", "fs.existsSync("))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -526,14 +677,17 @@ pub fn generate_grammar_files(
|
||||||
|
|
||||||
// Generate C bindings
|
// Generate C bindings
|
||||||
if tree_sitter_config.bindings.c {
|
if tree_sitter_config.bindings.c {
|
||||||
|
let kebab_case_name = language_name.to_kebab_case();
|
||||||
missing_path(bindings_dir.join("c"), create_dir)?.apply(|path| {
|
missing_path(bindings_dir.join("c"), create_dir)?.apply(|path| {
|
||||||
let old_file = &path.join(format!("tree-sitter-{}.h", language_name.to_kebab_case()));
|
let header_name = format!("tree-sitter-{kebab_case_name}.h");
|
||||||
|
let old_file = &path.join(&header_name);
|
||||||
if allow_update && fs::exists(old_file).unwrap_or(false) {
|
if allow_update && fs::exists(old_file).unwrap_or(false) {
|
||||||
|
info!("Removing bindings/c/{header_name}");
|
||||||
fs::remove_file(old_file)?;
|
fs::remove_file(old_file)?;
|
||||||
}
|
}
|
||||||
missing_path(path.join("tree_sitter"), create_dir)?.apply(|include_path| {
|
missing_path(path.join("tree_sitter"), create_dir)?.apply(|include_path| {
|
||||||
missing_path(
|
missing_path(
|
||||||
include_path.join(format!("tree-sitter-{}.h", language_name.to_kebab_case())),
|
include_path.join(&header_name),
|
||||||
|path| {
|
|path| {
|
||||||
generate_file(path, PARSER_NAME_H_TEMPLATE, language_name, &generate_opts)
|
generate_file(path, PARSER_NAME_H_TEMPLATE, language_name, &generate_opts)
|
||||||
},
|
},
|
||||||
|
|
@ -542,7 +696,7 @@ pub fn generate_grammar_files(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
missing_path(
|
missing_path(
|
||||||
path.join(format!("tree-sitter-{}.pc.in", language_name.to_kebab_case())),
|
path.join(format!("tree-sitter-{kebab_case_name}.pc.in")),
|
||||||
|path| {
|
|path| {
|
||||||
generate_file(
|
generate_file(
|
||||||
path,
|
path,
|
||||||
|
|
@ -562,23 +716,27 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("cd '$(DESTDIR)$(LIBDIR)' && ln -sf") {
|
if !contents.contains("cd '$(DESTDIR)$(LIBDIR)' && ln -sf") {
|
||||||
warn!("Replacing Makefile");
|
info!("Replacing Makefile");
|
||||||
generate_file(path, MAKEFILE_TEMPLATE, language_name, &generate_opts)?;
|
generate_file(path, MAKEFILE_TEMPLATE, language_name, &generate_opts)?;
|
||||||
} else {
|
} else {
|
||||||
contents = contents
|
let replaced = indoc! {r"
|
||||||
.replace(
|
$(PARSER): $(SRC_DIR)/grammar.json
|
||||||
indoc! {r"
|
$(TS) generate $^
|
||||||
$(PARSER): $(SRC_DIR)/grammar.json
|
"};
|
||||||
$(TS) generate $^
|
if contents.contains(replaced) {
|
||||||
"},
|
info!("Adding --no-parser target to Makefile");
|
||||||
indoc! {r"
|
contents = contents
|
||||||
$(SRC_DIR)/grammar.json: grammar.js
|
.replace(
|
||||||
$(TS) generate --emit=json $^
|
replaced,
|
||||||
|
indoc! {r"
|
||||||
|
$(SRC_DIR)/grammar.json: grammar.js
|
||||||
|
$(TS) generate --no-parser $^
|
||||||
|
|
||||||
$(PARSER): $(SRC_DIR)/grammar.json
|
$(PARSER): $(SRC_DIR)/grammar.json
|
||||||
$(TS) generate --emit=parser $^
|
$(TS) generate $^
|
||||||
"}
|
"}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
write_file(path, contents)?;
|
write_file(path, contents)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -590,8 +748,8 @@ pub fn generate_grammar_files(
|
||||||
allow_update,
|
allow_update,
|
||||||
|path| generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
contents = contents
|
let replaced_contents = contents
|
||||||
.replace("add_custom_target(test", "add_custom_target(ts-test")
|
.replace("add_custom_target(test", "add_custom_target(ts-test")
|
||||||
.replace(
|
.replace(
|
||||||
&formatdoc! {r#"
|
&formatdoc! {r#"
|
||||||
|
|
@ -622,21 +780,27 @@ pub fn generate_grammar_files(
|
||||||
"#},
|
"#},
|
||||||
indoc! {r#"
|
indoc! {r#"
|
||||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
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"
|
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js"
|
||||||
COMMAND "${TREE_SITTER_CLI}" generate grammar.js
|
COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser
|
||||||
--emit=json
|
|
||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
COMMENT "Generating grammar.json")
|
COMMENT "Generating grammar.json")
|
||||||
|
|
||||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
|
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"
|
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||||
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
||||||
--emit=parser --abi=${TREE_SITTER_ABI_VERSION}
|
--abi=${TREE_SITTER_ABI_VERSION}
|
||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
COMMENT "Generating parser.c")
|
COMMENT "Generating parser.c")
|
||||||
"#}
|
"#}
|
||||||
);
|
);
|
||||||
write_file(path, contents)?;
|
if !replaced_contents.eq(&contents) {
|
||||||
|
info!("Updating CMakeLists.txt");
|
||||||
|
write_file(path, replaced_contents)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -672,7 +836,8 @@ pub fn generate_grammar_files(
|
||||||
// Generate Python bindings
|
// Generate Python bindings
|
||||||
if tree_sitter_config.bindings.python {
|
if tree_sitter_config.bindings.python {
|
||||||
missing_path(bindings_dir.join("python"), create_dir)?.apply(|path| {
|
missing_path(bindings_dir.join("python"), create_dir)?.apply(|path| {
|
||||||
let lang_path = path.join(format!("tree_sitter_{}", language_name.to_snake_case()));
|
let snake_case_grammar_name = format!("tree_sitter_{}", language_name.to_snake_case());
|
||||||
|
let lang_path = path.join(&snake_case_grammar_name);
|
||||||
missing_path(&lang_path, create_dir)?;
|
missing_path(&lang_path, create_dir)?;
|
||||||
|
|
||||||
missing_path_else(
|
missing_path_else(
|
||||||
|
|
@ -682,6 +847,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("PyModuleDef_Init") {
|
if !contents.contains("PyModuleDef_Init") {
|
||||||
|
info!("Updating bindings/python/{snake_case_grammar_name}/binding.c");
|
||||||
contents = contents
|
contents = contents
|
||||||
.replace("PyModule_Create", "PyModuleDef_Init")
|
.replace("PyModule_Create", "PyModuleDef_Init")
|
||||||
.replace(
|
.replace(
|
||||||
|
|
@ -714,9 +880,21 @@ pub fn generate_grammar_files(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
missing_path(lang_path.join("__init__.py"), |path| {
|
missing_path_else(
|
||||||
generate_file(path, INIT_PY_TEMPLATE, language_name, &generate_opts)
|
lang_path.join("__init__.py"),
|
||||||
})?;
|
allow_update,
|
||||||
|
|path| {
|
||||||
|
generate_file(path, INIT_PY_TEMPLATE, language_name, &generate_opts)
|
||||||
|
},
|
||||||
|
|path| {
|
||||||
|
let contents = fs::read_to_string(path)?;
|
||||||
|
if !contents.contains("uncomment these to include any queries") {
|
||||||
|
info!("Replacing __init__.py");
|
||||||
|
generate_file(path, INIT_PY_TEMPLATE, language_name, &generate_opts)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
missing_path_else(
|
missing_path_else(
|
||||||
lang_path.join("__init__.pyi"),
|
lang_path.join("__init__.pyi"),
|
||||||
|
|
@ -724,7 +902,11 @@ pub fn generate_grammar_files(
|
||||||
|path| generate_file(path, INIT_PYI_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, INIT_PYI_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("CapsuleType") {
|
if contents.contains("uncomment these to include any queries") {
|
||||||
|
info!("Replacing __init__.pyi");
|
||||||
|
generate_file(path, INIT_PYI_TEMPLATE, language_name, &generate_opts)?;
|
||||||
|
} else if !contents.contains("CapsuleType") {
|
||||||
|
info!("Updating __init__.pyi");
|
||||||
contents = contents
|
contents = contents
|
||||||
.replace(
|
.replace(
|
||||||
"from typing import Final",
|
"from typing import Final",
|
||||||
|
|
@ -756,6 +938,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("Parser(Language(") {
|
if !contents.contains("Parser(Language(") {
|
||||||
|
info!("Updating Language function in bindings/python/tests/test_binding.py");
|
||||||
contents = contents
|
contents = contents
|
||||||
.replace("tree_sitter.Language(", "Parser(Language(")
|
.replace("tree_sitter.Language(", "Parser(Language(")
|
||||||
.replace(".language())\n", ".language()))\n")
|
.replace(".language())\n", ".language()))\n")
|
||||||
|
|
@ -776,11 +959,19 @@ pub fn generate_grammar_files(
|
||||||
allow_update,
|
allow_update,
|
||||||
|path| generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("build_ext") {
|
if !contents.contains("build_ext") {
|
||||||
warn!("Replacing setup.py");
|
info!("Replacing setup.py");
|
||||||
generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts)?;
|
generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts)?;
|
||||||
}
|
}
|
||||||
|
if !contents.contains(" and not get_config_var") {
|
||||||
|
info!("Updating Python free-threading support in setup.py");
|
||||||
|
contents = contents.replace(
|
||||||
|
r#"startswith("cp"):"#,
|
||||||
|
r#"startswith("cp") and not get_config_var("Py_GIL_DISABLED"):"#
|
||||||
|
);
|
||||||
|
write_file(path, contents)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -799,6 +990,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let mut contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("cp310-*") {
|
if !contents.contains("cp310-*") {
|
||||||
|
info!("Updating dependencies in pyproject.toml");
|
||||||
contents = contents
|
contents = contents
|
||||||
.replace(r#"build = "cp39-*""#, r#"build = "cp310-*""#)
|
.replace(r#"build = "cp39-*""#, r#"build = "cp310-*""#)
|
||||||
.replace(r#"python = ">=3.9""#, r#"python = ">=3.10""#)
|
.replace(r#"python = ">=3.9""#, r#"python = ">=3.10""#)
|
||||||
|
|
@ -836,15 +1028,18 @@ pub fn generate_grammar_files(
|
||||||
allow_update,
|
allow_update,
|
||||||
|path| generate_file(path, PACKAGE_SWIFT_TEMPLATE, language_name, &generate_opts),
|
|path| generate_file(path, PACKAGE_SWIFT_TEMPLATE, language_name, &generate_opts),
|
||||||
|path| {
|
|path| {
|
||||||
let mut contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
contents = contents
|
let replaced_contents = contents
|
||||||
.replace(
|
.replace(
|
||||||
"https://github.com/ChimeHQ/SwiftTreeSitter",
|
"https://github.com/ChimeHQ/SwiftTreeSitter",
|
||||||
"https://github.com/tree-sitter/swift-tree-sitter",
|
"https://github.com/tree-sitter/swift-tree-sitter",
|
||||||
)
|
)
|
||||||
.replace("version: \"0.8.0\")", "version: \"0.9.0\")")
|
.replace("version: \"0.8.0\")", "version: \"0.9.0\")")
|
||||||
.replace("(url:", "(name: \"SwiftTreeSitter\", url:");
|
.replace("(url:", "(name: \"SwiftTreeSitter\", url:");
|
||||||
write_file(path, contents)?;
|
if !replaced_contents.eq(&contents) {
|
||||||
|
info!("Updating tree-sitter dependency in Package.swift");
|
||||||
|
write_file(path, contents)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -862,7 +1057,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains("b.pkg_hash.len") {
|
if !contents.contains("b.pkg_hash.len") {
|
||||||
warn!("Replacing build.zig");
|
info!("Replacing build.zig");
|
||||||
generate_file(path, BUILD_ZIG_TEMPLATE, language_name, &generate_opts)
|
generate_file(path, BUILD_ZIG_TEMPLATE, language_name, &generate_opts)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -877,7 +1072,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if !contents.contains(".name = .tree_sitter_") {
|
if !contents.contains(".name = .tree_sitter_") {
|
||||||
warn!("Replacing build.zig.zon");
|
info!("Replacing build.zig.zon");
|
||||||
generate_file(path, BUILD_ZIG_ZON_TEMPLATE, language_name, &generate_opts)
|
generate_file(path, BUILD_ZIG_ZON_TEMPLATE, language_name, &generate_opts)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -893,7 +1088,7 @@ pub fn generate_grammar_files(
|
||||||
|path| {
|
|path| {
|
||||||
let contents = fs::read_to_string(path)?;
|
let contents = fs::read_to_string(path)?;
|
||||||
if contents.contains("ts.Language") {
|
if contents.contains("ts.Language") {
|
||||||
warn!("Replacing root.zig");
|
info!("Replacing root.zig");
|
||||||
generate_file(path, ROOT_ZIG_TEMPLATE, language_name, &generate_opts)
|
generate_file(path, ROOT_ZIG_TEMPLATE, language_name, &generate_opts)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -909,6 +1104,45 @@ pub fn generate_grammar_files(
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate Java bindings
|
||||||
|
if tree_sitter_config.bindings.java {
|
||||||
|
missing_path(repo_path.join("pom.xml"), |path| {
|
||||||
|
generate_file(path, POM_XML_TEMPLATE, language_name, &generate_opts)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
missing_path(bindings_dir.join("java"), create_dir)?.apply(|path| {
|
||||||
|
missing_path(path.join("main"), create_dir)?.apply(|path| {
|
||||||
|
let package_path = generate_opts
|
||||||
|
.namespace
|
||||||
|
.unwrap_or("io.github.treesitter")
|
||||||
|
.replace(['-', '_'], "")
|
||||||
|
.split('.')
|
||||||
|
.fold(path.to_path_buf(), |path, dir| path.join(dir))
|
||||||
|
.join("jtreesitter")
|
||||||
|
.join(language_name.to_lowercase().replace('_', ""));
|
||||||
|
missing_path(package_path, create_dir)?.apply(|path| {
|
||||||
|
missing_path(path.join(format!("{class_name}.java")), |path| {
|
||||||
|
generate_file(path, BINDING_JAVA_TEMPLATE, language_name, &generate_opts)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
missing_path(path.join("test"), create_dir)?.apply(|path| {
|
||||||
|
missing_path(path.join(format!("{class_name}Test.java")), |path| {
|
||||||
|
generate_file(path, TEST_JAVA_TEMPLATE, language_name, &generate_opts)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -958,6 +1192,15 @@ fn generate_file(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
let filename = path.file_name().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
|
let lower_parser_name = if path
|
||||||
|
.extension()
|
||||||
|
.is_some_and(|e| e.eq_ignore_ascii_case("java"))
|
||||||
|
{
|
||||||
|
language_name.to_snake_case().replace('_', "")
|
||||||
|
} else {
|
||||||
|
language_name.to_snake_case()
|
||||||
|
};
|
||||||
|
|
||||||
let mut replacement = template
|
let mut replacement = template
|
||||||
.replace(
|
.replace(
|
||||||
CAMEL_PARSER_NAME_PLACEHOLDER,
|
CAMEL_PARSER_NAME_PLACEHOLDER,
|
||||||
|
|
@ -971,14 +1214,11 @@ fn generate_file(
|
||||||
UPPER_PARSER_NAME_PLACEHOLDER,
|
UPPER_PARSER_NAME_PLACEHOLDER,
|
||||||
&language_name.to_shouty_snake_case(),
|
&language_name.to_shouty_snake_case(),
|
||||||
)
|
)
|
||||||
.replace(
|
|
||||||
LOWER_PARSER_NAME_PLACEHOLDER,
|
|
||||||
&language_name.to_snake_case(),
|
|
||||||
)
|
|
||||||
.replace(
|
.replace(
|
||||||
KEBAB_PARSER_NAME_PLACEHOLDER,
|
KEBAB_PARSER_NAME_PLACEHOLDER,
|
||||||
&language_name.to_kebab_case(),
|
&language_name.to_kebab_case(),
|
||||||
)
|
)
|
||||||
|
.replace(LOWER_PARSER_NAME_PLACEHOLDER, &lower_parser_name)
|
||||||
.replace(PARSER_NAME_PLACEHOLDER, language_name)
|
.replace(PARSER_NAME_PLACEHOLDER, language_name)
|
||||||
.replace(CLI_VERSION_PLACEHOLDER, CLI_VERSION)
|
.replace(CLI_VERSION_PLACEHOLDER, CLI_VERSION)
|
||||||
.replace(RUST_BINDING_VERSION_PLACEHOLDER, RUST_BINDING_VERSION)
|
.replace(RUST_BINDING_VERSION_PLACEHOLDER, RUST_BINDING_VERSION)
|
||||||
|
|
@ -987,7 +1227,20 @@ fn generate_file(
|
||||||
PARSER_VERSION_PLACEHOLDER,
|
PARSER_VERSION_PLACEHOLDER,
|
||||||
&generate_opts.version.to_string(),
|
&generate_opts.version.to_string(),
|
||||||
)
|
)
|
||||||
.replace(PARSER_CLASS_NAME_PLACEHOLDER, generate_opts.class_name);
|
.replace(PARSER_CLASS_NAME_PLACEHOLDER, generate_opts.class_name)
|
||||||
|
.replace(
|
||||||
|
HIGHLIGHTS_QUERY_PATH_PLACEHOLDER,
|
||||||
|
generate_opts.highlights_query_path,
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
INJECTIONS_QUERY_PATH_PLACEHOLDER,
|
||||||
|
generate_opts.injections_query_path,
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
LOCALS_QUERY_PATH_PLACEHOLDER,
|
||||||
|
generate_opts.locals_query_path,
|
||||||
|
)
|
||||||
|
.replace(TAGS_QUERY_PATH_PLACEHOLDER, generate_opts.tags_query_path);
|
||||||
|
|
||||||
if let Some(name) = generate_opts.author_name {
|
if let Some(name) = generate_opts.author_name {
|
||||||
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER, name);
|
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER, name);
|
||||||
|
|
@ -1005,6 +1258,9 @@ fn generate_file(
|
||||||
"Cargo.toml" => {
|
"Cargo.toml" => {
|
||||||
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER_RS, "");
|
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER_RS, "");
|
||||||
}
|
}
|
||||||
|
"pom.xml" => {
|
||||||
|
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER_JAVA, "");
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1030,30 +1286,52 @@ fn generate_file(
|
||||||
"Cargo.toml" => {
|
"Cargo.toml" => {
|
||||||
replacement = replacement.replace(AUTHOR_EMAIL_PLACEHOLDER_RS, "");
|
replacement = replacement.replace(AUTHOR_EMAIL_PLACEHOLDER_RS, "");
|
||||||
}
|
}
|
||||||
|
"pom.xml" => {
|
||||||
|
replacement = replacement.replace(AUTHOR_EMAIL_PLACEHOLDER_JAVA, "");
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filename == "package.json" {
|
match (generate_opts.author_url, filename) {
|
||||||
if let Some(url) = generate_opts.author_url {
|
(Some(url), "package.json" | "pom.xml") => {
|
||||||
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER, url);
|
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER, url);
|
||||||
} else {
|
}
|
||||||
|
(None, "package.json") => {
|
||||||
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER_JS, "");
|
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER_JS, "");
|
||||||
}
|
}
|
||||||
|
(None, "pom.xml") => {
|
||||||
|
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER_JAVA, "");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if generate_opts.author_name.is_none()
|
if generate_opts.author_name.is_none()
|
||||||
&& generate_opts.author_email.is_none()
|
&& generate_opts.author_email.is_none()
|
||||||
&& generate_opts.author_url.is_none()
|
&& generate_opts.author_url.is_none()
|
||||||
&& filename == "package.json"
|
|
||||||
{
|
{
|
||||||
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JS) {
|
match filename {
|
||||||
if let Some(end_idx) = replacement[start_idx..]
|
"package.json" => {
|
||||||
.find("},")
|
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JS) {
|
||||||
.map(|i| i + start_idx + 2)
|
if let Some(end_idx) = replacement[start_idx..]
|
||||||
{
|
.find("},")
|
||||||
replacement.replace_range(start_idx..end_idx, "");
|
.map(|i| i + start_idx + 2)
|
||||||
|
{
|
||||||
|
replacement.replace_range(start_idx..end_idx, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
"pom.xml" => {
|
||||||
|
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JAVA) {
|
||||||
|
if let Some(end_idx) = replacement[start_idx..]
|
||||||
|
.find("</developer>")
|
||||||
|
.map(|i| i + start_idx + 12)
|
||||||
|
{
|
||||||
|
replacement.replace_range(start_idx..end_idx, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if generate_opts.author_name.is_none() && generate_opts.author_email.is_none() {
|
} else if generate_opts.author_name.is_none() && generate_opts.author_email.is_none() {
|
||||||
match filename {
|
match filename {
|
||||||
|
|
@ -1134,6 +1412,19 @@ fn generate_file(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(namespace) = generate_opts.namespace {
|
||||||
|
replacement = replacement
|
||||||
|
.replace(
|
||||||
|
PARSER_NS_CLEANED_PLACEHOLDER,
|
||||||
|
&namespace.replace(['-', '_'], ""),
|
||||||
|
)
|
||||||
|
.replace(PARSER_NS_PLACEHOLDER, namespace);
|
||||||
|
} else {
|
||||||
|
replacement = replacement
|
||||||
|
.replace(PARSER_NS_CLEANED_PLACEHOLDER, "io.github.treesitter")
|
||||||
|
.replace(PARSER_NS_PLACEHOLDER, "io.github.tree-sitter");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(funding_url) = generate_opts.funding {
|
if let Some(funding_url) = generate_opts.funding {
|
||||||
match filename {
|
match filename {
|
||||||
"pyproject.toml" | "package.json" => {
|
"pyproject.toml" | "package.json" => {
|
||||||
|
|
@ -1153,6 +1444,18 @@ fn generate_file(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filename == "build.zig.zon" {
|
||||||
|
let id = thread_rng().gen_range(1u32..0xFFFF_FFFFu32);
|
||||||
|
let checksum = crc32(format!("tree_sitter_{language_name}").as_bytes());
|
||||||
|
replacement = replacement.replace(
|
||||||
|
PARSER_FINGERPRINT_PLACEHOLDER,
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
|
&format!("0x{checksum:x}{id:x}"),
|
||||||
|
#[cfg(target_endian = "big")]
|
||||||
|
&format!("0x{id:x}{checksum:x}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
write_file(path, replacement)?;
|
write_file(path, replacement)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,20 @@ use tree_sitter_cli::{
|
||||||
LOG_GRAPH_ENABLED, START_SEED,
|
LOG_GRAPH_ENABLED, START_SEED,
|
||||||
},
|
},
|
||||||
highlight::{self, HighlightOptions},
|
highlight::{self, HighlightOptions},
|
||||||
init::{generate_grammar_files, JsonConfigOpts},
|
init::{generate_grammar_files, JsonConfigOpts, TREE_SITTER_JSON_SCHEMA},
|
||||||
input::{get_input, get_tmp_source_file, CliInput},
|
input::{get_input, get_tmp_source_file, CliInput},
|
||||||
logger,
|
logger,
|
||||||
parse::{self, ParseDebugType, ParseFileOptions, ParseOutput, ParseTheme},
|
parse::{self, ParseDebugType, ParseFileOptions, ParseOutput, ParseTheme},
|
||||||
playground, query,
|
playground,
|
||||||
|
query::{self, QueryFileOptions},
|
||||||
tags::{self, TagsOptions},
|
tags::{self, TagsOptions},
|
||||||
test::{self, TestOptions, TestStats},
|
test::{self, TestOptions, TestStats, TestSummary},
|
||||||
test_highlight, test_tags, util, version,
|
test_highlight, test_tags, util,
|
||||||
version::BumpLevel,
|
version::{self, BumpLevel},
|
||||||
wasm,
|
wasm,
|
||||||
};
|
};
|
||||||
use tree_sitter_config::Config;
|
use tree_sitter_config::Config;
|
||||||
|
use tree_sitter_generate::OptLevel;
|
||||||
use tree_sitter_highlight::Highlighter;
|
use tree_sitter_highlight::Highlighter;
|
||||||
use tree_sitter_loader::{self as loader, Bindings, TreeSitterJSON};
|
use tree_sitter_loader::{self as loader, Bindings, TreeSitterJSON};
|
||||||
use tree_sitter_tags::TagsContext;
|
use tree_sitter_tags::TagsContext;
|
||||||
|
|
@ -87,17 +89,6 @@ struct Init {
|
||||||
pub grammar_path: Option<PathBuf>,
|
pub grammar_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, ValueEnum, PartialEq, Eq)]
|
|
||||||
enum GenerationEmit {
|
|
||||||
/// Generate `grammar.json` and `node-types.json`
|
|
||||||
Json,
|
|
||||||
/// Generate `parser.c` and related files
|
|
||||||
#[default]
|
|
||||||
Parser,
|
|
||||||
/// Compile to a library
|
|
||||||
Lib,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
#[command(alias = "gen", alias = "g")]
|
#[command(alias = "gen", alias = "g")]
|
||||||
struct Generate {
|
struct Generate {
|
||||||
|
|
@ -120,28 +111,38 @@ struct Generate {
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub abi_version: Option<String>,
|
pub abi_version: Option<String>,
|
||||||
/// What generated files to emit
|
/// Only generate `grammar.json` and `node-types.json`
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
#[clap(value_enum, default_value_t=GenerationEmit::Parser)]
|
pub no_parser: bool,
|
||||||
pub emit: GenerationEmit,
|
/// Deprecated: use the `build` command
|
||||||
/// Deprecated: use --emit=lib.
|
#[arg(long, short = 'b')]
|
||||||
#[arg(long, short = 'b', conflicts_with = "emit")]
|
|
||||||
pub build: bool,
|
pub build: bool,
|
||||||
/// Compile a parser in debug mode
|
/// Deprecated: use the `build` command
|
||||||
#[arg(long, short = '0')]
|
#[arg(long, short = '0')]
|
||||||
pub debug_build: bool,
|
pub debug_build: bool,
|
||||||
/// The path to the directory containing the parser library
|
/// Deprecated: use the `build` command
|
||||||
#[arg(long, value_name = "PATH")]
|
#[arg(long, value_name = "PATH")]
|
||||||
pub libdir: Option<PathBuf>,
|
pub libdir: Option<PathBuf>,
|
||||||
/// The path to output the generated source files
|
/// The path to output the generated source files
|
||||||
#[arg(long, short, value_name = "DIRECTORY")]
|
#[arg(long, short, value_name = "DIRECTORY")]
|
||||||
pub output: Option<PathBuf>,
|
pub output: Option<PathBuf>,
|
||||||
/// Produce a report of the states for the given rule, use `-` to report every rule
|
/// Produce a report of the states for the given rule, use `-` to report every rule
|
||||||
#[arg(long)]
|
#[arg(long, conflicts_with = "json", conflicts_with = "json_summary")]
|
||||||
pub report_states_for_rule: Option<String>,
|
pub report_states_for_rule: Option<String>,
|
||||||
/// Report conflicts in a JSON format
|
/// Deprecated: use --json-summary
|
||||||
#[arg(long)]
|
#[arg(
|
||||||
|
long,
|
||||||
|
conflicts_with = "json_summary",
|
||||||
|
conflicts_with = "report_states_for_rule"
|
||||||
|
)]
|
||||||
pub json: bool,
|
pub json: bool,
|
||||||
|
/// Report conflicts in a JSON format
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
conflicts_with = "json",
|
||||||
|
conflicts_with = "report_states_for_rule"
|
||||||
|
)]
|
||||||
|
pub json_summary: bool,
|
||||||
/// The name or path of the JavaScript runtime to use for generating parsers
|
/// The name or path of the JavaScript runtime to use for generating parsers
|
||||||
#[cfg(not(feature = "qjs-rt"))]
|
#[cfg(not(feature = "qjs-rt"))]
|
||||||
#[arg(
|
#[arg(
|
||||||
|
|
@ -162,6 +163,11 @@ struct Generate {
|
||||||
/// The name or path of the JavaScript runtime to use for generating parsers, specify `native`
|
/// The name or path of the JavaScript runtime to use for generating parsers, specify `native`
|
||||||
/// to use the native `QuickJS` runtime
|
/// to use the native `QuickJS` runtime
|
||||||
pub js_runtime: Option<String>,
|
pub js_runtime: Option<String>,
|
||||||
|
|
||||||
|
/// Disable optimizations when generating the parser. Currently, this only affects
|
||||||
|
/// the merging of compatible parse states.
|
||||||
|
#[arg(long)]
|
||||||
|
pub disable_optimizations: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -217,7 +223,7 @@ struct Parse {
|
||||||
#[arg(long, short = 'D')]
|
#[arg(long, short = 'D')]
|
||||||
pub debug_graph: bool,
|
pub debug_graph: bool,
|
||||||
/// Compile parsers to Wasm instead of native dynamic libraries
|
/// Compile parsers to Wasm instead of native dynamic libraries
|
||||||
#[arg(long)]
|
#[arg(long, hide = cfg!(not(feature = "wasm")))]
|
||||||
pub wasm: bool,
|
pub wasm: bool,
|
||||||
/// Output the parse data with graphviz dot
|
/// Output the parse data with graphviz dot
|
||||||
#[arg(long = "dot")]
|
#[arg(long = "dot")]
|
||||||
|
|
@ -229,7 +235,7 @@ struct Parse {
|
||||||
#[arg(long = "cst", short = 'c')]
|
#[arg(long = "cst", short = 'c')]
|
||||||
pub output_cst: bool,
|
pub output_cst: bool,
|
||||||
/// Show parsing statistic
|
/// Show parsing statistic
|
||||||
#[arg(long, short)]
|
#[arg(long, short, conflicts_with = "json", conflicts_with = "json_summary")]
|
||||||
pub stat: bool,
|
pub stat: bool,
|
||||||
/// Interrupt the parsing process by timeout (µs)
|
/// Interrupt the parsing process by timeout (µs)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
|
|
@ -254,9 +260,12 @@ struct Parse {
|
||||||
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub open_log: bool,
|
pub open_log: bool,
|
||||||
/// Output parsing results in a JSON format
|
/// Deprecated: use --json-summary
|
||||||
#[arg(long, short = 'j')]
|
#[arg(long, conflicts_with = "json_summary", conflicts_with = "stat")]
|
||||||
pub json: bool,
|
pub json: bool,
|
||||||
|
/// Output parsing results in a JSON format
|
||||||
|
#[arg(long, short = 'j', conflicts_with = "json", conflicts_with = "stat")]
|
||||||
|
pub json_summary: bool,
|
||||||
/// The path to an alternative config.json file
|
/// The path to an alternative config.json file
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub config_path: Option<PathBuf>,
|
pub config_path: Option<PathBuf>,
|
||||||
|
|
@ -314,7 +323,7 @@ struct Test {
|
||||||
#[arg(long, short = 'D')]
|
#[arg(long, short = 'D')]
|
||||||
pub debug_graph: bool,
|
pub debug_graph: bool,
|
||||||
/// Compile parsers to Wasm instead of native dynamic libraries
|
/// Compile parsers to Wasm instead of native dynamic libraries
|
||||||
#[arg(long)]
|
#[arg(long, hide = cfg!(not(feature = "wasm")))]
|
||||||
pub wasm: bool,
|
pub wasm: bool,
|
||||||
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
|
|
@ -334,6 +343,9 @@ struct Test {
|
||||||
/// Show only the pass-fail overview tree
|
/// Show only the pass-fail overview tree
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub overview_only: bool,
|
pub overview_only: bool,
|
||||||
|
/// Output the test summary in a JSON format
|
||||||
|
#[arg(long)]
|
||||||
|
pub json_summary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -436,6 +448,14 @@ struct Query {
|
||||||
/// The range of rows in which the query will be executed
|
/// The range of rows in which the query will be executed
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub row_range: Option<String>,
|
pub row_range: Option<String>,
|
||||||
|
/// The range of byte offsets in which the query will be executed. Only the matches that are fully contained within the provided
|
||||||
|
/// byte range will be returned.
|
||||||
|
#[arg(long)]
|
||||||
|
pub containing_byte_range: Option<String>,
|
||||||
|
/// The range of rows in which the query will be executed. Only the matches that are fully contained within the provided row range
|
||||||
|
/// will be returned.
|
||||||
|
#[arg(long)]
|
||||||
|
pub containing_row_range: Option<String>,
|
||||||
/// Select a language by the scope instead of a file extension
|
/// Select a language by the scope instead of a file extension
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub scope: Option<String>,
|
pub scope: Option<String>,
|
||||||
|
|
@ -577,6 +597,20 @@ pub enum Shell {
|
||||||
Nushell,
|
Nushell,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Complete `action` if the wasm feature is enabled, otherwise return an error
|
||||||
|
macro_rules! checked_wasm {
|
||||||
|
($action:block) => {
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
{
|
||||||
|
$action
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "wasm"))]
|
||||||
|
{
|
||||||
|
Err(anyhow!("--wasm flag specified, but this build of tree-sitter-cli does not include the wasm feature"))?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl InitConfig {
|
impl InitConfig {
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
if let Ok(Some(config_path)) = Config::find_config_file() {
|
if let Ok(Some(config_path)) = Config::find_config_file() {
|
||||||
|
|
@ -738,6 +772,14 @@ impl Init {
|
||||||
.map(|e| Some(e.trim().to_string()))
|
.map(|e| Some(e.trim().to_string()))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let namespace = || {
|
||||||
|
Input::<String>::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Package namespace")
|
||||||
|
.default("io.github.tree-sitter".to_string())
|
||||||
|
.allow_empty(true)
|
||||||
|
.interact()
|
||||||
|
};
|
||||||
|
|
||||||
let bindings = || {
|
let bindings = || {
|
||||||
let languages = Bindings::default().languages();
|
let languages = Bindings::default().languages();
|
||||||
|
|
||||||
|
|
@ -767,6 +809,7 @@ impl Init {
|
||||||
"author",
|
"author",
|
||||||
"email",
|
"email",
|
||||||
"url",
|
"url",
|
||||||
|
"namespace",
|
||||||
"bindings",
|
"bindings",
|
||||||
"exit",
|
"exit",
|
||||||
];
|
];
|
||||||
|
|
@ -787,6 +830,7 @@ impl Init {
|
||||||
"author" => opts.author = author()?,
|
"author" => opts.author = author()?,
|
||||||
"email" => opts.email = email()?,
|
"email" => opts.email = email()?,
|
||||||
"url" => opts.url = url()?,
|
"url" => opts.url = url()?,
|
||||||
|
"namespace" => opts.namespace = Some(namespace()?),
|
||||||
"bindings" => opts.bindings = bindings()?,
|
"bindings" => opts.bindings = bindings()?,
|
||||||
"exit" => break,
|
"exit" => break,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
@ -823,10 +867,26 @@ impl Init {
|
||||||
|
|
||||||
(opts.name.clone(), Some(opts))
|
(opts.name.clone(), Some(opts))
|
||||||
} else {
|
} else {
|
||||||
let mut json = serde_json::from_str::<TreeSitterJSON>(
|
let old_config = fs::read_to_string(current_dir.join("tree-sitter.json"))
|
||||||
&fs::read_to_string(current_dir.join("tree-sitter.json"))
|
.with_context(|| "Failed to read tree-sitter.json")?;
|
||||||
.with_context(|| "Failed to read tree-sitter.json")?,
|
|
||||||
)?;
|
let mut json = serde_json::from_str::<TreeSitterJSON>(&old_config)?;
|
||||||
|
if json.schema.is_none() {
|
||||||
|
json.schema = Some(TREE_SITTER_JSON_SCHEMA.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_config = format!("{}\n", serde_json::to_string_pretty(&json)?);
|
||||||
|
// Write the re-serialized config back, as newly added optional boolean fields
|
||||||
|
// will be included with explicit `false`s rather than implict `null`s
|
||||||
|
if self.update && !old_config.trim().eq(new_config.trim()) {
|
||||||
|
info!("Updating tree-sitter.json");
|
||||||
|
fs::write(
|
||||||
|
current_dir.join("tree-sitter.json"),
|
||||||
|
serde_json::to_string_pretty(&json)?,
|
||||||
|
)
|
||||||
|
.with_context(|| "Failed to write tree-sitter.json")?;
|
||||||
|
}
|
||||||
|
|
||||||
(json.grammars.swap_remove(0).name, None)
|
(json.grammars.swap_remove(0).name, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -856,9 +916,13 @@ impl Generate {
|
||||||
version.parse().expect("invalid abi version flag")
|
version.parse().expect("invalid abi version flag")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if self.build {
|
|
||||||
warn!("--build is deprecated, use --emit=lib instead");
|
let json_summary = if self.json {
|
||||||
}
|
warn!("--json is deprecated, use --json-summary instead");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.json_summary
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(err) = tree_sitter_generate::generate_parser_in_directory(
|
if let Err(err) = tree_sitter_generate::generate_parser_in_directory(
|
||||||
current_dir,
|
current_dir,
|
||||||
|
|
@ -867,9 +931,14 @@ impl Generate {
|
||||||
abi_version,
|
abi_version,
|
||||||
self.report_states_for_rule.as_deref(),
|
self.report_states_for_rule.as_deref(),
|
||||||
self.js_runtime.as_deref(),
|
self.js_runtime.as_deref(),
|
||||||
self.emit != GenerationEmit::Json,
|
!self.no_parser,
|
||||||
|
if self.disable_optimizations {
|
||||||
|
OptLevel::empty()
|
||||||
|
} else {
|
||||||
|
OptLevel::default()
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
if self.json {
|
if json_summary {
|
||||||
eprintln!("{}", serde_json::to_string_pretty(&err)?);
|
eprintln!("{}", serde_json::to_string_pretty(&err)?);
|
||||||
// Exit early to prevent errors from being printed a second time in the caller
|
// Exit early to prevent errors from being printed a second time in the caller
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
@ -878,7 +947,8 @@ impl Generate {
|
||||||
Err(anyhow!(err.to_string())).with_context(|| "Error when generating parser")?;
|
Err(anyhow!(err.to_string())).with_context(|| "Error when generating parser")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.emit == GenerationEmit::Lib || self.build {
|
if self.build {
|
||||||
|
warn!("--build is deprecated, use the `build` command");
|
||||||
if let Some(path) = self.libdir {
|
if let Some(path) = self.libdir {
|
||||||
loader = loader::Loader::with_parser_lib_path(path);
|
loader = loader::Loader::with_parser_lib_path(path);
|
||||||
}
|
}
|
||||||
|
|
@ -901,11 +971,21 @@ impl Build {
|
||||||
} else {
|
} else {
|
||||||
let output_path = if let Some(ref path) = self.output {
|
let output_path = if let Some(ref path) = self.output {
|
||||||
let path = Path::new(path);
|
let path = Path::new(path);
|
||||||
if path.is_absolute() {
|
let full_path = if path.is_absolute() {
|
||||||
path.to_path_buf()
|
path.to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
current_dir.join(path)
|
current_dir.join(path)
|
||||||
}
|
};
|
||||||
|
let parent_path = full_path
|
||||||
|
.parent()
|
||||||
|
.context("Output path must have a parent")?;
|
||||||
|
let name = full_path
|
||||||
|
.file_name()
|
||||||
|
.context("Ouput path must have a filename")?;
|
||||||
|
fs::create_dir_all(parent_path).context("Failed to create output path")?;
|
||||||
|
let mut canon_path = parent_path.canonicalize().context("Invalid output path")?;
|
||||||
|
canon_path.push(name);
|
||||||
|
canon_path
|
||||||
} else {
|
} else {
|
||||||
let file_name = grammar_path
|
let file_name = grammar_path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
|
|
@ -928,12 +1008,9 @@ impl Build {
|
||||||
|
|
||||||
loader.force_rebuild(true);
|
loader.force_rebuild(true);
|
||||||
|
|
||||||
let config = Config::load(None)?;
|
|
||||||
let loader_config = config.get()?;
|
|
||||||
loader.find_all_languages(&loader_config).unwrap();
|
|
||||||
loader
|
loader
|
||||||
.compile_parser_at_path(&grammar_path, output_path, flags)
|
.compile_parser_at_path(&grammar_path, output_path, flags)
|
||||||
.unwrap();
|
.context("Failed to compile parser")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -943,13 +1020,19 @@ impl Parse {
|
||||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||||
let config = Config::load(self.config_path)?;
|
let config = Config::load(self.config_path)?;
|
||||||
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||||
|
let json_summary = if self.json {
|
||||||
|
warn!("--json is deprecated, use --json-summary instead");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.json_summary
|
||||||
|
};
|
||||||
let output = if self.output_dot {
|
let output = if self.output_dot {
|
||||||
ParseOutput::Dot
|
ParseOutput::Dot
|
||||||
} else if self.output_xml {
|
} else if self.output_xml {
|
||||||
ParseOutput::Xml
|
ParseOutput::Xml
|
||||||
} else if self.output_cst {
|
} else if self.output_cst {
|
||||||
ParseOutput::Cst
|
ParseOutput::Cst
|
||||||
} else if self.quiet || self.json {
|
} else if self.quiet || json_summary {
|
||||||
ParseOutput::Quiet
|
ParseOutput::Quiet
|
||||||
} else {
|
} else {
|
||||||
ParseOutput::Normal
|
ParseOutput::Normal
|
||||||
|
|
@ -980,13 +1063,14 @@ impl Parse {
|
||||||
loader.debug_build(self.debug_build);
|
loader.debug_build(self.debug_build);
|
||||||
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
if self.wasm {
|
if self.wasm {
|
||||||
let engine = tree_sitter::wasmtime::Engine::default();
|
checked_wasm!({
|
||||||
parser
|
let engine = tree_sitter::wasmtime::Engine::default();
|
||||||
.set_wasm_store(tree_sitter::WasmStore::new(&engine).unwrap())
|
parser
|
||||||
.unwrap();
|
.set_wasm_store(tree_sitter::WasmStore::new(&engine).unwrap())
|
||||||
loader.use_wasm(&engine);
|
.unwrap();
|
||||||
|
loader.use_wasm(&engine);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout = self.timeout.unwrap_or_default();
|
let timeout = self.timeout.unwrap_or_default();
|
||||||
|
|
@ -1023,7 +1107,7 @@ impl Parse {
|
||||||
|
|
||||||
let mut update_stats = |stats: &mut parse::ParseStats| {
|
let mut update_stats = |stats: &mut parse::ParseStats| {
|
||||||
let parse_result = stats.parse_summaries.last().unwrap();
|
let parse_result = stats.parse_summaries.last().unwrap();
|
||||||
if should_track_stats {
|
if should_track_stats || json_summary {
|
||||||
stats.cumulative_stats.total_parses += 1;
|
stats.cumulative_stats.total_parses += 1;
|
||||||
if parse_result.successful {
|
if parse_result.successful {
|
||||||
stats.cumulative_stats.successful_parses += 1;
|
stats.cumulative_stats.successful_parses += 1;
|
||||||
|
|
@ -1055,18 +1139,19 @@ impl Parse {
|
||||||
.map(|p| p.to_string_lossy().chars().count())
|
.map(|p| p.to_string_lossy().chars().count())
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
options.stats.source_count = paths.len();
|
||||||
|
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
let language = loader
|
let language = loader
|
||||||
.select_language(
|
.select_language(
|
||||||
path,
|
Some(path),
|
||||||
current_dir,
|
current_dir,
|
||||||
self.scope.as_deref(),
|
self.scope.as_deref(),
|
||||||
lib_info.as_ref(),
|
lib_info.as_ref(),
|
||||||
)
|
)
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
anyhow!("Failed to load langauge for path \"{}\"", path.display())
|
anyhow!("Failed to load language for path \"{}\"", path.display())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
parse::parse_file_at_path(
|
parse::parse_file_at_path(
|
||||||
|
|
@ -1091,7 +1176,12 @@ impl Parse {
|
||||||
|
|
||||||
let language = if let Some(ref lib_path) = self.lib_path {
|
let language = if let Some(ref lib_path) = self.lib_path {
|
||||||
&loader
|
&loader
|
||||||
.select_language(lib_path, current_dir, None, lib_info.as_ref())
|
.select_language(
|
||||||
|
None,
|
||||||
|
current_dir,
|
||||||
|
self.scope.as_deref(),
|
||||||
|
lib_info.as_ref(),
|
||||||
|
)
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to load language for path \"{}\"",
|
"Failed to load language for path \"{}\"",
|
||||||
|
|
@ -1125,8 +1215,12 @@ impl Parse {
|
||||||
|
|
||||||
let path = get_tmp_source_file(&contents)?;
|
let path = get_tmp_source_file(&contents)?;
|
||||||
let name = "stdin";
|
let name = "stdin";
|
||||||
let language =
|
let language = loader.select_language(
|
||||||
loader.select_language(&path, current_dir, None, lib_info.as_ref())?;
|
None,
|
||||||
|
current_dir,
|
||||||
|
self.scope.as_deref(),
|
||||||
|
lib_info.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
parse::parse_file_at_path(
|
parse::parse_file_at_path(
|
||||||
&mut parser,
|
&mut parser,
|
||||||
|
|
@ -1144,7 +1238,7 @@ impl Parse {
|
||||||
if should_track_stats {
|
if should_track_stats {
|
||||||
println!("\n{}", stats.cumulative_stats);
|
println!("\n{}", stats.cumulative_stats);
|
||||||
}
|
}
|
||||||
if self.json {
|
if json_summary {
|
||||||
println!("{}", serde_json::to_string_pretty(&stats)?);
|
println!("{}", serde_json::to_string_pretty(&stats)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1156,6 +1250,28 @@ impl Parse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// In case an error is encountered, prints out the contents of `test_summary` and
|
||||||
|
/// propagates the error
|
||||||
|
fn check_test(
|
||||||
|
test_result: Result<()>,
|
||||||
|
test_summary: &TestSummary,
|
||||||
|
json_summary: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Err(e) = test_result {
|
||||||
|
if json_summary {
|
||||||
|
let json_summary = serde_json::to_string_pretty(test_summary)
|
||||||
|
.expect("Failed to encode summary to JSON");
|
||||||
|
println!("{json_summary}");
|
||||||
|
} else {
|
||||||
|
println!("{test_summary}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl Test {
|
impl Test {
|
||||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||||
let config = Config::load(self.config_path)?;
|
let config = Config::load(self.config_path)?;
|
||||||
|
|
@ -1167,13 +1283,14 @@ impl Test {
|
||||||
|
|
||||||
let mut parser = Parser::new();
|
let mut parser = Parser::new();
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
if self.wasm {
|
if self.wasm {
|
||||||
let engine = tree_sitter::wasmtime::Engine::default();
|
checked_wasm!({
|
||||||
parser
|
let engine = tree_sitter::wasmtime::Engine::default();
|
||||||
.set_wasm_store(tree_sitter::WasmStore::new(&engine).unwrap())
|
parser
|
||||||
.unwrap();
|
.set_wasm_store(tree_sitter::WasmStore::new(&engine).unwrap())
|
||||||
loader.use_wasm(&engine);
|
.unwrap();
|
||||||
|
loader.use_wasm(&engine);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.lib_path.is_none() && self.lang_name.is_some() {
|
if self.lib_path.is_none() && self.lang_name.is_some() {
|
||||||
|
|
@ -1184,7 +1301,7 @@ impl Test {
|
||||||
let lib_info =
|
let lib_info =
|
||||||
get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
||||||
&loader
|
&loader
|
||||||
.select_language(lib_path, current_dir, None, lib_info.as_ref())
|
.select_language(None, current_dir, None, lib_info.as_ref())
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to load language for path \"{}\"",
|
"Failed to load language for path \"{}\"",
|
||||||
|
|
@ -1200,15 +1317,18 @@ impl Test {
|
||||||
parser.set_language(language)?;
|
parser.set_language(language)?;
|
||||||
|
|
||||||
let test_dir = current_dir.join("test");
|
let test_dir = current_dir.join("test");
|
||||||
let mut stats = parse::Stats::default();
|
let mut test_summary = TestSummary::new(
|
||||||
|
color,
|
||||||
|
stat,
|
||||||
|
self.update,
|
||||||
|
self.overview_only,
|
||||||
|
self.json_summary,
|
||||||
|
);
|
||||||
|
|
||||||
// Run the corpus tests. Look for them in `test/corpus`.
|
// Run the corpus tests. Look for them in `test/corpus`.
|
||||||
let test_corpus_dir = test_dir.join("corpus");
|
let test_corpus_dir = test_dir.join("corpus");
|
||||||
if test_corpus_dir.is_dir() {
|
if test_corpus_dir.is_dir() {
|
||||||
let mut output = String::new();
|
let opts = TestOptions {
|
||||||
let mut rates = Vec::new();
|
|
||||||
let mut opts = TestOptions {
|
|
||||||
output: &mut output,
|
|
||||||
path: test_corpus_dir,
|
path: test_corpus_dir,
|
||||||
debug: self.debug,
|
debug: self.debug,
|
||||||
debug_graph: self.debug_graph,
|
debug_graph: self.debug_graph,
|
||||||
|
|
@ -1219,51 +1339,67 @@ impl Test {
|
||||||
open_log: self.open_log,
|
open_log: self.open_log,
|
||||||
languages: languages.iter().map(|(l, n)| (n.as_str(), l)).collect(),
|
languages: languages.iter().map(|(l, n)| (n.as_str(), l)).collect(),
|
||||||
color,
|
color,
|
||||||
test_num: 1,
|
|
||||||
parse_rates: &mut rates,
|
|
||||||
stat_display: stat,
|
|
||||||
stats: &mut stats,
|
|
||||||
show_fields: self.show_fields,
|
show_fields: self.show_fields,
|
||||||
overview_only: self.overview_only,
|
overview_only: self.overview_only,
|
||||||
};
|
};
|
||||||
|
|
||||||
test::run_tests_at_path(&mut parser, &mut opts)?;
|
check_test(
|
||||||
println!("\n{stats}");
|
test::run_tests_at_path(&mut parser, &opts, &mut test_summary),
|
||||||
|
&test_summary,
|
||||||
|
self.json_summary,
|
||||||
|
)?;
|
||||||
|
test_summary.test_num = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that all of the queries are valid.
|
// Check that all of the queries are valid.
|
||||||
test::check_queries_at_path(language, ¤t_dir.join("queries"))?;
|
let query_dir = current_dir.join("queries");
|
||||||
|
check_test(
|
||||||
|
test::check_queries_at_path(language, &query_dir),
|
||||||
|
&test_summary,
|
||||||
|
self.json_summary,
|
||||||
|
)?;
|
||||||
|
test_summary.test_num = 1;
|
||||||
|
|
||||||
// Run the syntax highlighting tests.
|
// Run the syntax highlighting tests.
|
||||||
let test_highlight_dir = test_dir.join("highlight");
|
let test_highlight_dir = test_dir.join("highlight");
|
||||||
if test_highlight_dir.is_dir() {
|
if test_highlight_dir.is_dir() {
|
||||||
let mut highlighter = Highlighter::new();
|
let mut highlighter = Highlighter::new();
|
||||||
highlighter.parser = parser;
|
highlighter.parser = parser;
|
||||||
test_highlight::test_highlights(
|
check_test(
|
||||||
&loader,
|
test_highlight::test_highlights(
|
||||||
&config.get()?,
|
&loader,
|
||||||
&mut highlighter,
|
&config.get()?,
|
||||||
&test_highlight_dir,
|
&mut highlighter,
|
||||||
color,
|
&test_highlight_dir,
|
||||||
|
&mut test_summary,
|
||||||
|
),
|
||||||
|
&test_summary,
|
||||||
|
self.json_summary,
|
||||||
)?;
|
)?;
|
||||||
parser = highlighter.parser;
|
parser = highlighter.parser;
|
||||||
|
test_summary.test_num = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_tag_dir = test_dir.join("tags");
|
let test_tag_dir = test_dir.join("tags");
|
||||||
if test_tag_dir.is_dir() {
|
if test_tag_dir.is_dir() {
|
||||||
let mut tags_context = TagsContext::new();
|
let mut tags_context = TagsContext::new();
|
||||||
tags_context.parser = parser;
|
tags_context.parser = parser;
|
||||||
test_tags::test_tags(
|
check_test(
|
||||||
&loader,
|
test_tags::test_tags(
|
||||||
&config.get()?,
|
&loader,
|
||||||
&mut tags_context,
|
&config.get()?,
|
||||||
&test_tag_dir,
|
&mut tags_context,
|
||||||
color,
|
&test_tag_dir,
|
||||||
|
&mut test_summary,
|
||||||
|
),
|
||||||
|
&test_summary,
|
||||||
|
self.json_summary,
|
||||||
)?;
|
)?;
|
||||||
|
test_summary.test_num = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the rest of the queries, find their tests and run them
|
// For the rest of the queries, find their tests and run them
|
||||||
for entry in walkdir::WalkDir::new(current_dir.join("queries"))
|
for entry in walkdir::WalkDir::new(&query_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.file_type().is_file())
|
.filter(|e| e.file_type().is_file())
|
||||||
|
|
@ -1286,34 +1422,48 @@ impl Test {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if !entries.is_empty() {
|
if !entries.is_empty() {
|
||||||
println!("{stem}:");
|
test_summary.query_results.add_group(stem);
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in entries {
|
test_summary.test_num = 1;
|
||||||
|
let opts = QueryFileOptions::default();
|
||||||
|
for entry in &entries {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
query::query_file_at_path(
|
check_test(
|
||||||
language,
|
query::query_file_at_path(
|
||||||
path,
|
language,
|
||||||
&path.display().to_string(),
|
path,
|
||||||
path,
|
&path.display().to_string(),
|
||||||
false,
|
path,
|
||||||
None,
|
&opts,
|
||||||
None,
|
Some(&mut test_summary),
|
||||||
true,
|
),
|
||||||
false,
|
&test_summary,
|
||||||
false,
|
self.json_summary,
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
if !entries.is_empty() {
|
||||||
|
test_summary.query_results.pop_traversal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
test_summary.test_num = 1;
|
||||||
|
|
||||||
|
if self.json_summary {
|
||||||
|
let json_summary = serde_json::to_string_pretty(&test_summary)
|
||||||
|
.expect("Failed to encode test summary to JSON");
|
||||||
|
println!("{json_summary}");
|
||||||
|
} else {
|
||||||
|
println!("{test_summary}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
fn run(self, current_dir: PathBuf) -> Result<()> {
|
fn run(self, current_dir: PathBuf) -> Result<()> {
|
||||||
version::Version::new(self.version, current_dir, self.bump).run()
|
Ok(version::Version::new(self.version, current_dir, self.bump).run()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1332,7 +1482,7 @@ impl Fuzz {
|
||||||
let lang_name = lib_info.1.to_string();
|
let lang_name = lib_info.1.to_string();
|
||||||
&(
|
&(
|
||||||
loader
|
loader
|
||||||
.select_language(lib_path, current_dir, None, Some(&lib_info))
|
.select_language(None, current_dir, None, Some(&lib_info))
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to load language for path \"{}\"",
|
"Failed to load language for path \"{}\"",
|
||||||
|
|
@ -1377,18 +1527,11 @@ impl Query {
|
||||||
loader.find_all_languages(&loader_config)?;
|
loader.find_all_languages(&loader_config)?;
|
||||||
let query_path = Path::new(&self.query_path);
|
let query_path = Path::new(&self.query_path);
|
||||||
|
|
||||||
let byte_range = self.byte_range.as_ref().and_then(|range| {
|
let byte_range = parse_range(&self.byte_range, |x| x)?;
|
||||||
let mut parts = range.split(':');
|
let point_range = parse_range(&self.row_range, |row| Point::new(row, 0))?;
|
||||||
let start = parts.next()?.parse().ok()?;
|
let containing_byte_range = parse_range(&self.containing_byte_range, |x| x)?;
|
||||||
let end = parts.next().unwrap().parse().ok()?;
|
let containing_point_range =
|
||||||
Some(start..end)
|
parse_range(&self.containing_row_range, |row| Point::new(row, 0))?;
|
||||||
});
|
|
||||||
let point_range = self.row_range.as_ref().and_then(|range| {
|
|
||||||
let mut parts = range.split(':');
|
|
||||||
let start = parts.next()?.parse().ok()?;
|
|
||||||
let end = parts.next().unwrap().parse().ok()?;
|
|
||||||
Some(Point::new(start, 0)..Point::new(end, 0))
|
|
||||||
});
|
|
||||||
|
|
||||||
let cancellation_flag = util::cancel_on_signal();
|
let cancellation_flag = util::cancel_on_signal();
|
||||||
|
|
||||||
|
|
@ -1407,25 +1550,30 @@ impl Query {
|
||||||
match input {
|
match input {
|
||||||
CliInput::Paths(paths) => {
|
CliInput::Paths(paths) => {
|
||||||
let language = loader.select_language(
|
let language = loader.select_language(
|
||||||
Path::new(&paths[0]),
|
Some(Path::new(&paths[0])),
|
||||||
current_dir,
|
current_dir,
|
||||||
self.scope.as_deref(),
|
self.scope.as_deref(),
|
||||||
lib_info.as_ref(),
|
lib_info.as_ref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let opts = QueryFileOptions {
|
||||||
|
ordered_captures: self.captures,
|
||||||
|
byte_range,
|
||||||
|
point_range,
|
||||||
|
containing_byte_range,
|
||||||
|
containing_point_range,
|
||||||
|
quiet: self.quiet,
|
||||||
|
print_time: self.time,
|
||||||
|
stdin: false,
|
||||||
|
};
|
||||||
for path in paths {
|
for path in paths {
|
||||||
query::query_file_at_path(
|
query::query_file_at_path(
|
||||||
&language,
|
&language,
|
||||||
&path,
|
&path,
|
||||||
&path.display().to_string(),
|
&path.display().to_string(),
|
||||||
query_path,
|
query_path,
|
||||||
self.captures,
|
&opts,
|
||||||
byte_range.clone(),
|
None,
|
||||||
point_range.clone(),
|
|
||||||
self.test,
|
|
||||||
self.quiet,
|
|
||||||
self.time,
|
|
||||||
false,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1438,7 +1586,7 @@ impl Query {
|
||||||
let languages = loader.languages_at_path(current_dir)?;
|
let languages = loader.languages_at_path(current_dir)?;
|
||||||
let language = if let Some(ref lib_path) = self.lib_path {
|
let language = if let Some(ref lib_path) = self.lib_path {
|
||||||
&loader
|
&loader
|
||||||
.select_language(lib_path, current_dir, None, lib_info.as_ref())
|
.select_language(None, current_dir, None, lib_info.as_ref())
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"Failed to load language for path \"{}\"",
|
"Failed to load language for path \"{}\"",
|
||||||
|
|
@ -1453,19 +1601,17 @@ impl Query {
|
||||||
.map(|(l, _)| l.clone())
|
.map(|(l, _)| l.clone())
|
||||||
.ok_or_else(|| anyhow!("No language found"))?
|
.ok_or_else(|| anyhow!("No language found"))?
|
||||||
};
|
};
|
||||||
query::query_file_at_path(
|
let opts = QueryFileOptions {
|
||||||
language,
|
ordered_captures: self.captures,
|
||||||
&path,
|
|
||||||
&name,
|
|
||||||
query_path,
|
|
||||||
self.captures,
|
|
||||||
byte_range,
|
byte_range,
|
||||||
point_range,
|
point_range,
|
||||||
self.test,
|
containing_byte_range,
|
||||||
self.quiet,
|
containing_point_range,
|
||||||
self.time,
|
quiet: self.quiet,
|
||||||
true,
|
print_time: self.time,
|
||||||
)?;
|
stdin: true,
|
||||||
|
};
|
||||||
|
query::query_file_at_path(language, &path, &name, query_path, &opts, None)?;
|
||||||
fs::remove_file(path)?;
|
fs::remove_file(path)?;
|
||||||
}
|
}
|
||||||
CliInput::Stdin(contents) => {
|
CliInput::Stdin(contents) => {
|
||||||
|
|
@ -1474,20 +1620,18 @@ impl Query {
|
||||||
|
|
||||||
let path = get_tmp_source_file(&contents)?;
|
let path = get_tmp_source_file(&contents)?;
|
||||||
let language =
|
let language =
|
||||||
loader.select_language(&path, current_dir, None, lib_info.as_ref())?;
|
loader.select_language(None, current_dir, None, lib_info.as_ref())?;
|
||||||
query::query_file_at_path(
|
let opts = QueryFileOptions {
|
||||||
&language,
|
ordered_captures: self.captures,
|
||||||
&path,
|
|
||||||
"stdin",
|
|
||||||
query_path,
|
|
||||||
self.captures,
|
|
||||||
byte_range,
|
byte_range,
|
||||||
point_range,
|
point_range,
|
||||||
self.test,
|
containing_byte_range,
|
||||||
self.quiet,
|
containing_point_range,
|
||||||
self.time,
|
quiet: self.quiet,
|
||||||
true,
|
print_time: self.time,
|
||||||
)?;
|
stdin: true,
|
||||||
|
};
|
||||||
|
query::query_file_at_path(&language, &path, "stdin", query_path, &opts, None)?;
|
||||||
fs::remove_file(path)?;
|
fs::remove_file(path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1504,6 +1648,7 @@ impl Highlight {
|
||||||
let loader_config = config.get()?;
|
let loader_config = config.get()?;
|
||||||
loader.find_all_languages(&loader_config)?;
|
loader.find_all_languages(&loader_config)?;
|
||||||
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
||||||
|
let languages = loader.languages_at_path(current_dir)?;
|
||||||
|
|
||||||
let cancellation_flag = util::cancel_on_signal();
|
let cancellation_flag = util::cancel_on_signal();
|
||||||
|
|
||||||
|
|
@ -1584,7 +1729,6 @@ impl Highlight {
|
||||||
} => {
|
} => {
|
||||||
let path = get_tmp_source_file(&contents)?;
|
let path = get_tmp_source_file(&contents)?;
|
||||||
|
|
||||||
let languages = loader.languages_at_path(current_dir)?;
|
|
||||||
let language = languages
|
let language = languages
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, n)| language_names.contains(&Box::from(n.as_str())))
|
.find(|(_, n)| language_names.contains(&Box::from(n.as_str())))
|
||||||
|
|
@ -1615,7 +1759,6 @@ impl Highlight {
|
||||||
if let (Some(l), Some(lc)) = (language.clone(), language_configuration) {
|
if let (Some(l), Some(lc)) = (language.clone(), language_configuration) {
|
||||||
(l, lc)
|
(l, lc)
|
||||||
} else {
|
} else {
|
||||||
let languages = loader.languages_at_path(current_dir)?;
|
|
||||||
let language = languages
|
let language = languages
|
||||||
.first()
|
.first()
|
||||||
.map(|(l, _)| l.clone())
|
.map(|(l, _)| l.clone())
|
||||||
|
|
@ -1988,3 +2131,32 @@ fn get_lib_info<'a>(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a range string of the form "start:end" into an optional Range<T>.
|
||||||
|
fn parse_range<T>(
|
||||||
|
range_str: &Option<String>,
|
||||||
|
make: impl Fn(usize) -> T,
|
||||||
|
) -> Result<Option<std::ops::Range<T>>> {
|
||||||
|
if let Some(range) = range_str.as_ref() {
|
||||||
|
let err_msg = format!("Invalid range '{range}', expected 'start:end'");
|
||||||
|
let mut parts = range.split(':');
|
||||||
|
|
||||||
|
let Some(part) = parts.next() else {
|
||||||
|
Err(anyhow!(err_msg))?
|
||||||
|
};
|
||||||
|
let Ok(start) = part.parse::<usize>() else {
|
||||||
|
Err(anyhow!(err_msg))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(part) = parts.next() else {
|
||||||
|
Err(anyhow!(err_msg))?
|
||||||
|
};
|
||||||
|
let Ok(end) = part.parse::<usize>() else {
|
||||||
|
Err(anyhow!(err_msg))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(make(start)..make(end)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use anstyle::{AnsiColor, Color, RgbColor};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tree_sitter::{
|
use tree_sitter::{
|
||||||
ffi, InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree,
|
ffi, InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree,
|
||||||
|
|
@ -19,7 +20,7 @@ use tree_sitter::{
|
||||||
|
|
||||||
use crate::{fuzz::edits::Edit, logger::paint, util};
|
use crate::{fuzz::edits::Edit, logger::paint, util};
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Serialize, JsonSchema)]
|
||||||
pub struct Stats {
|
pub struct Stats {
|
||||||
pub successful_parses: usize,
|
pub successful_parses: usize,
|
||||||
pub total_parses: usize,
|
pub total_parses: usize,
|
||||||
|
|
@ -230,10 +231,21 @@ impl ParseSummary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Default)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct ParseStats {
|
pub struct ParseStats {
|
||||||
pub parse_summaries: Vec<ParseSummary>,
|
pub parse_summaries: Vec<ParseSummary>,
|
||||||
pub cumulative_stats: Stats,
|
pub cumulative_stats: Stats,
|
||||||
|
pub source_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ParseStats {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
parse_summaries: Vec::new(),
|
||||||
|
cumulative_stats: Stats::default(),
|
||||||
|
source_count: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, ValueEnum, Debug, Copy, Clone, Default, Eq, PartialEq)]
|
#[derive(Serialize, ValueEnum, Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||||
|
|
@ -503,16 +515,22 @@ pub fn parse_file_at_path(
|
||||||
|
|
||||||
if opts.output == ParseOutput::Cst {
|
if opts.output == ParseOutput::Cst {
|
||||||
render_cst(&source_code, &tree, &mut cursor, opts, &mut stdout)?;
|
render_cst(&source_code, &tree, &mut cursor, opts, &mut stdout)?;
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.output == ParseOutput::Xml {
|
if opts.output == ParseOutput::Xml {
|
||||||
let mut needs_newline = false;
|
let mut needs_newline = false;
|
||||||
let mut indent_level = 0;
|
let mut indent_level = 2;
|
||||||
let mut did_visit_children = false;
|
let mut did_visit_children = false;
|
||||||
let mut had_named_children = false;
|
let mut had_named_children = false;
|
||||||
let mut tags = Vec::<&str>::new();
|
let mut tags = Vec::<&str>::new();
|
||||||
writeln!(&mut stdout, "<?xml version=\"1.0\"?>")?;
|
|
||||||
|
// If we're parsing the first file, write the header
|
||||||
|
if opts.stats.parse_summaries.is_empty() {
|
||||||
|
writeln!(&mut stdout, "<?xml version=\"1.0\"?>")?;
|
||||||
|
writeln!(&mut stdout, "<sources>")?;
|
||||||
|
}
|
||||||
|
writeln!(&mut stdout, " <source name=\"{}\">", path.display())?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let node = cursor.node();
|
let node = cursor.node();
|
||||||
let is_named = node.is_named();
|
let is_named = node.is_named();
|
||||||
|
|
@ -591,8 +609,14 @@ pub fn parse_file_at_path(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
writeln!(&mut stdout)?;
|
||||||
|
writeln!(&mut stdout, " </source>")?;
|
||||||
|
|
||||||
|
// If we parsed the last file, write the closing tag for the `sources` header
|
||||||
|
if opts.stats.parse_summaries.len() == opts.stats.source_count - 1 {
|
||||||
|
writeln!(&mut stdout, "</sources>")?;
|
||||||
|
}
|
||||||
cursor.reset(tree.root_node());
|
cursor.reset(tree.root_node());
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.output == ParseOutput::Dot {
|
if opts.output == ParseOutput::Dot {
|
||||||
|
|
@ -650,10 +674,9 @@ pub fn parse_file_at_path(
|
||||||
width = max_path_length
|
width = max_path_length
|
||||||
)?;
|
)?;
|
||||||
if let Some(node) = first_error {
|
if let Some(node) = first_error {
|
||||||
let start = node.start_position();
|
let node_kind = node.kind();
|
||||||
let end = node.end_position();
|
let mut node_text = String::with_capacity(node_kind.len());
|
||||||
let mut node_text = String::new();
|
for c in node_kind.chars() {
|
||||||
for c in node.kind().chars() {
|
|
||||||
if let Some(escaped) = escape_invisible(c) {
|
if let Some(escaped) = escape_invisible(c) {
|
||||||
node_text += escaped;
|
node_text += escaped;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -670,6 +693,9 @@ pub fn parse_file_at_path(
|
||||||
} else {
|
} else {
|
||||||
write!(&mut stdout, "{node_text}")?;
|
write!(&mut stdout, "{node_text}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let start = node.start_position();
|
||||||
|
let end = node.end_position();
|
||||||
write!(
|
write!(
|
||||||
&mut stdout,
|
&mut stdout,
|
||||||
" [{}, {}] - [{}, {}])",
|
" [{}, {}] - [{}, {}])",
|
||||||
|
|
@ -758,7 +784,7 @@ pub fn render_cst<'a, 'b: 'a>(
|
||||||
.map(|(row, col)| (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1)
|
.map(|(row, col)| (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
let mut indent_level = 1;
|
let mut indent_level = usize::from(!opts.no_ranges);
|
||||||
let mut did_visit_children = false;
|
let mut did_visit_children = false;
|
||||||
let mut in_error = false;
|
let mut in_error = false;
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -856,35 +882,24 @@ fn write_node_text(
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let formatted_line = render_line_feed(line, opts);
|
let formatted_line = render_line_feed(line, opts);
|
||||||
if !opts.no_ranges {
|
write!(
|
||||||
write!(
|
out,
|
||||||
out,
|
"{}{}{}{}{}{}",
|
||||||
"{}{}{}{}{}{}",
|
if multiline { "\n" } else { " " },
|
||||||
if multiline { "\n" } else { "" },
|
if multiline && !opts.no_ranges {
|
||||||
if multiline {
|
render_node_range(opts, cursor, is_named, true, total_width, node_range)
|
||||||
render_node_range(opts, cursor, is_named, true, total_width, node_range)
|
} else {
|
||||||
} else {
|
String::new()
|
||||||
String::new()
|
},
|
||||||
},
|
if multiline {
|
||||||
if multiline {
|
" ".repeat(indent_level + 1)
|
||||||
" ".repeat(indent_level + 1)
|
} else {
|
||||||
} else {
|
String::new()
|
||||||
String::new()
|
},
|
||||||
},
|
paint(quote_color, &String::from(quote)),
|
||||||
paint(quote_color, &String::from(quote)),
|
paint(color, &render_node_text(&formatted_line)),
|
||||||
&paint(color, &render_node_text(&formatted_line)),
|
paint(quote_color, &String::from(quote)),
|
||||||
paint(quote_color, &String::from(quote)),
|
)?;
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
out,
|
|
||||||
"\n{}{}{}{}",
|
|
||||||
" ".repeat(indent_level + 1),
|
|
||||||
paint(quote_color, &String::from(quote)),
|
|
||||||
&paint(color, &render_node_text(&formatted_line)),
|
|
||||||
paint(quote_color, &String::from(quote)),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -938,7 +953,7 @@ fn render_node_range(
|
||||||
|
|
||||||
fn cst_render_node(
|
fn cst_render_node(
|
||||||
opts: &ParseFileOptions,
|
opts: &ParseFileOptions,
|
||||||
cursor: &mut TreeCursor,
|
cursor: &TreeCursor,
|
||||||
source_code: &[u8],
|
source_code: &[u8],
|
||||||
out: &mut impl Write,
|
out: &mut impl Write,
|
||||||
total_width: usize,
|
total_width: usize,
|
||||||
|
|
@ -984,10 +999,9 @@ fn cst_render_node(
|
||||||
} else {
|
} else {
|
||||||
opts.parse_theme.node_kind
|
opts.parse_theme.node_kind
|
||||||
};
|
};
|
||||||
write!(out, "{}", paint(kind_color, node.kind()),)?;
|
write!(out, "{}", paint(kind_color, node.kind()))?;
|
||||||
|
|
||||||
if node.child_count() == 0 {
|
if node.child_count() == 0 {
|
||||||
write!(out, " ")?;
|
|
||||||
// Node text from a pattern or external scanner
|
// Node text from a pattern or external scanner
|
||||||
write_node_text(
|
write_node_text(
|
||||||
opts,
|
opts,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
--light-scrollbar-track: #f1f1f1;
|
--light-scrollbar-track: #f1f1f1;
|
||||||
--light-scrollbar-thumb: #c1c1c1;
|
--light-scrollbar-thumb: #c1c1c1;
|
||||||
--light-scrollbar-thumb-hover: #a8a8a8;
|
--light-scrollbar-thumb-hover: #a8a8a8;
|
||||||
|
--light-tree-row-bg: #e3f2fd;
|
||||||
|
|
||||||
--dark-bg: #1d1f21;
|
--dark-bg: #1d1f21;
|
||||||
--dark-border: #2d2d2d;
|
--dark-border: #2d2d2d;
|
||||||
|
|
@ -28,6 +29,7 @@
|
||||||
--dark-scrollbar-track: #25282c;
|
--dark-scrollbar-track: #25282c;
|
||||||
--dark-scrollbar-thumb: #4a4d51;
|
--dark-scrollbar-thumb: #4a4d51;
|
||||||
--dark-scrollbar-thumb-hover: #5a5d61;
|
--dark-scrollbar-thumb-hover: #5a5d61;
|
||||||
|
--dark-tree-row-bg: #373737;
|
||||||
|
|
||||||
--primary-color: #0550ae;
|
--primary-color: #0550ae;
|
||||||
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
||||||
|
|
@ -42,6 +44,7 @@
|
||||||
--text-color: var(--dark-text);
|
--text-color: var(--dark-text);
|
||||||
--panel-bg: var(--dark-panel-bg);
|
--panel-bg: var(--dark-panel-bg);
|
||||||
--code-bg: var(--dark-code-bg);
|
--code-bg: var(--dark-code-bg);
|
||||||
|
--tree-row-bg: var(--dark-tree-row-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
|
|
@ -50,6 +53,7 @@
|
||||||
--text-color: var(--light-text);
|
--text-color: var(--light-text);
|
||||||
--panel-bg: white;
|
--panel-bg: white;
|
||||||
--code-bg: white;
|
--code-bg: white;
|
||||||
|
--tree-row-bg: var(--light-tree-row-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base Styles */
|
/* Base Styles */
|
||||||
|
|
@ -275,7 +279,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#output-container a.highlighted {
|
#output-container a.highlighted {
|
||||||
background-color: #d9d9d9;
|
background-color: #cae2ff;
|
||||||
color: red;
|
color: red;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
@ -346,7 +350,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& #output-container a.highlighted {
|
& #output-container a.highlighted {
|
||||||
background-color: #373b41;
|
background-color: #656669;
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,6 +377,9 @@
|
||||||
color: var(--dark-text);
|
color: var(--dark-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tree-row:has(.highlighted) {
|
||||||
|
background-color: var(--tree-row-bg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,35 @@ use std::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anstyle::AnsiColor;
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use streaming_iterator::StreamingIterator;
|
use streaming_iterator::StreamingIterator;
|
||||||
use tree_sitter::{Language, Parser, Point, Query, QueryCursor};
|
use tree_sitter::{Language, Parser, Point, Query, QueryCursor};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
logger::paint,
|
|
||||||
query_testing::{self, to_utf8_point},
|
query_testing::{self, to_utf8_point},
|
||||||
|
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[derive(Default)]
|
||||||
|
pub struct QueryFileOptions {
|
||||||
|
pub ordered_captures: bool,
|
||||||
|
pub byte_range: Option<Range<usize>>,
|
||||||
|
pub point_range: Option<Range<Point>>,
|
||||||
|
pub containing_byte_range: Option<Range<usize>>,
|
||||||
|
pub containing_point_range: Option<Range<Point>>,
|
||||||
|
pub quiet: bool,
|
||||||
|
pub print_time: bool,
|
||||||
|
pub stdin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn query_file_at_path(
|
pub fn query_file_at_path(
|
||||||
language: &Language,
|
language: &Language,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
query_path: &Path,
|
query_path: &Path,
|
||||||
ordered_captures: bool,
|
opts: &QueryFileOptions,
|
||||||
byte_range: Option<Range<usize>>,
|
test_summary: Option<&mut TestSummary>,
|
||||||
point_range: Option<Range<Point>>,
|
|
||||||
should_test: bool,
|
|
||||||
quiet: bool,
|
|
||||||
print_time: bool,
|
|
||||||
stdin: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
|
@ -39,19 +44,26 @@ pub fn query_file_at_path(
|
||||||
let query = Query::new(language, &query_source).with_context(|| "Query compilation failed")?;
|
let query = Query::new(language, &query_source).with_context(|| "Query compilation failed")?;
|
||||||
|
|
||||||
let mut query_cursor = QueryCursor::new();
|
let mut query_cursor = QueryCursor::new();
|
||||||
if let Some(range) = byte_range {
|
if let Some(ref range) = opts.byte_range {
|
||||||
query_cursor.set_byte_range(range);
|
query_cursor.set_byte_range(range.clone());
|
||||||
}
|
}
|
||||||
if let Some(range) = point_range {
|
if let Some(ref range) = opts.point_range {
|
||||||
query_cursor.set_point_range(range);
|
query_cursor.set_point_range(range.clone());
|
||||||
|
}
|
||||||
|
if let Some(ref range) = opts.containing_byte_range {
|
||||||
|
query_cursor.set_containing_byte_range(range.clone());
|
||||||
|
}
|
||||||
|
if let Some(ref range) = opts.containing_point_range {
|
||||||
|
query_cursor.set_containing_point_range(range.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parser = Parser::new();
|
let mut parser = Parser::new();
|
||||||
parser.set_language(language)?;
|
parser.set_language(language)?;
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
let should_test = test_summary.is_some();
|
||||||
|
|
||||||
if !should_test && !stdin {
|
if !should_test && !opts.stdin {
|
||||||
writeln!(&mut stdout, "{name}")?;
|
writeln!(&mut stdout, "{name}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,12 +72,12 @@ pub fn query_file_at_path(
|
||||||
let tree = parser.parse(&source_code, None).unwrap();
|
let tree = parser.parse(&source_code, None).unwrap();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
if ordered_captures {
|
if opts.ordered_captures {
|
||||||
let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
|
let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
|
||||||
while let Some((mat, capture_index)) = captures.next() {
|
while let Some((mat, capture_index)) = captures.next() {
|
||||||
let capture = mat.captures[*capture_index];
|
let capture = mat.captures[*capture_index];
|
||||||
let capture_name = &query.capture_names()[capture.index as usize];
|
let capture_name = &query.capture_names()[capture.index as usize];
|
||||||
if !quiet && !should_test {
|
if !opts.quiet && !should_test {
|
||||||
writeln!(
|
writeln!(
|
||||||
&mut stdout,
|
&mut stdout,
|
||||||
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
|
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
|
||||||
|
|
@ -76,23 +88,25 @@ pub fn query_file_at_path(
|
||||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
results.push(query_testing::CaptureInfo {
|
if should_test {
|
||||||
name: (*capture_name).to_string(),
|
results.push(query_testing::CaptureInfo {
|
||||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
name: (*capture_name).to_string(),
|
||||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||||
});
|
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
|
let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
|
||||||
while let Some(m) = matches.next() {
|
while let Some(m) = matches.next() {
|
||||||
if !quiet && !should_test {
|
if !opts.quiet && !should_test {
|
||||||
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
||||||
}
|
}
|
||||||
for capture in m.captures {
|
for capture in m.captures {
|
||||||
let start = capture.node.start_position();
|
let start = capture.node.start_position();
|
||||||
let end = capture.node.end_position();
|
let end = capture.node.end_position();
|
||||||
let capture_name = &query.capture_names()[capture.index as usize];
|
let capture_name = &query.capture_names()[capture.index as usize];
|
||||||
if !quiet && !should_test {
|
if !opts.quiet && !should_test {
|
||||||
if end.row == start.row {
|
if end.row == start.row {
|
||||||
writeln!(
|
writeln!(
|
||||||
&mut stdout,
|
&mut stdout,
|
||||||
|
|
@ -107,38 +121,52 @@ pub fn query_file_at_path(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.push(query_testing::CaptureInfo {
|
if should_test {
|
||||||
name: (*capture_name).to_string(),
|
results.push(query_testing::CaptureInfo {
|
||||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
name: (*capture_name).to_string(),
|
||||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||||
});
|
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !query_cursor.did_exceed_match_limit() {
|
if query_cursor.did_exceed_match_limit() {
|
||||||
warn!("Query exceeded maximum number of in-progress captures!");
|
warn!("Query exceeded maximum number of in-progress captures!");
|
||||||
}
|
}
|
||||||
if should_test {
|
if should_test {
|
||||||
let path_name = if stdin {
|
let path_name = if opts.stdin {
|
||||||
"stdin"
|
"stdin"
|
||||||
} else {
|
} else {
|
||||||
Path::new(&path).file_name().unwrap().to_str().unwrap()
|
Path::new(&path).file_name().unwrap().to_str().unwrap()
|
||||||
};
|
};
|
||||||
|
// Invariant: `test_summary` will always be `Some` when `should_test` is true
|
||||||
|
let test_summary = test_summary.unwrap();
|
||||||
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
|
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
|
||||||
Ok(assertion_count) => {
|
Ok(assertion_count) => {
|
||||||
println!(
|
test_summary.query_results.add_case(TestResult {
|
||||||
" ✓ {} ({} assertions)",
|
name: path_name.to_string(),
|
||||||
paint(Some(AnsiColor::Green), path_name),
|
info: TestInfo::AssertionTest {
|
||||||
assertion_count
|
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||||
);
|
test_num: test_summary.test_num,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(" ✗ {}", paint(Some(AnsiColor::Red), path_name));
|
test_summary.query_results.add_case(TestResult {
|
||||||
|
name: path_name.to_string(),
|
||||||
|
info: TestInfo::AssertionTest {
|
||||||
|
outcome: TestOutcome::AssertionFailed {
|
||||||
|
error: e.to_string(),
|
||||||
|
},
|
||||||
|
test_num: test_summary.test_num,
|
||||||
|
},
|
||||||
|
});
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if print_time {
|
if opts.print_time {
|
||||||
writeln!(&mut stdout, "{:?}", start.elapsed())?;
|
writeln!(&mut stdout, "{:?}", start.elapsed())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ root = true
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
[*.{json,toml,yml,gyp}]
|
[*.{json,toml,yml,gyp,xml}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.js]
|
[*.{js,ts}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
@ -31,6 +31,10 @@ indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.java]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
[*.go]
|
[*.go]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
|
|
|
||||||
|
|
@ -6,32 +6,33 @@ from ._binding import language
|
||||||
|
|
||||||
|
|
||||||
def _get_query(name, file):
|
def _get_query(name, file):
|
||||||
query = _files(f"{__package__}.queries") / file
|
try:
|
||||||
globals()[name] = query.read_text()
|
query = _files(f"{__package__}") / file
|
||||||
|
globals()[name] = query.read_text()
|
||||||
|
except FileNotFoundError:
|
||||||
|
globals()[name] = None
|
||||||
return globals()[name]
|
return globals()[name]
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
# NOTE: uncomment these to include any queries that this grammar contains:
|
if name == "HIGHLIGHTS_QUERY":
|
||||||
|
return _get_query("HIGHLIGHTS_QUERY", "HIGHLIGHTS_QUERY_PATH")
|
||||||
# if name == "HIGHLIGHTS_QUERY":
|
if name == "INJECTIONS_QUERY":
|
||||||
# return _get_query("HIGHLIGHTS_QUERY", "highlights.scm")
|
return _get_query("INJECTIONS_QUERY", "INJECTIONS_QUERY_PATH")
|
||||||
# if name == "INJECTIONS_QUERY":
|
if name == "LOCALS_QUERY":
|
||||||
# return _get_query("INJECTIONS_QUERY", "injections.scm")
|
return _get_query("LOCALS_QUERY", "LOCALS_QUERY_PATH")
|
||||||
# if name == "LOCALS_QUERY":
|
if name == "TAGS_QUERY":
|
||||||
# return _get_query("LOCALS_QUERY", "locals.scm")
|
return _get_query("TAGS_QUERY", "TAGS_QUERY_PATH")
|
||||||
# if name == "TAGS_QUERY":
|
|
||||||
# return _get_query("TAGS_QUERY", "tags.scm")
|
|
||||||
|
|
||||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"language",
|
"language",
|
||||||
# "HIGHLIGHTS_QUERY",
|
"HIGHLIGHTS_QUERY",
|
||||||
# "INJECTIONS_QUERY",
|
"INJECTIONS_QUERY",
|
||||||
# "LOCALS_QUERY",
|
"LOCALS_QUERY",
|
||||||
# "TAGS_QUERY",
|
"TAGS_QUERY",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing_extensions import CapsuleType
|
from typing_extensions import CapsuleType
|
||||||
|
|
||||||
# NOTE: uncomment these to include any queries that this grammar contains:
|
HIGHLIGHTS_QUERY: Final[str] | None
|
||||||
|
"""The syntax highlighting query for this grammar."""
|
||||||
|
|
||||||
# HIGHLIGHTS_QUERY: Final[str]
|
INJECTIONS_QUERY: Final[str] | None
|
||||||
# INJECTIONS_QUERY: Final[str]
|
"""The language injection query for this grammar."""
|
||||||
# LOCALS_QUERY: Final[str]
|
|
||||||
# TAGS_QUERY: Final[str]
|
|
||||||
|
|
||||||
def language() -> CapsuleType: ...
|
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."""
|
||||||
|
|
|
||||||
65
crates/cli/src/templates/binding.java
Normal file
65
crates/cli/src/templates/binding.java
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package PARSER_NS_CLEANED.jtreesitter.LOWER_PARSER_NAME;
|
||||||
|
|
||||||
|
import java.lang.foreign.*;
|
||||||
|
|
||||||
|
public final class PARSER_CLASS_NAME {
|
||||||
|
private static final ValueLayout VOID_PTR =
|
||||||
|
ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE));
|
||||||
|
private static final FunctionDescriptor FUNC_DESC = FunctionDescriptor.of(VOID_PTR);
|
||||||
|
private static final Linker LINKER = Linker.nativeLinker();
|
||||||
|
private static final PARSER_CLASS_NAME INSTANCE = new PARSER_CLASS_NAME();
|
||||||
|
|
||||||
|
private final Arena arena = Arena.ofAuto();
|
||||||
|
private volatile SymbolLookup lookup = null;
|
||||||
|
|
||||||
|
private PARSER_CLASS_NAME() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tree-sitter language for this grammar.
|
||||||
|
*/
|
||||||
|
public static MemorySegment language() {
|
||||||
|
if (INSTANCE.lookup == null)
|
||||||
|
INSTANCE.lookup = INSTANCE.findLibrary();
|
||||||
|
return language(INSTANCE.lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tree-sitter language for this grammar.
|
||||||
|
*
|
||||||
|
* <strong>The {@linkplain Arena} used in the {@code lookup}
|
||||||
|
* must not be closed while the language is being used.</strong>
|
||||||
|
*/
|
||||||
|
public static MemorySegment language(SymbolLookup lookup) {
|
||||||
|
return call(lookup, "tree_sitter_PARSER_NAME");
|
||||||
|
}
|
||||||
|
|
||||||
|
private SymbolLookup findLibrary() {
|
||||||
|
try {
|
||||||
|
var library = System.mapLibraryName("tree-sitter-KEBAB_PARSER_NAME");
|
||||||
|
return SymbolLookup.libraryLookup(library, arena);
|
||||||
|
} catch (IllegalArgumentException ex1) {
|
||||||
|
try {
|
||||||
|
System.loadLibrary("tree-sitter-KEBAB_PARSER_NAME");
|
||||||
|
return SymbolLookup.loaderLookup();
|
||||||
|
} catch (UnsatisfiedLinkError ex2) {
|
||||||
|
ex1.addSuppressed(ex2);
|
||||||
|
throw ex1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnsatisfiedLinkError unresolved(String name) {
|
||||||
|
return new UnsatisfiedLinkError("Unresolved symbol: %s".formatted(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private static MemorySegment call(SymbolLookup lookup, String name) throws UnsatisfiedLinkError {
|
||||||
|
var address = lookup.find(name).orElseThrow(() -> unresolved(name));
|
||||||
|
try {
|
||||||
|
var function = LINKER.downcallHandle(address, FUNC_DESC);
|
||||||
|
return (MemorySegment) function.invokeExact();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException("Call to %s failed".formatted(name), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,4 +36,21 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
|
c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
|
||||||
|
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_highlights_query)");
|
||||||
|
if !"HIGHLIGHTS_QUERY_PATH".is_empty() && std::path::Path::new("HIGHLIGHTS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_highlights_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_injections_query)");
|
||||||
|
if !"INJECTIONS_QUERY_PATH".is_empty() && std::path::Path::new("INJECTIONS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_injections_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_locals_query)");
|
||||||
|
if !"LOCALS_QUERY_PATH".is_empty() && std::path::Path::new("LOCALS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_locals_query");
|
||||||
|
}
|
||||||
|
println!("cargo:rustc-check-cfg=cfg(with_tags_query)");
|
||||||
|
if !"TAGS_QUERY_PATH".is_empty() && std::path::Path::new("TAGS_QUERY_PATH").exists() {
|
||||||
|
println!("cargo:rustc-cfg=with_tags_query");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
.{
|
.{
|
||||||
.name = .tree_sitter_PARSER_NAME,
|
.name = .tree_sitter_PARSER_NAME,
|
||||||
|
.fingerprint = PARSER_FINGERPRINT,
|
||||||
.version = "PARSER_VERSION",
|
.version = "PARSER_VERSION",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.tree_sitter = .{
|
.tree_sitter = .{
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,19 @@ include(GNUInstallDirs)
|
||||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
|
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
|
||||||
|
|
||||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
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"
|
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js"
|
||||||
COMMAND "${TREE_SITTER_CLI}" generate grammar.js
|
COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser
|
||||||
--emit=json
|
|
||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
COMMENT "Generating grammar.json")
|
COMMENT "Generating grammar.json")
|
||||||
|
|
||||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
|
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"
|
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||||
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
||||||
--emit=parser --abi=${TREE_SITTER_ABI_VERSION}
|
--abi=${TREE_SITTER_ABI_VERSION}
|
||||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
COMMENT "Generating parser.c")
|
COMMENT "Generating parser.c")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,3 +40,7 @@ Package.resolved linguist-generated
|
||||||
bindings/zig/* linguist-generated
|
bindings/zig/* linguist-generated
|
||||||
build.zig linguist-generated
|
build.zig linguist-generated
|
||||||
build.zig.zon linguist-generated
|
build.zig.zon linguist-generated
|
||||||
|
|
||||||
|
# Java bindings
|
||||||
|
pom.xml linguist-generated
|
||||||
|
bindings/java/** linguist-generated
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,4 @@ zig-out/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.tgz
|
*.tgz
|
||||||
*.zip
|
*.zip
|
||||||
|
*.jar
|
||||||
|
|
|
||||||
39
crates/cli/src/templates/index.d.ts
vendored
39
crates/cli/src/templates/index.d.ts
vendored
|
|
@ -18,10 +18,43 @@ type NodeInfo =
|
||||||
children: ChildNode[];
|
children: ChildNode[];
|
||||||
});
|
});
|
||||||
|
|
||||||
type Language = {
|
/**
|
||||||
|
* 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 CAMEL_PARSER_NAME from "tree-sitter-KEBAB_PARSER_NAME";
|
||||||
|
*
|
||||||
|
* const parser = new Parser();
|
||||||
|
* parser.setLanguage(CAMEL_PARSER_NAME);
|
||||||
|
*/
|
||||||
|
declare const binding: {
|
||||||
|
/**
|
||||||
|
* The inner language object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
language: unknown;
|
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[];
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare const language: Language;
|
export default binding;
|
||||||
export = language;
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
const root = new URL("../..", import.meta.url).pathname;
|
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"
|
const binding = typeof process.versions.bun === "string"
|
||||||
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
|
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
|
||||||
|
|
@ -6,8 +9,29 @@ const binding = typeof process.versions.bun === "string"
|
||||||
: (await import("node-gyp-build")).default(root);
|
: (await import("node-gyp-build")).default(root);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nodeTypes = await import(`${root}/src/node-types.json`, {with: {type: "json"}});
|
const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } });
|
||||||
binding.nodeTypeInfo = nodeTypes.default;
|
binding.nodeTypeInfo = nodeTypes.default;
|
||||||
} catch (_) {}
|
} catch { }
|
||||||
|
|
||||||
|
const queries = [
|
||||||
|
["HIGHLIGHTS_QUERY", `${root}/HIGHLIGHTS_QUERY_PATH`],
|
||||||
|
["INJECTIONS_QUERY", `${root}/INJECTIONS_QUERY_PATH`],
|
||||||
|
["LOCALS_QUERY", `${root}/LOCALS_QUERY_PATH`],
|
||||||
|
["TAGS_QUERY", `${root}/TAGS_QUERY_PATH`],
|
||||||
|
];
|
||||||
|
|
||||||
|
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;
|
export default binding;
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,21 @@ pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_PARSE
|
||||||
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types
|
/// [`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");
|
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
||||||
|
|
||||||
// NOTE: uncomment these to include any queries that this grammar contains:
|
#[cfg(with_highlights_query)]
|
||||||
|
/// The syntax highlighting query for this grammar.
|
||||||
|
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../HIGHLIGHTS_QUERY_PATH");
|
||||||
|
|
||||||
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
#[cfg(with_injections_query)]
|
||||||
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
|
/// The language injection query for this grammar.
|
||||||
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
|
pub const INJECTIONS_QUERY: &str = include_str!("../../INJECTIONS_QUERY_PATH");
|
||||||
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
|
|
||||||
|
#[cfg(with_locals_query)]
|
||||||
|
/// The local variable query for this grammar.
|
||||||
|
pub const LOCALS_QUERY: &str = include_str!("../../LOCALS_QUERY_PATH");
|
||||||
|
|
||||||
|
#[cfg(with_tags_query)]
|
||||||
|
/// The symbol tagging query for this grammar.
|
||||||
|
pub const TAGS_QUERY: &str = include_str!("../../TAGS_QUERY_PATH");
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
||||||
|
|
@ -73,10 +73,10 @@ $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
|
||||||
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
|
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
|
||||||
|
|
||||||
$(SRC_DIR)/grammar.json: grammar.js
|
$(SRC_DIR)/grammar.json: grammar.js
|
||||||
$(TS) generate --emit=json $^
|
$(TS) generate --no-parser $^
|
||||||
|
|
||||||
$(PARSER): $(SRC_DIR)/grammar.json
|
$(PARSER): $(SRC_DIR)/grammar.json
|
||||||
$(TS) generate --emit=parser $^
|
$(TS) generate $^
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/KEBAB_PARSER_NAME '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
|
install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/KEBAB_PARSER_NAME '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prebuildify": "^6.0.1",
|
"prebuildify": "^6.0.1",
|
||||||
"tree-sitter": "^0.22.4",
|
"tree-sitter": "^0.25.0",
|
||||||
"tree-sitter-cli": "^CLI_VERSION"
|
"tree-sitter-cli": "^CLI_VERSION"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"tree-sitter": "^0.22.4"
|
"tree-sitter": "^0.25.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"tree-sitter": {
|
"tree-sitter": {
|
||||||
|
|
|
||||||
154
crates/cli/src/templates/pom.xml
Normal file
154
crates/cli/src/templates/pom.xml
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>PARSER_NS</groupId>
|
||||||
|
<artifactId>jtreesitter-KEBAB_PARSER_NAME</artifactId>
|
||||||
|
<name>JTreeSitter CAMEL_PARSER_NAME</name>
|
||||||
|
<version>PARSER_VERSION</version>
|
||||||
|
<description>PARSER_DESCRIPTION</description>
|
||||||
|
<url>PARSER_URL</url>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>PARSER_LICENSE</name>
|
||||||
|
<url>https://spdx.org/licenses/PARSER_LICENSE.html</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>PARSER_AUTHOR_NAME</name>
|
||||||
|
<email>PARSER_AUTHOR_EMAIL</email>
|
||||||
|
<url>PARSER_AUTHOR_URL</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<url>PARSER_URL</url>
|
||||||
|
<connection>scm:git:git://PARSER_URL_STRIPPED.git</connection>
|
||||||
|
<developerConnection>scm:git:ssh://PARSER_URL_STRIPPED.git</developerConnection>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>23</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.deploy.skip>true</maven.deploy.skip>
|
||||||
|
<gpg.skip>true</gpg.skip>
|
||||||
|
<publish.auto>false</publish.auto>
|
||||||
|
<publish.skip>true</publish.skip>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.tree-sitter</groupId>
|
||||||
|
<artifactId>jtreesitter</artifactId>
|
||||||
|
<version>0.26.0</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<version>6.0.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>bindings/java/main</sourceDirectory>
|
||||||
|
<testSourceDirectory>bindings/java/test</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.5.4</version>
|
||||||
|
<configuration>
|
||||||
|
<reportsDirectory>
|
||||||
|
${project.build.directory}/reports/surefire
|
||||||
|
</reportsDirectory>
|
||||||
|
<argLine>--enable-native-access=ALL-UNNAMED</argLine>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<show>public</show>
|
||||||
|
<nohelp>true</nohelp>
|
||||||
|
<noqualifier>true</noqualifier>
|
||||||
|
<doclint>all,-missing</doclint>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>3.2.8</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>sign</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<bestPractices>true</bestPractices>
|
||||||
|
<gpgArguments>
|
||||||
|
<arg>--no-tty</arg>
|
||||||
|
<arg>--pinentry-mode</arg>
|
||||||
|
<arg>loopback</arg>
|
||||||
|
</gpgArguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.github.mavenplugins</groupId>
|
||||||
|
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>deploy</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>publish</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<waitUntil>validated</waitUntil>
|
||||||
|
<autoPublish>${publish.auto}</autoPublish>
|
||||||
|
<skipPublishing>${publish.skip}</skipPublishing>
|
||||||
|
<outputFilename>${project.artifactId}-${project.version}.zip</outputFilename>
|
||||||
|
<deploymentName>${project.artifactId}-${project.version}.zip</deploymentName>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>ci</id>
|
||||||
|
<activation>
|
||||||
|
<property>
|
||||||
|
<name>env.CI</name>
|
||||||
|
<value>true</value>
|
||||||
|
</property>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<gpg.skip>false</gpg.skip>
|
||||||
|
<publish.auto>true</publish.auto>
|
||||||
|
<publish.skip>false</publish.skip>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
|
|
@ -32,7 +32,7 @@ class BuildExt(build_ext):
|
||||||
class BdistWheel(bdist_wheel):
|
class BdistWheel(bdist_wheel):
|
||||||
def get_tag(self):
|
def get_tag(self):
|
||||||
python, abi, platform = super().get_tag()
|
python, abi, platform = super().get_tag()
|
||||||
if python.startswith("cp"):
|
if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"):
|
||||||
python, abi = "cp310", "abi3"
|
python, abi = "cp310", "abi3"
|
||||||
return python, abi, platform
|
return python, abi, platform
|
||||||
|
|
||||||
|
|
|
||||||
12
crates/cli/src/templates/test.java
Normal file
12
crates/cli/src/templates/test.java
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import io.github.treesitter.jtreesitter.Language;
|
||||||
|
import PARSER_NS_CLEANED.jtreesitter.LOWER_PARSER_NAME.PARSER_CLASS_NAME;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
public class PARSER_CLASS_NAMETest {
|
||||||
|
@Test
|
||||||
|
public void testCanLoadLanguage() {
|
||||||
|
assertDoesNotThrow(() -> new Language(PARSER_CLASS_NAME.language()));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,13 @@
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use anstyle::AnsiColor;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use tree_sitter::Point;
|
use tree_sitter::Point;
|
||||||
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter};
|
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter};
|
||||||
use tree_sitter_loader::{Config, Loader};
|
use tree_sitter_loader::{Config, Loader};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
logger::paint,
|
|
||||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||||
|
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -48,19 +47,7 @@ pub fn test_highlights(
|
||||||
loader_config: &Config,
|
loader_config: &Config,
|
||||||
highlighter: &mut Highlighter,
|
highlighter: &mut Highlighter,
|
||||||
directory: &Path,
|
directory: &Path,
|
||||||
use_color: bool,
|
test_summary: &mut TestSummary,
|
||||||
) -> Result<()> {
|
|
||||||
println!("syntax highlighting:");
|
|
||||||
test_highlights_indented(loader, loader_config, highlighter, directory, use_color, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_highlights_indented(
|
|
||||||
loader: &Loader,
|
|
||||||
loader_config: &Config,
|
|
||||||
highlighter: &mut Highlighter,
|
|
||||||
directory: &Path,
|
|
||||||
use_color: bool,
|
|
||||||
indent_level: usize,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
|
|
||||||
|
|
@ -68,25 +55,22 @@ fn test_highlights_indented(
|
||||||
let highlight_test_file = highlight_test_file?;
|
let highlight_test_file = highlight_test_file?;
|
||||||
let test_file_path = highlight_test_file.path();
|
let test_file_path = highlight_test_file.path();
|
||||||
let test_file_name = highlight_test_file.file_name();
|
let test_file_name = highlight_test_file.file_name();
|
||||||
print!(
|
|
||||||
"{indent:indent_level$}",
|
|
||||||
indent = "",
|
|
||||||
indent_level = indent_level * 2
|
|
||||||
);
|
|
||||||
if test_file_path.is_dir() && test_file_path.read_dir()?.next().is_some() {
|
if test_file_path.is_dir() && test_file_path.read_dir()?.next().is_some() {
|
||||||
println!("{}:", test_file_name.to_string_lossy());
|
test_summary
|
||||||
if test_highlights_indented(
|
.highlight_results
|
||||||
|
.add_group(test_file_name.to_string_lossy().as_ref());
|
||||||
|
if test_highlights(
|
||||||
loader,
|
loader,
|
||||||
loader_config,
|
loader_config,
|
||||||
highlighter,
|
highlighter,
|
||||||
&test_file_path,
|
&test_file_path,
|
||||||
use_color,
|
test_summary,
|
||||||
indent_level + 1,
|
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
|
test_summary.highlight_results.pop_traversal();
|
||||||
} else {
|
} else {
|
||||||
let (language, language_config) = loader
|
let (language, language_config) = loader
|
||||||
.language_configuration_for_file_name(&test_file_path)?
|
.language_configuration_for_file_name(&test_file_path)?
|
||||||
|
|
@ -98,7 +82,12 @@ fn test_highlights_indented(
|
||||||
})?;
|
})?;
|
||||||
let highlight_config = language_config
|
let highlight_config = language_config
|
||||||
.highlight_config(language, None)?
|
.highlight_config(language, None)?
|
||||||
.ok_or_else(|| anyhow!("No highlighting config found for {test_file_path:?}"))?;
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"No highlighting config found for {}",
|
||||||
|
test_file_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
match test_highlight(
|
match test_highlight(
|
||||||
loader,
|
loader,
|
||||||
highlighter,
|
highlighter,
|
||||||
|
|
@ -106,30 +95,28 @@ fn test_highlights_indented(
|
||||||
fs::read(&test_file_path)?.as_slice(),
|
fs::read(&test_file_path)?.as_slice(),
|
||||||
) {
|
) {
|
||||||
Ok(assertion_count) => {
|
Ok(assertion_count) => {
|
||||||
println!(
|
test_summary.highlight_results.add_case(TestResult {
|
||||||
"✓ {} ({assertion_count} assertions)",
|
name: test_file_name.to_string_lossy().to_string(),
|
||||||
paint(
|
info: TestInfo::AssertionTest {
|
||||||
use_color.then_some(AnsiColor::Green),
|
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||||
test_file_name.to_string_lossy().as_ref()
|
test_num: test_summary.test_num,
|
||||||
),
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
test_summary.highlight_results.add_case(TestResult {
|
||||||
"✗ {}",
|
name: test_file_name.to_string_lossy().to_string(),
|
||||||
paint(
|
info: TestInfo::AssertionTest {
|
||||||
use_color.then_some(AnsiColor::Red),
|
outcome: TestOutcome::AssertionFailed {
|
||||||
test_file_name.to_string_lossy().as_ref()
|
error: e.to_string(),
|
||||||
)
|
},
|
||||||
);
|
test_num: test_summary.test_num,
|
||||||
println!(
|
},
|
||||||
"{indent:indent_level$} {e}",
|
});
|
||||||
indent = "",
|
|
||||||
indent_level = indent_level * 2
|
|
||||||
);
|
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
test_summary.test_num += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use anstyle::AnsiColor;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use tree_sitter_loader::{Config, Loader};
|
use tree_sitter_loader::{Config, Loader};
|
||||||
use tree_sitter_tags::{TagsConfiguration, TagsContext};
|
use tree_sitter_tags::{TagsConfiguration, TagsContext};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
logger::paint,
|
|
||||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||||
|
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -47,19 +46,7 @@ pub fn test_tags(
|
||||||
loader_config: &Config,
|
loader_config: &Config,
|
||||||
tags_context: &mut TagsContext,
|
tags_context: &mut TagsContext,
|
||||||
directory: &Path,
|
directory: &Path,
|
||||||
use_color: bool,
|
test_summary: &mut TestSummary,
|
||||||
) -> Result<()> {
|
|
||||||
println!("tags:");
|
|
||||||
test_tags_indented(loader, loader_config, tags_context, directory, use_color, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_tags_indented(
|
|
||||||
loader: &Loader,
|
|
||||||
loader_config: &Config,
|
|
||||||
tags_context: &mut TagsContext,
|
|
||||||
directory: &Path,
|
|
||||||
use_color: bool,
|
|
||||||
indent_level: usize,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
|
|
||||||
|
|
@ -67,25 +54,22 @@ pub fn test_tags_indented(
|
||||||
let tag_test_file = tag_test_file?;
|
let tag_test_file = tag_test_file?;
|
||||||
let test_file_path = tag_test_file.path();
|
let test_file_path = tag_test_file.path();
|
||||||
let test_file_name = tag_test_file.file_name();
|
let test_file_name = tag_test_file.file_name();
|
||||||
print!(
|
|
||||||
"{indent:indent_level$}",
|
|
||||||
indent = "",
|
|
||||||
indent_level = indent_level * 2
|
|
||||||
);
|
|
||||||
if test_file_path.is_dir() && test_file_path.read_dir()?.next().is_some() {
|
if test_file_path.is_dir() && test_file_path.read_dir()?.next().is_some() {
|
||||||
println!("{}:", test_file_name.to_string_lossy());
|
test_summary
|
||||||
if test_tags_indented(
|
.tag_results
|
||||||
|
.add_group(test_file_name.to_string_lossy().as_ref());
|
||||||
|
if test_tags(
|
||||||
loader,
|
loader,
|
||||||
loader_config,
|
loader_config,
|
||||||
tags_context,
|
tags_context,
|
||||||
&test_file_path,
|
&test_file_path,
|
||||||
use_color,
|
test_summary,
|
||||||
indent_level + 1,
|
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
|
test_summary.tag_results.pop_traversal();
|
||||||
} else {
|
} else {
|
||||||
let (language, language_config) = loader
|
let (language, language_config) = loader
|
||||||
.language_configuration_for_file_name(&test_file_path)?
|
.language_configuration_for_file_name(&test_file_path)?
|
||||||
|
|
@ -97,37 +81,35 @@ pub fn test_tags_indented(
|
||||||
})?;
|
})?;
|
||||||
let tags_config = language_config
|
let tags_config = language_config
|
||||||
.tags_config(language)?
|
.tags_config(language)?
|
||||||
.ok_or_else(|| anyhow!("No tags config found for {test_file_path:?}"))?;
|
.ok_or_else(|| anyhow!("No tags config found for {}", test_file_path.display()))?;
|
||||||
match test_tag(
|
match test_tag(
|
||||||
tags_context,
|
tags_context,
|
||||||
tags_config,
|
tags_config,
|
||||||
fs::read(&test_file_path)?.as_slice(),
|
fs::read(&test_file_path)?.as_slice(),
|
||||||
) {
|
) {
|
||||||
Ok(assertion_count) => {
|
Ok(assertion_count) => {
|
||||||
println!(
|
test_summary.tag_results.add_case(TestResult {
|
||||||
"✓ {} ({assertion_count} assertions)",
|
name: test_file_name.to_string_lossy().to_string(),
|
||||||
paint(
|
info: TestInfo::AssertionTest {
|
||||||
use_color.then_some(AnsiColor::Green),
|
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||||
test_file_name.to_string_lossy().as_ref()
|
test_num: test_summary.test_num,
|
||||||
),
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
test_summary.tag_results.add_case(TestResult {
|
||||||
"✗ {}",
|
name: test_file_name.to_string_lossy().to_string(),
|
||||||
paint(
|
info: TestInfo::AssertionTest {
|
||||||
use_color.then_some(AnsiColor::Red),
|
outcome: TestOutcome::AssertionFailed {
|
||||||
test_file_name.to_string_lossy().as_ref()
|
error: e.to_string(),
|
||||||
)
|
},
|
||||||
);
|
test_num: test_summary.test_num,
|
||||||
println!(
|
},
|
||||||
"{indent:indent_level$} {e}",
|
});
|
||||||
indent = "",
|
|
||||||
indent_level = indent_level * 2
|
|
||||||
);
|
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
test_summary.test_num += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
mod async_context_test;
|
mod async_boundary_test;
|
||||||
mod corpus_test;
|
mod corpus_test;
|
||||||
mod detect_language;
|
mod detect_language;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod highlight_test;
|
mod highlight_test;
|
||||||
mod language_test;
|
mod language_test;
|
||||||
mod node_test;
|
mod node_test;
|
||||||
mod parser_hang_test;
|
|
||||||
mod parser_test;
|
mod parser_test;
|
||||||
mod pathological_test;
|
mod pathological_test;
|
||||||
mod query_test;
|
mod query_test;
|
||||||
|
|
@ -27,6 +26,8 @@ pub use crate::fuzz::{
|
||||||
ITERATION_COUNT,
|
ITERATION_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use helpers::fixtures::get_language;
|
||||||
|
|
||||||
/// This is a simple wrapper around [`tree_sitter_generate::generate_parser_for_grammar`], because
|
/// This is a simple wrapper around [`tree_sitter_generate::generate_parser_for_grammar`], because
|
||||||
/// our tests do not need to pass in a version number, only the grammar JSON.
|
/// our tests do not need to pass in a version number, only the grammar JSON.
|
||||||
fn generate_parser(grammar_json: &str) -> GenerateResult<(String, String)> {
|
fn generate_parser(grammar_json: &str) -> GenerateResult<(String, String)> {
|
||||||
|
|
|
||||||
150
crates/cli/src/tests/async_boundary_test.rs
Normal file
150
crates/cli/src/tests/async_boundary_test.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
ptr,
|
||||||
|
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||||
|
};
|
||||||
|
|
||||||
|
use tree_sitter::Parser;
|
||||||
|
|
||||||
|
use super::helpers::fixtures::get_language;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_node_across_async_boundaries() {
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
let language = get_language("bash");
|
||||||
|
parser.set_language(&language).unwrap();
|
||||||
|
let tree = parser.parse("#", None).unwrap();
|
||||||
|
let root = tree.root_node();
|
||||||
|
|
||||||
|
let (result, yields) = simple_async_executor(async {
|
||||||
|
let root_ref = &root;
|
||||||
|
|
||||||
|
// Test node captured by value
|
||||||
|
let fut_by_value = async {
|
||||||
|
yield_once().await;
|
||||||
|
root.child(0).unwrap().kind()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test node captured by reference
|
||||||
|
let fut_by_ref = async {
|
||||||
|
yield_once().await;
|
||||||
|
root_ref.child(0).unwrap().kind()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result1 = fut_by_value.await;
|
||||||
|
let result2 = fut_by_ref.await;
|
||||||
|
|
||||||
|
assert_eq!(result1, result2);
|
||||||
|
result1
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(result, "comment");
|
||||||
|
assert_eq!(yields, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_across_async_boundaries() {
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
let language = get_language("c");
|
||||||
|
parser.set_language(&language).unwrap();
|
||||||
|
let tree = parser.parse("#", None).unwrap();
|
||||||
|
let mut cursor = tree.walk();
|
||||||
|
|
||||||
|
let ((), yields) = simple_async_executor(async {
|
||||||
|
cursor.goto_first_child();
|
||||||
|
|
||||||
|
// Test cursor usage across yield point
|
||||||
|
yield_once().await;
|
||||||
|
cursor.goto_first_child();
|
||||||
|
|
||||||
|
// Test cursor in async block
|
||||||
|
let cursor_ref = &mut cursor;
|
||||||
|
let fut = async {
|
||||||
|
yield_once().await;
|
||||||
|
cursor_ref.goto_first_child();
|
||||||
|
};
|
||||||
|
fut.await;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(yields, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_node_and_cursor_together() {
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
let language = get_language("javascript");
|
||||||
|
parser.set_language(&language).unwrap();
|
||||||
|
let tree = parser.parse("#", None).unwrap();
|
||||||
|
let root = tree.root_node();
|
||||||
|
let mut cursor = tree.walk();
|
||||||
|
|
||||||
|
let ((), yields) = simple_async_executor(async {
|
||||||
|
cursor.goto_first_child();
|
||||||
|
|
||||||
|
let fut = async {
|
||||||
|
yield_once().await;
|
||||||
|
let _ = root.to_sexp();
|
||||||
|
cursor.goto_first_child();
|
||||||
|
};
|
||||||
|
|
||||||
|
yield_once().await;
|
||||||
|
fut.await;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(yields, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_async_executor<F>(future: F) -> (F::Output, u32)
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
{
|
||||||
|
let waker = noop_waker();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
let mut yields = 0;
|
||||||
|
let mut future = Box::pin(future);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match future.as_mut().poll(&mut cx) {
|
||||||
|
Poll::Ready(result) => return (result, yields),
|
||||||
|
Poll::Pending => yields += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn yield_once() {
|
||||||
|
struct YieldOnce {
|
||||||
|
yielded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for YieldOnce {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
|
if self.yielded {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
self.yielded = true;
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
YieldOnce { yielded: false }.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn noop_waker() -> Waker {
|
||||||
|
const VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||||
|
// Cloning just returns a new no-op raw waker
|
||||||
|
|_| RAW,
|
||||||
|
// `wake` does nothing
|
||||||
|
|_| {},
|
||||||
|
// `wake_by_ref` does nothing
|
||||||
|
|_| {},
|
||||||
|
// Dropping does nothing as we don't allocate anything
|
||||||
|
|_| {},
|
||||||
|
);
|
||||||
|
const RAW: RawWaker = RawWaker::new(ptr::null(), &VTABLE);
|
||||||
|
unsafe { Waker::from_raw(RAW) }
|
||||||
|
}
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
pin::{pin, Pin},
|
|
||||||
ptr,
|
|
||||||
task::{self, Context, Poll, RawWaker, RawWakerVTable, Waker},
|
|
||||||
};
|
|
||||||
|
|
||||||
use tree_sitter::Parser;
|
|
||||||
|
|
||||||
use super::helpers::fixtures::get_language;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_node_in_fut() {
|
|
||||||
let (ret, pended) = tokio_like_spawn(async {
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
let language = get_language("bash");
|
|
||||||
parser.set_language(&language).unwrap();
|
|
||||||
|
|
||||||
let tree = parser.parse("#", None).unwrap();
|
|
||||||
|
|
||||||
let root = tree.root_node();
|
|
||||||
let root_ref = &root;
|
|
||||||
|
|
||||||
let fut_val_fn = || async {
|
|
||||||
yield_now().await;
|
|
||||||
root.child(0).unwrap().kind()
|
|
||||||
};
|
|
||||||
|
|
||||||
yield_now().await;
|
|
||||||
|
|
||||||
let fut_ref_fn = || async {
|
|
||||||
yield_now().await;
|
|
||||||
root_ref.child(0).unwrap().kind()
|
|
||||||
};
|
|
||||||
|
|
||||||
let f1 = fut_val_fn().await;
|
|
||||||
let f2 = fut_ref_fn().await;
|
|
||||||
assert_eq!(f1, f2);
|
|
||||||
|
|
||||||
let fut_val = async {
|
|
||||||
yield_now().await;
|
|
||||||
root.child(0).unwrap().kind()
|
|
||||||
};
|
|
||||||
|
|
||||||
let fut_ref = async {
|
|
||||||
yield_now().await;
|
|
||||||
root_ref.child(0).unwrap().kind()
|
|
||||||
};
|
|
||||||
|
|
||||||
let f1 = fut_val.await;
|
|
||||||
let f2 = fut_ref.await;
|
|
||||||
assert_eq!(f1, f2);
|
|
||||||
|
|
||||||
f1
|
|
||||||
})
|
|
||||||
.join();
|
|
||||||
assert_eq!(ret, "comment");
|
|
||||||
assert_eq!(pended, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_node_and_cursor_ref_in_fut() {
|
|
||||||
let ((), pended) = tokio_like_spawn(async {
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
let language = get_language("c");
|
|
||||||
parser.set_language(&language).unwrap();
|
|
||||||
|
|
||||||
let tree = parser.parse("#", None).unwrap();
|
|
||||||
|
|
||||||
let root = tree.root_node();
|
|
||||||
let root_ref = &root;
|
|
||||||
|
|
||||||
let mut cursor = tree.walk();
|
|
||||||
let cursor_ref = &mut cursor;
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
|
|
||||||
let fut_val = async {
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root.to_sexp();
|
|
||||||
};
|
|
||||||
|
|
||||||
yield_now().await;
|
|
||||||
|
|
||||||
let fut_ref = async {
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root_ref.to_sexp();
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
};
|
|
||||||
|
|
||||||
fut_val.await;
|
|
||||||
fut_ref.await;
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
})
|
|
||||||
.join();
|
|
||||||
assert_eq!(pended, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_node_and_cursor_ref_in_fut_with_fut_fabrics() {
|
|
||||||
let ((), pended) = tokio_like_spawn(async {
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
let language = get_language("javascript");
|
|
||||||
parser.set_language(&language).unwrap();
|
|
||||||
|
|
||||||
let tree = parser.parse("#", None).unwrap();
|
|
||||||
|
|
||||||
let root = tree.root_node();
|
|
||||||
let root_ref = &root;
|
|
||||||
|
|
||||||
let mut cursor = tree.walk();
|
|
||||||
let cursor_ref = &mut cursor;
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
|
|
||||||
let fut_val = || async {
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root.to_sexp();
|
|
||||||
};
|
|
||||||
|
|
||||||
yield_now().await;
|
|
||||||
|
|
||||||
let fut_ref = || async move {
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root_ref.to_sexp();
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
};
|
|
||||||
|
|
||||||
fut_val().await;
|
|
||||||
fut_val().await;
|
|
||||||
fut_ref().await;
|
|
||||||
})
|
|
||||||
.join();
|
|
||||||
assert_eq!(pended, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_node_and_cursor_ref_in_fut_with_inner_spawns() {
|
|
||||||
let (ret, pended) = tokio_like_spawn(async {
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
let language = get_language("rust");
|
|
||||||
parser.set_language(&language).unwrap();
|
|
||||||
|
|
||||||
let tree = parser.parse("#", None).unwrap();
|
|
||||||
|
|
||||||
let mut cursor = tree.walk();
|
|
||||||
let cursor_ref = &mut cursor;
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
|
|
||||||
let fut_val = || {
|
|
||||||
let tree = tree.clone();
|
|
||||||
async move {
|
|
||||||
let root = tree.root_node();
|
|
||||||
let mut cursor = tree.walk();
|
|
||||||
let cursor_ref = &mut cursor;
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root.to_sexp();
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
yield_now().await;
|
|
||||||
|
|
||||||
let fut_ref = || {
|
|
||||||
let tree = tree.clone();
|
|
||||||
async move {
|
|
||||||
let root = tree.root_node();
|
|
||||||
let root_ref = &root;
|
|
||||||
let mut cursor = tree.walk();
|
|
||||||
let cursor_ref = &mut cursor;
|
|
||||||
yield_now().await;
|
|
||||||
let _ = root_ref.to_sexp();
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let ((), p1) = tokio_like_spawn(fut_val()).await.unwrap();
|
|
||||||
let ((), p2) = tokio_like_spawn(fut_ref()).await.unwrap();
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
|
|
||||||
fut_val().await;
|
|
||||||
fut_val().await;
|
|
||||||
fut_ref().await;
|
|
||||||
|
|
||||||
cursor_ref.goto_first_child();
|
|
||||||
|
|
||||||
p1 + p2
|
|
||||||
})
|
|
||||||
.join();
|
|
||||||
assert_eq!(pended, 4);
|
|
||||||
assert_eq!(ret, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tokio_like_spawn<T>(future: T) -> JoinHandle<(T::Output, usize)>
|
|
||||||
where
|
|
||||||
T: Future + Send + 'static,
|
|
||||||
T::Output: Send + 'static,
|
|
||||||
{
|
|
||||||
// No runtime, just noop waker
|
|
||||||
|
|
||||||
let waker = noop_waker();
|
|
||||||
let mut cx = task::Context::from_waker(&waker);
|
|
||||||
|
|
||||||
let mut pending = 0;
|
|
||||||
let mut future = pin!(future);
|
|
||||||
let ret = loop {
|
|
||||||
match future.as_mut().poll(&mut cx) {
|
|
||||||
Poll::Pending => pending += 1,
|
|
||||||
Poll::Ready(r) => {
|
|
||||||
break r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
JoinHandle::new((ret, pending))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn yield_now() {
|
|
||||||
struct SimpleYieldNow {
|
|
||||||
yielded: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Future for SimpleYieldNow {
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
|
||||||
cx.waker().wake_by_ref();
|
|
||||||
if self.yielded {
|
|
||||||
return Poll::Ready(());
|
|
||||||
}
|
|
||||||
self.yielded = true;
|
|
||||||
Poll::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleYieldNow { yielded: false }.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn noop_waker() -> Waker {
|
|
||||||
const VTABLE: RawWakerVTable = RawWakerVTable::new(
|
|
||||||
// Cloning just returns a new no-op raw waker
|
|
||||||
|_| RAW,
|
|
||||||
// `wake` does nothing
|
|
||||||
|_| {},
|
|
||||||
// `wake_by_ref` does nothing
|
|
||||||
|_| {},
|
|
||||||
// Dropping does nothing as we don't allocate anything
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
const RAW: RawWaker = RawWaker::new(ptr::null(), &VTABLE);
|
|
||||||
unsafe { Waker::from_raw(RAW) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct JoinHandle<T> {
|
|
||||||
data: Option<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> JoinHandle<T> {
|
|
||||||
#[must_use]
|
|
||||||
const fn new(data: T) -> Self {
|
|
||||||
Self { data: Some(data) }
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn join(&mut self) -> T {
|
|
||||||
self.data.take().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Unpin> Future for JoinHandle<T> {
|
|
||||||
type Output = std::result::Result<T, ()>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let data = self.get_mut().data.take().unwrap();
|
|
||||||
Poll::Ready(Ok(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
LOG_GRAPH_ENABLED, START_SEED,
|
LOG_GRAPH_ENABLED, START_SEED,
|
||||||
},
|
},
|
||||||
parse::perform_edit,
|
parse::perform_edit,
|
||||||
test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields},
|
test::{parse_tests, strip_sexp_fields, DiffKey, TestDiff},
|
||||||
tests::{
|
tests::{
|
||||||
allocations,
|
allocations,
|
||||||
helpers::fixtures::{fixtures_dir, get_language, get_test_language, SCRATCH_BASE_DIR},
|
helpers::fixtures::{fixtures_dir, get_language, get_test_language, SCRATCH_BASE_DIR},
|
||||||
|
|
@ -209,8 +209,8 @@ pub fn test_language_corpus(
|
||||||
|
|
||||||
if actual_output != test.output {
|
if actual_output != test.output {
|
||||||
println!("Incorrect initial parse for {test_name}");
|
println!("Incorrect initial parse for {test_name}");
|
||||||
print_diff_key();
|
DiffKey::print();
|
||||||
print_diff(&actual_output, &test.output, true);
|
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||||
println!();
|
println!();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +297,8 @@ pub fn test_language_corpus(
|
||||||
|
|
||||||
if actual_output != test.output {
|
if actual_output != test.output {
|
||||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||||
print_diff_key();
|
DiffKey::print();
|
||||||
print_diff(&actual_output, &test.output, true);
|
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||||
println!();
|
println!();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -428,8 +428,8 @@ fn test_feature_corpus_files() {
|
||||||
if actual_output == test.output {
|
if actual_output == test.output {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
print_diff_key();
|
DiffKey::print();
|
||||||
print_diff(&actual_output, &test.output, true);
|
print!("{}", TestDiff::new(&actual_output, &test.output));
|
||||||
println!();
|
println!();
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ fn detect_language_by_first_line_regex() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_langauge_by_double_barrel_file_extension() {
|
fn detect_language_by_double_barrel_file_extension() {
|
||||||
let blade_dir = tree_sitter_dir(
|
let blade_dir = tree_sitter_dir(
|
||||||
r#"{
|
r#"{
|
||||||
"grammars": [
|
"grammars": [
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct Pattern {
|
||||||
named: bool,
|
named: bool,
|
||||||
field: Option<&'static str>,
|
field: Option<&'static str>,
|
||||||
capture: Option<String>,
|
capture: Option<String>,
|
||||||
children: Vec<Pattern>,
|
children: Vec<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -225,7 +225,7 @@ impl Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find every matching combination of child patterns and child nodes.
|
// Find every matching combination of child patterns and child nodes.
|
||||||
let mut finished_matches = Vec::<Match>::new();
|
let mut finished_matches = Vec::<Match<'_, 'tree>>::new();
|
||||||
if cursor.goto_first_child() {
|
if cursor.goto_first_child() {
|
||||||
let mut match_states = vec![(0, mat)];
|
let mut match_states = vec![(0, mat)];
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
|
|
@ -481,7 +481,7 @@ fn test_highlighting_cancellation() {
|
||||||
|
|
||||||
// The initial `highlight` call, which eagerly parses the outer document, should not fail.
|
// The initial `highlight` call, which eagerly parses the outer document, should not fail.
|
||||||
let mut highlighter = Highlighter::new();
|
let mut highlighter = Highlighter::new();
|
||||||
let events = highlighter
|
let mut events = highlighter
|
||||||
.highlight(
|
.highlight(
|
||||||
&HTML_HIGHLIGHT,
|
&HTML_HIGHLIGHT,
|
||||||
source.as_bytes(),
|
source.as_bytes(),
|
||||||
|
|
@ -492,14 +492,18 @@ fn test_highlighting_cancellation() {
|
||||||
|
|
||||||
// Iterating the scopes should not panic. It should return an error once the
|
// Iterating the scopes should not panic. It should return an error once the
|
||||||
// cancellation is detected.
|
// cancellation is detected.
|
||||||
for event in events {
|
let found_cancellation_error = events.any(|event| match event {
|
||||||
if let Err(e) = event {
|
Ok(_) => false,
|
||||||
assert_eq!(e, Error::Cancelled);
|
Err(Error::Cancelled) => true,
|
||||||
return;
|
Err(Error::InvalidLanguage | Error::Unknown) => {
|
||||||
|
unreachable!("Unexpected error type while iterating events")
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
panic!("Expected an error while iterating highlighter");
|
assert!(
|
||||||
|
found_cancellation_error,
|
||||||
|
"Expected a cancellation error while iterating events"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
// For some reasons `Command::spawn` doesn't work in CI env for many exotic arches.
|
|
||||||
#![cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
env::VarError,
|
|
||||||
process::{Command, Stdio},
|
|
||||||
};
|
|
||||||
|
|
||||||
use tree_sitter::Parser;
|
|
||||||
use tree_sitter_generate::load_grammar_file;
|
|
||||||
|
|
||||||
use super::generate_parser;
|
|
||||||
use crate::tests::helpers::fixtures::{fixtures_dir, get_test_language};
|
|
||||||
|
|
||||||
// The `sanitizing` cfg is required to don't run tests under specific sunitizer
|
|
||||||
// because they don't work well with subprocesses _(it's an assumption)_.
|
|
||||||
//
|
|
||||||
// Below are two alternative examples of how to disable tests for some arches
|
|
||||||
// if a way with excluding the whole mod from compilation wouldn't work well.
|
|
||||||
//
|
|
||||||
// XXX: Also may be it makes sense to keep such tests as ignored by default
|
|
||||||
// to omit surprises and enable them on CI by passing an extra option explicitly:
|
|
||||||
//
|
|
||||||
// > cargo test -- --include-ignored
|
|
||||||
//
|
|
||||||
// #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
|
|
||||||
// #[cfg_attr(not(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing))), ignore)]
|
|
||||||
//
|
|
||||||
#[test]
|
|
||||||
fn test_grammar_that_should_hang_and_not_segfault() {
|
|
||||||
let parent_sleep_millis = 1000;
|
|
||||||
let test_name = "test_grammar_that_should_hang_and_not_segfault";
|
|
||||||
let test_var = "CARGO_HANG_TEST";
|
|
||||||
|
|
||||||
eprintln!(" {test_name}");
|
|
||||||
|
|
||||||
let tests_exec_path = std::env::args()
|
|
||||||
.next()
|
|
||||||
.expect("Failed to get tests executable path");
|
|
||||||
|
|
||||||
match std::env::var(test_var) {
|
|
||||||
Ok(v) if v == test_name => {
|
|
||||||
eprintln!(" child process id {}", std::process::id());
|
|
||||||
hang_test();
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(VarError::NotPresent) => {
|
|
||||||
eprintln!(" parent process id {}", std::process::id());
|
|
||||||
let mut command = Command::new(tests_exec_path);
|
|
||||||
command.arg(test_name).env(test_var, test_name);
|
|
||||||
|
|
||||||
if std::env::args().any(|x| x == "--nocapture") {
|
|
||||||
command.arg("--nocapture");
|
|
||||||
} else {
|
|
||||||
command.stdout(Stdio::null()).stderr(Stdio::null());
|
|
||||||
}
|
|
||||||
|
|
||||||
match command.spawn() {
|
|
||||||
Ok(mut child) => {
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(parent_sleep_millis));
|
|
||||||
match child.try_wait() {
|
|
||||||
Ok(Some(status)) if status.success() => {
|
|
||||||
panic!("Child didn't hang and exited successfully")
|
|
||||||
}
|
|
||||||
Ok(Some(status)) => panic!(
|
|
||||||
"Child didn't hang and exited with status code: {:?}",
|
|
||||||
status.code()
|
|
||||||
),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
if let Err(e) = child.kill() {
|
|
||||||
eprintln!(
|
|
||||||
"Failed to kill hang test's process id: {}, error: {e}",
|
|
||||||
child.id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => panic!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => panic!("Env var error: {e}"),
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hang_test() {
|
|
||||||
let test_grammar_dir = fixtures_dir()
|
|
||||||
.join("test_grammars")
|
|
||||||
.join("get_col_should_hang_not_crash");
|
|
||||||
|
|
||||||
let grammar_json = load_grammar_file(&test_grammar_dir.join("grammar.js"), None).unwrap();
|
|
||||||
let (parser_name, parser_code) = generate_parser(grammar_json.as_str()).unwrap();
|
|
||||||
|
|
||||||
let language = get_test_language(&parser_name, &parser_code, Some(test_grammar_dir.as_path()));
|
|
||||||
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
parser.set_language(&language).unwrap();
|
|
||||||
|
|
||||||
let code_that_should_hang = "\nHello";
|
|
||||||
|
|
||||||
parser.parse(code_that_should_hang, None).unwrap();
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
use std::{
|
use std::{
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
sync::{
|
||||||
thread, time,
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
mpsc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
time::{self, Duration},
|
||||||
};
|
};
|
||||||
|
|
||||||
use tree_sitter::{
|
use tree_sitter::{
|
||||||
Decode, IncludedRangesError, InputEdit, LogType, ParseOptions, ParseState, Parser, Point, Range,
|
Decode, IncludedRangesError, InputEdit, LogType, ParseOptions, ParseState, Parser, Point, Range,
|
||||||
};
|
};
|
||||||
|
use tree_sitter_generate::load_grammar_file;
|
||||||
use tree_sitter_proc_macro::retry;
|
use tree_sitter_proc_macro::retry;
|
||||||
|
|
||||||
use super::helpers::{
|
use super::helpers::{
|
||||||
|
|
@ -17,7 +22,11 @@ use super::helpers::{
|
||||||
use crate::{
|
use crate::{
|
||||||
fuzz::edits::Edit,
|
fuzz::edits::Edit,
|
||||||
parse::perform_edit,
|
parse::perform_edit,
|
||||||
tests::{generate_parser, helpers::fixtures::get_test_fixture_language, invert_edit},
|
tests::{
|
||||||
|
generate_parser,
|
||||||
|
helpers::fixtures::{fixtures_dir, get_test_fixture_language},
|
||||||
|
invert_edit,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -88,7 +97,6 @@ fn test_parsing_with_logging() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
|
||||||
fn test_parsing_with_debug_graph_enabled() {
|
fn test_parsing_with_debug_graph_enabled() {
|
||||||
use std::io::{BufRead, BufReader, Seek};
|
use std::io::{BufRead, BufReader, Seek};
|
||||||
|
|
||||||
|
|
@ -2126,3 +2134,46 @@ fn test_parse_options_reborrow() {
|
||||||
|
|
||||||
assert!(parse_count.load(Ordering::SeqCst) > 0);
|
assert!(parse_count.load(Ordering::SeqCst) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_grammar_that_should_hang_and_not_segfault() {
|
||||||
|
fn hang_test() {
|
||||||
|
let test_grammar_dir = fixtures_dir()
|
||||||
|
.join("test_grammars")
|
||||||
|
.join("get_col_should_hang_not_crash");
|
||||||
|
|
||||||
|
let grammar_json = load_grammar_file(&test_grammar_dir.join("grammar.js"), None)
|
||||||
|
.expect("Failed to load grammar file");
|
||||||
|
|
||||||
|
let (parser_name, parser_code) =
|
||||||
|
generate_parser(grammar_json.as_str()).expect("Failed to generate parser");
|
||||||
|
|
||||||
|
let language =
|
||||||
|
get_test_language(&parser_name, &parser_code, Some(test_grammar_dir.as_path()));
|
||||||
|
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
parser
|
||||||
|
.set_language(&language)
|
||||||
|
.expect("Failed to set parser language");
|
||||||
|
|
||||||
|
let code_that_should_hang = "\nHello";
|
||||||
|
|
||||||
|
parser
|
||||||
|
.parse(code_that_should_hang, None)
|
||||||
|
.expect("Parse operation completed unexpectedly");
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = Duration::from_millis(500);
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || tx.send(std::panic::catch_unwind(hang_test)));
|
||||||
|
|
||||||
|
match rx.recv_timeout(timeout) {
|
||||||
|
Ok(Ok(())) => panic!("The test completed rather than hanging"),
|
||||||
|
Ok(Err(panic_info)) => panic!("The test panicked unexpectedly: {panic_info:?}"),
|
||||||
|
Err(mpsc::RecvTimeoutError::Timeout) => {} // Expected
|
||||||
|
Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||||
|
panic!("The test thread disconnected unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use tree_sitter::{
|
||||||
QueryCursorOptions, QueryError, QueryErrorKind, QueryPredicate, QueryPredicateArg,
|
QueryCursorOptions, QueryError, QueryErrorKind, QueryPredicate, QueryPredicateArg,
|
||||||
QueryProperty, Range,
|
QueryProperty, Range,
|
||||||
};
|
};
|
||||||
|
use tree_sitter_generate::load_grammar_file;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
use super::helpers::{
|
use super::helpers::{
|
||||||
|
|
@ -237,6 +238,20 @@ fn test_query_errors_on_invalid_syntax() {
|
||||||
]
|
]
|
||||||
.join("\n")
|
.join("\n")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Query::new(&language, "(statement / export_statement)").unwrap_err(),
|
||||||
|
QueryError {
|
||||||
|
row: 0,
|
||||||
|
offset: 11,
|
||||||
|
column: 11,
|
||||||
|
kind: QueryErrorKind::Syntax,
|
||||||
|
message: [
|
||||||
|
"(statement / export_statement)", //
|
||||||
|
" ^"
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,11 +430,11 @@ fn test_query_errors_on_impossible_patterns() {
|
||||||
Err(QueryError {
|
Err(QueryError {
|
||||||
kind: QueryErrorKind::Structure,
|
kind: QueryErrorKind::Structure,
|
||||||
row: 0,
|
row: 0,
|
||||||
offset: 51,
|
offset: 37,
|
||||||
column: 51,
|
column: 37,
|
||||||
message: [
|
message: [
|
||||||
"(binary_expression left: (expression (identifier)) left: (expression (identifier)))",
|
"(binary_expression left: (expression (identifier)) left: (expression (identifier)))",
|
||||||
" ^",
|
" ^",
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
})
|
})
|
||||||
|
|
@ -2654,6 +2669,64 @@ fn test_query_matches_within_range_of_long_repetition() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_matches_contained_within_range() {
|
||||||
|
allocations::record(|| {
|
||||||
|
let language = get_language("json");
|
||||||
|
let query = Query::new(
|
||||||
|
&language,
|
||||||
|
r#"
|
||||||
|
("[" @l_bracket "]" @r_bracket)
|
||||||
|
("{" @l_brace "}" @r_brace)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let source = r#"
|
||||||
|
[
|
||||||
|
{"key1": "value1"},
|
||||||
|
{"key2": "value2"},
|
||||||
|
{"key3": "value3"},
|
||||||
|
{"key4": "value4"},
|
||||||
|
{"key5": "value5"},
|
||||||
|
{"key6": "value6"},
|
||||||
|
{"key7": "value7"},
|
||||||
|
{"key8": "value8"},
|
||||||
|
{"key9": "value9"},
|
||||||
|
{"key10": "value10"},
|
||||||
|
{"key11": "value11"},
|
||||||
|
{"key12": "value12"},
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
parser.set_language(&language).unwrap();
|
||||||
|
let tree = parser.parse(&source, None).unwrap();
|
||||||
|
|
||||||
|
let expected_matches = [
|
||||||
|
(1, vec![("l_brace", "{"), ("r_brace", "}")]),
|
||||||
|
(1, vec![("l_brace", "{"), ("r_brace", "}")]),
|
||||||
|
];
|
||||||
|
{
|
||||||
|
let mut cursor = QueryCursor::new();
|
||||||
|
let matches = cursor
|
||||||
|
.set_containing_point_range(Point::new(5, 0)..Point::new(7, 0))
|
||||||
|
.matches(&query, tree.root_node(), source.as_bytes());
|
||||||
|
assert_eq!(collect_matches(matches, &query, &source), &expected_matches);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut cursor = QueryCursor::new();
|
||||||
|
let matches = cursor.set_containing_byte_range(78..120).matches(
|
||||||
|
&query,
|
||||||
|
tree.root_node(),
|
||||||
|
source.as_bytes(),
|
||||||
|
);
|
||||||
|
assert_eq!(collect_matches(matches, &query, &source), &expected_matches);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_query_matches_different_queries_same_cursor() {
|
fn test_query_matches_different_queries_same_cursor() {
|
||||||
allocations::record(|| {
|
allocations::record(|| {
|
||||||
|
|
@ -4186,12 +4259,9 @@ fn test_query_random() {
|
||||||
let pattern = pattern_ast.to_string();
|
let pattern = pattern_ast.to_string();
|
||||||
let expected_matches = pattern_ast.matches_in_tree(&test_tree);
|
let expected_matches = pattern_ast.matches_in_tree(&test_tree);
|
||||||
|
|
||||||
let query = match Query::new(&language, &pattern) {
|
let query = Query::new(&language, &pattern).unwrap_or_else(|e| {
|
||||||
Ok(query) => query,
|
panic!("failed to build query for pattern {pattern}. seed: {seed}\n{e}")
|
||||||
Err(e) => {
|
});
|
||||||
panic!("failed to build query for pattern {pattern} - {e}. seed: {seed}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut actual_matches = Vec::new();
|
let mut actual_matches = Vec::new();
|
||||||
let mut match_iter = cursor.matches(
|
let mut match_iter = cursor.matches(
|
||||||
&query,
|
&query,
|
||||||
|
|
@ -5020,6 +5090,26 @@ fn test_query_quantified_captures() {
|
||||||
("comment.documentation", "// quuz"),
|
("comment.documentation", "// quuz"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Row {
|
||||||
|
description: "multiple quantifiers should not hang query parsing",
|
||||||
|
language: get_language("c"),
|
||||||
|
code: indoc! {"
|
||||||
|
// foo
|
||||||
|
// bar
|
||||||
|
// baz
|
||||||
|
"},
|
||||||
|
pattern: r"
|
||||||
|
((comment) ?+ @comment)
|
||||||
|
",
|
||||||
|
// This should be identical to the `*` quantifier.
|
||||||
|
captures: &[
|
||||||
|
("comment", "// foo"),
|
||||||
|
("comment", "// foo"),
|
||||||
|
("comment", "// foo"),
|
||||||
|
("comment", "// bar"),
|
||||||
|
("comment", "// baz"),
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
allocations::record(|| {
|
allocations::record(|| {
|
||||||
|
|
@ -5765,3 +5855,109 @@ fn test_query_allows_error_nodes_with_children() {
|
||||||
assert_eq!(matches, &[(0, vec![("error", ".bar")])]);
|
assert_eq!(matches, &[(0, vec![("error", ".bar")])]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_assertion_on_unreachable_node_with_child() {
|
||||||
|
// The `await_binding` rule is unreachable because it has a lower precedence than
|
||||||
|
// `identifier`, so we'll always reduce to an expression of type `identifier`
|
||||||
|
// instead whenever we see the token `await` followed by an identifier.
|
||||||
|
//
|
||||||
|
// A query that tries to capture the `await` token in the `await_binding` rule
|
||||||
|
// should not cause an assertion failure during query analysis.
|
||||||
|
let grammar = r#"
|
||||||
|
export default grammar({
|
||||||
|
name: "query_assertion_crash",
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
source_file: $ => repeat($.expression),
|
||||||
|
|
||||||
|
expression: $ => choice(
|
||||||
|
$.await_binding,
|
||||||
|
$.await_expr,
|
||||||
|
$.equal_expr,
|
||||||
|
prec(3, $.identifier),
|
||||||
|
),
|
||||||
|
|
||||||
|
await_binding: $ => prec(1, seq('await', $.identifier, '=', $.expression)),
|
||||||
|
|
||||||
|
await_expr: $ => prec(1, seq('await', $.expression)),
|
||||||
|
|
||||||
|
equal_expr: $ => prec.right(2, seq($.expression, '=', $.expression)),
|
||||||
|
|
||||||
|
identifier: _ => /[a-z]+/,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let file = tempfile::NamedTempFile::with_suffix(".js").unwrap();
|
||||||
|
std::fs::write(file.path(), grammar).unwrap();
|
||||||
|
|
||||||
|
let grammar_json = load_grammar_file(file.path(), None).unwrap();
|
||||||
|
|
||||||
|
let (parser_name, parser_code) = generate_parser(&grammar_json).unwrap();
|
||||||
|
|
||||||
|
let language = get_test_language(&parser_name, &parser_code, None);
|
||||||
|
|
||||||
|
let query_result = Query::new(&language, r#"(await_binding "await")"#);
|
||||||
|
|
||||||
|
assert!(query_result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
query_result.unwrap_err(),
|
||||||
|
QueryError {
|
||||||
|
kind: QueryErrorKind::Structure,
|
||||||
|
row: 0,
|
||||||
|
offset: 0,
|
||||||
|
column: 0,
|
||||||
|
message: ["(await_binding \"await\")", "^"].join("\n"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_supertype_with_anonymous_node() {
|
||||||
|
let grammar = r#"
|
||||||
|
export default grammar({
|
||||||
|
name: "supertype_anonymous_test",
|
||||||
|
|
||||||
|
extras: $ => [/\s/, $.comment],
|
||||||
|
|
||||||
|
supertypes: $ => [$.expression],
|
||||||
|
|
||||||
|
word: $ => $.identifier,
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
source_file: $ => repeat($.expression),
|
||||||
|
|
||||||
|
expression: $ => choice(
|
||||||
|
$.function_call,
|
||||||
|
'()' // an empty tuple, which should be queryable with the supertype syntax
|
||||||
|
),
|
||||||
|
|
||||||
|
function_call: $ => seq($.identifier, '()'),
|
||||||
|
|
||||||
|
identifier: _ => /[a-zA-Z_][a-zA-Z0-9_]*/,
|
||||||
|
|
||||||
|
comment: _ => token(seq('//', /.*/)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let file = tempfile::NamedTempFile::with_suffix(".js").unwrap();
|
||||||
|
std::fs::write(file.path(), grammar).unwrap();
|
||||||
|
|
||||||
|
let grammar_json = load_grammar_file(file.path(), None).unwrap();
|
||||||
|
|
||||||
|
let (parser_name, parser_code) = generate_parser(&grammar_json).unwrap();
|
||||||
|
|
||||||
|
let language = get_test_language(&parser_name, &parser_code, None);
|
||||||
|
|
||||||
|
let query_result = Query::new(&language, r#"(expression/"()") @tuple"#);
|
||||||
|
|
||||||
|
assert!(query_result.is_ok());
|
||||||
|
|
||||||
|
let query = query_result.unwrap();
|
||||||
|
|
||||||
|
let source = "foo()\n()";
|
||||||
|
|
||||||
|
assert_query_matches(&language, &query, source, &[(0, vec![("tuple", "()")])]);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
ffi::{CStr, CString},
|
ffi::{CStr, CString},
|
||||||
fs, ptr, slice, str,
|
fs, ptr, slice, str,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use tree_sitter::Point;
|
use tree_sitter::Point;
|
||||||
|
|
@ -262,34 +263,34 @@ fn test_tags_ruby() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tags_cancellation() {
|
fn test_tags_cancellation() {
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
allocations::record(|| {
|
allocations::record(|| {
|
||||||
// Large javascript document
|
// Large javascript document
|
||||||
let source = (0..500)
|
let source = "/* hi */ class A { /* ok */ b() {} }\n".repeat(500);
|
||||||
.map(|_| "/* hi */ class A { /* ok */ b() {} }\n")
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
let cancellation_flag = AtomicUsize::new(0);
|
let cancellation_flag = AtomicUsize::new(0);
|
||||||
let language = get_language("javascript");
|
let language = get_language("javascript");
|
||||||
let tags_config = TagsConfiguration::new(language, JS_TAG_QUERY, "").unwrap();
|
let tags_config = TagsConfiguration::new(language, JS_TAG_QUERY, "").unwrap();
|
||||||
|
|
||||||
let mut tag_context = TagsContext::new();
|
let mut tag_context = TagsContext::new();
|
||||||
let tags = tag_context
|
let tags = tag_context
|
||||||
.generate_tags(&tags_config, source.as_bytes(), Some(&cancellation_flag))
|
.generate_tags(&tags_config, source.as_bytes(), Some(&cancellation_flag))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (i, tag) in tags.0.enumerate() {
|
let found_cancellation_error = tags.0.enumerate().any(|(i, tag)| {
|
||||||
if i == 150 {
|
if i == 150 {
|
||||||
cancellation_flag.store(1, Ordering::SeqCst);
|
cancellation_flag.store(1, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
if let Err(e) = tag {
|
match tag {
|
||||||
assert_eq!(e, Error::Cancelled);
|
Ok(_) => false,
|
||||||
return;
|
Err(Error::Cancelled) => true,
|
||||||
|
Err(e) => {
|
||||||
|
unreachable!("Unexpected error type while iterating tags: {e}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
panic!("Expected to halt tagging with an error");
|
assert!(
|
||||||
|
found_cancellation_error,
|
||||||
|
"Expected to halt tagging with a cancellation error"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{fs, path::PathBuf, process::Command};
|
use std::{fs, path::PathBuf, process::Command};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
@ -22,6 +21,36 @@ pub struct Version {
|
||||||
pub bump: Option<BumpLevel>,
|
pub bump: Option<BumpLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum VersionError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Failed to update one or more files:\n\n{0}")]
|
||||||
|
Update(UpdateErrors),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub struct UpdateErrors(Vec<UpdateError>);
|
||||||
|
|
||||||
|
impl std::fmt::Display for UpdateErrors {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for error in &self.0 {
|
||||||
|
writeln!(f, "{error}\n")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum UpdateError {
|
||||||
|
#[error("Failed to update {1}:\n{0}")]
|
||||||
|
Io(std::io::Error, PathBuf),
|
||||||
|
#[error("Failed to run `{0}`:\n{1}")]
|
||||||
|
Command(&'static str, String),
|
||||||
|
}
|
||||||
|
|
||||||
impl Version {
|
impl Version {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
|
|
@ -36,7 +65,7 @@ impl Version {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(mut self) -> Result<()> {
|
pub fn run(mut self) -> Result<(), VersionError> {
|
||||||
let tree_sitter_json = self.current_dir.join("tree-sitter.json");
|
let tree_sitter_json = self.current_dir.join("tree-sitter.json");
|
||||||
|
|
||||||
let tree_sitter_json =
|
let tree_sitter_json =
|
||||||
|
|
@ -84,98 +113,101 @@ impl Version {
|
||||||
|
|
||||||
let is_multigrammar = tree_sitter_json.grammars.len() > 1;
|
let is_multigrammar = tree_sitter_json.grammars.len() > 1;
|
||||||
|
|
||||||
self.update_treesitter_json().with_context(|| {
|
let mut errors = Vec::new();
|
||||||
format!(
|
|
||||||
"Failed to update tree-sitter.json at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.update_cargo_toml().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to update Cargo.toml at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.update_package_json().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to update package.json at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.update_makefile(is_multigrammar).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to update Makefile at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.update_cmakelists_txt().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to update CMakeLists.txt at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
self.update_pyproject_toml().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to update pyproject.toml at {}",
|
|
||||||
self.current_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
// Helper to push errors into the errors vector, returns true if an error was pushed
|
||||||
|
let mut push_err = |result: Result<(), UpdateError>| -> bool {
|
||||||
|
if let Err(e) = result {
|
||||||
|
errors.push(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
push_err(self.update_treesitter_json());
|
||||||
|
|
||||||
|
// Only update Cargo.lock if Cargo.toml was updated
|
||||||
|
push_err(self.update_cargo_toml()).then(|| push_err(self.update_cargo_lock()));
|
||||||
|
|
||||||
|
// Only update package-lock.json if package.json was updated
|
||||||
|
push_err(self.update_package_json()).then(|| push_err(self.update_package_lock_json()));
|
||||||
|
|
||||||
|
push_err(self.update_makefile(is_multigrammar));
|
||||||
|
push_err(self.update_cmakelists_txt());
|
||||||
|
push_err(self.update_pyproject_toml());
|
||||||
|
push_err(self.update_zig_zon());
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(VersionError::Update(UpdateErrors(errors)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_treesitter_json(&self) -> Result<()> {
|
fn update_file_with<F>(&self, path: &PathBuf, update_fn: F) -> Result<(), UpdateError>
|
||||||
let tree_sitter_json = &fs::read_to_string(self.current_dir.join("tree-sitter.json"))?;
|
where
|
||||||
|
F: Fn(&str) -> String,
|
||||||
let tree_sitter_json = tree_sitter_json
|
{
|
||||||
.lines()
|
let content = fs::read_to_string(path).map_err(|e| UpdateError::Io(e, path.clone()))?;
|
||||||
.map(|line| {
|
let updated_content = update_fn(&content);
|
||||||
if line.contains("\"version\":") {
|
fs::write(path, updated_content).map_err(|e| UpdateError::Io(e, path.clone()))
|
||||||
let prefix_index = line.find("\"version\":").unwrap() + "\"version\":".len();
|
|
||||||
let start_quote = line[prefix_index..].find('"').unwrap() + prefix_index + 1;
|
|
||||||
let end_quote = line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
&line[..start_quote],
|
|
||||||
self.version.as_ref().unwrap(),
|
|
||||||
&line[end_quote..]
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
line.to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
+ "\n";
|
|
||||||
|
|
||||||
fs::write(self.current_dir.join("tree-sitter.json"), tree_sitter_json)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cargo_toml(&self) -> Result<()> {
|
fn update_treesitter_json(&self) -> Result<(), UpdateError> {
|
||||||
if !self.current_dir.join("Cargo.toml").exists() {
|
let json_path = self.current_dir.join("tree-sitter.json");
|
||||||
|
self.update_file_with(&json_path, |content| {
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.contains("\"version\":") {
|
||||||
|
let prefix_index =
|
||||||
|
line.find("\"version\":").unwrap() + "\"version\":".len();
|
||||||
|
let start_quote =
|
||||||
|
line[prefix_index..].find('"').unwrap() + prefix_index + 1;
|
||||||
|
let end_quote =
|
||||||
|
line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
&line[..start_quote],
|
||||||
|
self.version.as_ref().unwrap(),
|
||||||
|
&line[end_quote..]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cargo_toml(&self) -> Result<(), UpdateError> {
|
||||||
|
let cargo_toml_path = self.current_dir.join("Cargo.toml");
|
||||||
|
if !cargo_toml_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let cargo_toml = fs::read_to_string(self.current_dir.join("Cargo.toml"))?;
|
self.update_file_with(&cargo_toml_path, |content| {
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.starts_with("version =") {
|
||||||
|
format!("version = \"{}\"", self.version.as_ref().unwrap())
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
|
})?;
|
||||||
|
|
||||||
let cargo_toml = cargo_toml
|
Ok(())
|
||||||
.lines()
|
}
|
||||||
.map(|line| {
|
|
||||||
if line.starts_with("version =") {
|
|
||||||
format!("version = \"{}\"", self.version.as_ref().unwrap())
|
|
||||||
} else {
|
|
||||||
line.to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
+ "\n";
|
|
||||||
|
|
||||||
fs::write(self.current_dir.join("Cargo.toml"), cargo_toml)?;
|
|
||||||
|
|
||||||
|
fn update_cargo_lock(&self) -> Result<(), UpdateError> {
|
||||||
if self.current_dir.join("Cargo.lock").exists() {
|
if self.current_dir.join("Cargo.lock").exists() {
|
||||||
let Ok(cmd) = Command::new("cargo")
|
let Ok(cmd) = Command::new("cargo")
|
||||||
.arg("generate-lockfile")
|
.arg("generate-lockfile")
|
||||||
|
|
@ -188,8 +220,9 @@ impl Version {
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
||||||
return Err(anyhow!(
|
return Err(UpdateError::Command(
|
||||||
"Failed to run `cargo generate-lockfile`:\n{stderr}"
|
"cargo generate-lockfile",
|
||||||
|
stderr.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,37 +230,43 @@ impl Version {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_package_json(&self) -> Result<()> {
|
fn update_package_json(&self) -> Result<(), UpdateError> {
|
||||||
if !self.current_dir.join("package.json").exists() {
|
let package_json_path = self.current_dir.join("package.json");
|
||||||
|
if !package_json_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let package_json = &fs::read_to_string(self.current_dir.join("package.json"))?;
|
self.update_file_with(&package_json_path, |content| {
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.contains("\"version\":") {
|
||||||
|
let prefix_index =
|
||||||
|
line.find("\"version\":").unwrap() + "\"version\":".len();
|
||||||
|
let start_quote =
|
||||||
|
line[prefix_index..].find('"').unwrap() + prefix_index + 1;
|
||||||
|
let end_quote =
|
||||||
|
line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
|
||||||
|
|
||||||
let package_json = package_json
|
format!(
|
||||||
.lines()
|
"{}{}{}",
|
||||||
.map(|line| {
|
&line[..start_quote],
|
||||||
if line.contains("\"version\":") {
|
self.version.as_ref().unwrap(),
|
||||||
let prefix_index = line.find("\"version\":").unwrap() + "\"version\":".len();
|
&line[end_quote..]
|
||||||
let start_quote = line[prefix_index..].find('"').unwrap() + prefix_index + 1;
|
)
|
||||||
let end_quote = line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
|
})?;
|
||||||
|
|
||||||
format!(
|
Ok(())
|
||||||
"{}{}{}",
|
}
|
||||||
&line[..start_quote],
|
|
||||||
self.version.as_ref().unwrap(),
|
|
||||||
&line[end_quote..]
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
line.to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
+ "\n";
|
|
||||||
|
|
||||||
fs::write(self.current_dir.join("package.json"), package_json)?;
|
|
||||||
|
|
||||||
|
fn update_package_lock_json(&self) -> Result<(), UpdateError> {
|
||||||
if self.current_dir.join("package-lock.json").exists() {
|
if self.current_dir.join("package-lock.json").exists() {
|
||||||
let Ok(cmd) = Command::new("npm")
|
let Ok(cmd) = Command::new("npm")
|
||||||
.arg("install")
|
.arg("install")
|
||||||
|
|
@ -240,82 +279,117 @@ impl Version {
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
||||||
return Err(anyhow!("Failed to run `npm install`:\n{stderr}"));
|
return Err(UpdateError::Command("npm install", stderr.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_makefile(&self, is_multigrammar: bool) -> Result<()> {
|
fn update_makefile(&self, is_multigrammar: bool) -> Result<(), UpdateError> {
|
||||||
let makefile = if is_multigrammar {
|
let makefile_path = if is_multigrammar {
|
||||||
if !self.current_dir.join("common").join("common.mak").exists() {
|
self.current_dir.join("common").join("common.mak")
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::read_to_string(self.current_dir.join("Makefile"))?
|
|
||||||
} else {
|
} else {
|
||||||
if !self.current_dir.join("Makefile").exists() {
|
self.current_dir.join("Makefile")
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::read_to_string(self.current_dir.join("Makefile"))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let makefile = makefile
|
self.update_file_with(&makefile_path, |content| {
|
||||||
.lines()
|
content
|
||||||
.map(|line| {
|
.lines()
|
||||||
if line.starts_with("VERSION") {
|
.map(|line| {
|
||||||
format!("VERSION := {}", self.version.as_ref().unwrap())
|
if line.starts_with("VERSION") {
|
||||||
} else {
|
format!("VERSION := {}", self.version.as_ref().unwrap())
|
||||||
line.to_string()
|
} else {
|
||||||
}
|
line.to_string()
|
||||||
})
|
}
|
||||||
.collect::<Vec<_>>()
|
})
|
||||||
.join("\n")
|
.collect::<Vec<_>>()
|
||||||
+ "\n";
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
fs::write(self.current_dir.join("Makefile"), makefile)?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cmakelists_txt(&self) -> Result<()> {
|
fn update_cmakelists_txt(&self) -> Result<(), UpdateError> {
|
||||||
if !self.current_dir.join("CMakeLists.txt").exists() {
|
let cmake_lists_path = self.current_dir.join("CMakeLists.txt");
|
||||||
|
if !cmake_lists_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmake = fs::read_to_string(self.current_dir.join("CMakeLists.txt"))?;
|
self.update_file_with(&cmake_lists_path, |content| {
|
||||||
|
let re = Regex::new(r#"(\s*VERSION\s+)"[0-9]+\.[0-9]+\.[0-9]+""#)
|
||||||
let re = Regex::new(r#"(\s*VERSION\s+)"[0-9]+\.[0-9]+\.[0-9]+""#)?;
|
.expect("Failed to compile regex");
|
||||||
let cmake = re.replace(&cmake, format!(r#"$1"{}""#, self.version.as_ref().unwrap()));
|
re.replace(
|
||||||
|
content,
|
||||||
fs::write(self.current_dir.join("CMakeLists.txt"), cmake.as_bytes())?;
|
format!(r#"$1"{}""#, self.version.as_ref().unwrap()),
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_pyproject_toml(&self) -> Result<()> {
|
fn update_pyproject_toml(&self) -> Result<(), UpdateError> {
|
||||||
if !self.current_dir.join("pyproject.toml").exists() {
|
let pyproject_toml_path = self.current_dir.join("pyproject.toml");
|
||||||
|
if !pyproject_toml_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let pyproject_toml = fs::read_to_string(self.current_dir.join("pyproject.toml"))?;
|
self.update_file_with(&pyproject_toml_path, |content| {
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.starts_with("version =") {
|
||||||
|
format!("version = \"{}\"", self.version.as_ref().unwrap())
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
|
})?;
|
||||||
|
|
||||||
let pyproject_toml = pyproject_toml
|
Ok(())
|
||||||
.lines()
|
}
|
||||||
.map(|line| {
|
|
||||||
if line.starts_with("version =") {
|
|
||||||
format!("version = \"{}\"", self.version.as_ref().unwrap())
|
|
||||||
} else {
|
|
||||||
line.to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
+ "\n";
|
|
||||||
|
|
||||||
fs::write(self.current_dir.join("pyproject.toml"), pyproject_toml)?;
|
fn update_zig_zon(&self) -> Result<(), UpdateError> {
|
||||||
|
let zig_zon_path = self.current_dir.join("build.zig.zon");
|
||||||
|
if !zig_zon_path.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_file_with(&zig_zon_path, |content| {
|
||||||
|
let zig_version_prefix = ".version =";
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line
|
||||||
|
.trim_start_matches(|c: char| c.is_ascii_whitespace())
|
||||||
|
.starts_with(zig_version_prefix)
|
||||||
|
{
|
||||||
|
let prefix_index =
|
||||||
|
line.find(zig_version_prefix).unwrap() + zig_version_prefix.len();
|
||||||
|
let start_quote =
|
||||||
|
line[prefix_index..].find('"').unwrap() + prefix_index + 1;
|
||||||
|
let end_quote =
|
||||||
|
line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
&line[..start_quote],
|
||||||
|
self.version.as_ref().unwrap(),
|
||||||
|
&line[end_quote..]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
+ "\n"
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ path = "src/tree_sitter_config.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
|
||||||
etcetera.workspace = true
|
etcetera.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,54 @@
|
||||||
#![cfg_attr(not(any(test, doctest)), doc = include_str!("../README.md"))]
|
#![cfg_attr(not(any(test, doctest)), doc = include_str!("../README.md"))]
|
||||||
|
|
||||||
use std::{env, fs, path::PathBuf};
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use etcetera::BaseStrategy as _;
|
use etcetera::BaseStrategy as _;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type ConfigResult<T> = Result<T, ConfigError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("Bad JSON config {0} -- {1}")]
|
||||||
|
ConfigRead(String, serde_json::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
HomeDir(#[from] etcetera::HomeDirError),
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(IoError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Serialization(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub struct IoError {
|
||||||
|
pub error: std::io::Error,
|
||||||
|
pub path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoError {
|
||||||
|
fn new(error: std::io::Error, path: Option<&Path>) -> Self {
|
||||||
|
Self {
|
||||||
|
error,
|
||||||
|
path: path.map(|p| p.to_string_lossy().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for IoError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.error)?;
|
||||||
|
if let Some(ref path) = self.path {
|
||||||
|
write!(f, " ({path})")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds the contents of tree-sitter's configuration file.
|
/// Holds the contents of tree-sitter's configuration file.
|
||||||
///
|
///
|
||||||
|
|
@ -23,7 +65,7 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn find_config_file() -> Result<Option<PathBuf>> {
|
pub fn find_config_file() -> ConfigResult<Option<PathBuf>> {
|
||||||
if let Ok(path) = env::var("TREE_SITTER_DIR") {
|
if let Ok(path) = env::var("TREE_SITTER_DIR") {
|
||||||
let mut path = PathBuf::from(path);
|
let mut path = PathBuf::from(path);
|
||||||
path.push("config.json");
|
path.push("config.json");
|
||||||
|
|
@ -46,8 +88,12 @@ impl Config {
|
||||||
.join("tree-sitter")
|
.join("tree-sitter")
|
||||||
.join("config.json");
|
.join("config.json");
|
||||||
if legacy_apple_path.is_file() {
|
if legacy_apple_path.is_file() {
|
||||||
fs::create_dir_all(xdg_path.parent().unwrap())?;
|
let xdg_dir = xdg_path.parent().unwrap();
|
||||||
fs::rename(&legacy_apple_path, &xdg_path)?;
|
fs::create_dir_all(xdg_dir)
|
||||||
|
.map_err(|e| ConfigError::IO(IoError::new(e, Some(xdg_dir))))?;
|
||||||
|
fs::rename(&legacy_apple_path, &xdg_path).map_err(|e| {
|
||||||
|
ConfigError::IO(IoError::new(e, Some(legacy_apple_path.as_path())))
|
||||||
|
})?;
|
||||||
warn!(
|
warn!(
|
||||||
"Your config.json file has been automatically migrated from \"{}\" to \"{}\"",
|
"Your config.json file has been automatically migrated from \"{}\" to \"{}\"",
|
||||||
legacy_apple_path.display(),
|
legacy_apple_path.display(),
|
||||||
|
|
@ -67,7 +113,7 @@ impl Config {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xdg_config_file() -> Result<PathBuf> {
|
fn xdg_config_file() -> ConfigResult<PathBuf> {
|
||||||
let xdg_path = etcetera::choose_base_strategy()?
|
let xdg_path = etcetera::choose_base_strategy()?
|
||||||
.config_dir()
|
.config_dir()
|
||||||
.join("tree-sitter")
|
.join("tree-sitter")
|
||||||
|
|
@ -84,7 +130,7 @@ impl Config {
|
||||||
/// [`etcetera::choose_base_strategy`](https://docs.rs/etcetera/*/etcetera/#basestrategy)
|
/// [`etcetera::choose_base_strategy`](https://docs.rs/etcetera/*/etcetera/#basestrategy)
|
||||||
/// - `$HOME/.tree-sitter/config.json` as a fallback from where tree-sitter _used_ to store
|
/// - `$HOME/.tree-sitter/config.json` as a fallback from where tree-sitter _used_ to store
|
||||||
/// its configuration
|
/// its configuration
|
||||||
pub fn load(path: Option<PathBuf>) -> Result<Self> {
|
pub fn load(path: Option<PathBuf>) -> ConfigResult<Self> {
|
||||||
let location = if let Some(path) = path {
|
let location = if let Some(path) = path {
|
||||||
path
|
path
|
||||||
} else if let Some(path) = Self::find_config_file()? {
|
} else if let Some(path) = Self::find_config_file()? {
|
||||||
|
|
@ -94,9 +140,9 @@ impl Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = fs::read_to_string(&location)
|
let content = fs::read_to_string(&location)
|
||||||
.with_context(|| format!("Failed to read {}", location.to_string_lossy()))?;
|
.map_err(|e| ConfigError::IO(IoError::new(e, Some(location.as_path()))))?;
|
||||||
let config = serde_json::from_str(&content)
|
let config = serde_json::from_str(&content)
|
||||||
.with_context(|| format!("Bad JSON config {}", location.to_string_lossy()))?;
|
.map_err(|e| ConfigError::ConfigRead(location.to_string_lossy().to_string(), e))?;
|
||||||
Ok(Self { location, config })
|
Ok(Self { location, config })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +152,7 @@ impl Config {
|
||||||
/// disk.
|
/// disk.
|
||||||
///
|
///
|
||||||
/// (Note that this is typically only done by the `tree-sitter init-config` command.)
|
/// (Note that this is typically only done by the `tree-sitter init-config` command.)
|
||||||
pub fn initial() -> Result<Self> {
|
pub fn initial() -> ConfigResult<Self> {
|
||||||
let location = if let Ok(path) = env::var("TREE_SITTER_DIR") {
|
let location = if let Ok(path) = env::var("TREE_SITTER_DIR") {
|
||||||
let mut path = PathBuf::from(path);
|
let mut path = PathBuf::from(path);
|
||||||
path.push("config.json");
|
path.push("config.json");
|
||||||
|
|
@ -119,17 +165,20 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves this configuration to the file that it was originally loaded from.
|
/// Saves this configuration to the file that it was originally loaded from.
|
||||||
pub fn save(&self) -> Result<()> {
|
pub fn save(&self) -> ConfigResult<()> {
|
||||||
let json = serde_json::to_string_pretty(&self.config)?;
|
let json = serde_json::to_string_pretty(&self.config)?;
|
||||||
fs::create_dir_all(self.location.parent().unwrap())?;
|
let config_dir = self.location.parent().unwrap();
|
||||||
fs::write(&self.location, json)?;
|
fs::create_dir_all(config_dir)
|
||||||
|
.map_err(|e| ConfigError::IO(IoError::new(e, Some(config_dir))))?;
|
||||||
|
fs::write(&self.location, json)
|
||||||
|
.map_err(|e| ConfigError::IO(IoError::new(e, Some(self.location.as_path()))))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a component-specific configuration from the configuration file. The type `C` must
|
/// Parses a component-specific configuration from the configuration file. The type `C` must
|
||||||
/// be [deserializable](https://docs.rs/serde/*/serde/trait.Deserialize.html) from a JSON
|
/// be [deserializable](https://docs.rs/serde/*/serde/trait.Deserialize.html) from a JSON
|
||||||
/// object, and must only include the fields relevant to that component.
|
/// object, and must only include the fields relevant to that component.
|
||||||
pub fn get<C>(&self) -> Result<C>
|
pub fn get<C>(&self) -> ConfigResult<C>
|
||||||
where
|
where
|
||||||
C: for<'de> Deserialize<'de>,
|
C: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +189,7 @@ impl Config {
|
||||||
/// Adds a component-specific configuration to the configuration file. The type `C` must be
|
/// Adds a component-specific configuration to the configuration file. The type `C` must be
|
||||||
/// [serializable](https://docs.rs/serde/*/serde/trait.Serialize.html) into a JSON object, and
|
/// [serializable](https://docs.rs/serde/*/serde/trait.Serialize.html) into a JSON object, and
|
||||||
/// must only include the fields relevant to that component.
|
/// must only include the fields relevant to that component.
|
||||||
pub fn add<C>(&mut self, config: C) -> Result<()>
|
pub fn add<C>(&mut self, config: C) -> ConfigResult<()>
|
||||||
where
|
where
|
||||||
C: Serialize,
|
C: Serialize,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ load = ["dep:semver"]
|
||||||
qjs-rt = ["load", "rquickjs", "pathdiff"]
|
qjs-rt = ["load", "rquickjs", "pathdiff"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
bitflags = "2.9.4"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
|
|
@ -33,7 +33,7 @@ log.workspace = true
|
||||||
pathdiff = { version = "0.2.3", optional = true }
|
pathdiff = { version = "0.2.3", optional = true }
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
regex-syntax.workspace = true
|
regex-syntax.workspace = true
|
||||||
rquickjs = { version = "0.9.0", optional = true, features = [
|
rquickjs = { version = "0.11.0", optional = true, features = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"loader",
|
"loader",
|
||||||
"macro",
|
"macro",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ use crate::{
|
||||||
node_types::VariableInfo,
|
node_types::VariableInfo,
|
||||||
rules::{AliasMap, Symbol, SymbolType, TokenSet},
|
rules::{AliasMap, Symbol, SymbolType, TokenSet},
|
||||||
tables::{LexTable, ParseAction, ParseTable, ParseTableEntry},
|
tables::{LexTable, ParseAction, ParseTable, ParseTableEntry},
|
||||||
|
OptLevel,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Tables {
|
pub struct Tables {
|
||||||
|
|
@ -43,6 +44,7 @@ pub fn build_tables(
|
||||||
variable_info: &[VariableInfo],
|
variable_info: &[VariableInfo],
|
||||||
inlines: &InlinedProductionMap,
|
inlines: &InlinedProductionMap,
|
||||||
report_symbol_name: Option<&str>,
|
report_symbol_name: Option<&str>,
|
||||||
|
optimizations: OptLevel,
|
||||||
) -> BuildTableResult<Tables> {
|
) -> BuildTableResult<Tables> {
|
||||||
let item_set_builder = ParseItemSetBuilder::new(syntax_grammar, lexical_grammar, inlines);
|
let item_set_builder = ParseItemSetBuilder::new(syntax_grammar, lexical_grammar, inlines);
|
||||||
let following_tokens =
|
let following_tokens =
|
||||||
|
|
@ -78,6 +80,7 @@ pub fn build_tables(
|
||||||
simple_aliases,
|
simple_aliases,
|
||||||
&token_conflict_map,
|
&token_conflict_map,
|
||||||
&keywords,
|
&keywords,
|
||||||
|
optimizations,
|
||||||
);
|
);
|
||||||
let lex_tables = build_lex_table(
|
let lex_tables = build_lex_table(
|
||||||
&mut parse_table,
|
&mut parse_table,
|
||||||
|
|
@ -100,6 +103,10 @@ pub fn build_tables(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parse_table.states.len() > u16::MAX as usize {
|
||||||
|
Err(ParseTableBuilderError::StateCount(parse_table.states.len()))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Tables {
|
Ok(Tables {
|
||||||
parse_table,
|
parse_table,
|
||||||
main_lex_table: lex_tables.main_lex_table,
|
main_lex_table: lex_tables.main_lex_table,
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,11 @@ pub enum ParseTableBuilderError {
|
||||||
"The non-terminal rule `{0}` is used in a non-terminal `extra` rule, which is not allowed."
|
"The non-terminal rule `{0}` is used in a non-terminal `extra` rule, which is not allowed."
|
||||||
)]
|
)]
|
||||||
ImproperNonTerminalExtra(String),
|
ImproperNonTerminalExtra(String),
|
||||||
|
#[error("State count `{0}` exceeds the max value {max}.", max=u16::MAX)]
|
||||||
|
StateCount(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize)]
|
#[derive(Default, Debug, Serialize, Error)]
|
||||||
pub struct ConflictError {
|
pub struct ConflictError {
|
||||||
pub symbol_sequence: Vec<String>,
|
pub symbol_sequence: Vec<String>,
|
||||||
pub conflicting_lookahead: String,
|
pub conflicting_lookahead: String,
|
||||||
|
|
@ -87,7 +89,7 @@ pub struct ConflictError {
|
||||||
pub possible_resolutions: Vec<Resolution>,
|
pub possible_resolutions: Vec<Resolution>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize)]
|
#[derive(Default, Debug, Serialize, Error)]
|
||||||
pub struct Interpretation {
|
pub struct Interpretation {
|
||||||
pub preceding_symbols: Vec<String>,
|
pub preceding_symbols: Vec<String>,
|
||||||
pub variable_name: String,
|
pub variable_name: String,
|
||||||
|
|
@ -106,7 +108,7 @@ pub enum Resolution {
|
||||||
AddConflict { symbols: Vec<String> },
|
AddConflict { symbols: Vec<String> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, Error)]
|
||||||
pub struct AmbiguousExtraError {
|
pub struct AmbiguousExtraError {
|
||||||
pub parent_symbols: Vec<String>,
|
pub parent_symbols: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
@ -236,9 +238,6 @@ impl std::fmt::Display for AmbiguousExtraError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ConflictError {}
|
|
||||||
impl std::error::Error for AmbiguousExtraError {}
|
|
||||||
|
|
||||||
impl<'a> ParseTableBuilder<'a> {
|
impl<'a> ParseTableBuilder<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
syntax_grammar: &'a SyntaxGrammar,
|
syntax_grammar: &'a SyntaxGrammar,
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ impl fmt::Display for ParseItemDisplay<'_> {
|
||||||
|| step.reserved_word_set_id != ReservedWordSetId::default()
|
|| step.reserved_word_set_id != ReservedWordSetId::default()
|
||||||
{
|
{
|
||||||
write!(f, " (")?;
|
write!(f, " (")?;
|
||||||
if step.precedence.is_none() {
|
if !step.precedence.is_none() {
|
||||||
write!(f, " {}", step.precedence)?;
|
write!(f, " {}", step.precedence)?;
|
||||||
}
|
}
|
||||||
if let Some(associativity) = step.associativity {
|
if let Some(associativity) = step.associativity {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::{
|
||||||
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
|
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
|
||||||
rules::{AliasMap, Symbol, TokenSet},
|
rules::{AliasMap, Symbol, TokenSet},
|
||||||
tables::{GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry},
|
tables::{GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry},
|
||||||
|
OptLevel,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn minimize_parse_table(
|
pub fn minimize_parse_table(
|
||||||
|
|
@ -20,6 +21,7 @@ pub fn minimize_parse_table(
|
||||||
simple_aliases: &AliasMap,
|
simple_aliases: &AliasMap,
|
||||||
token_conflict_map: &TokenConflictMap,
|
token_conflict_map: &TokenConflictMap,
|
||||||
keywords: &TokenSet,
|
keywords: &TokenSet,
|
||||||
|
optimizations: OptLevel,
|
||||||
) {
|
) {
|
||||||
let mut minimizer = Minimizer {
|
let mut minimizer = Minimizer {
|
||||||
parse_table,
|
parse_table,
|
||||||
|
|
@ -29,7 +31,9 @@ pub fn minimize_parse_table(
|
||||||
keywords,
|
keywords,
|
||||||
simple_aliases,
|
simple_aliases,
|
||||||
};
|
};
|
||||||
minimizer.merge_compatible_states();
|
if optimizations.contains(OptLevel::MergeStates) {
|
||||||
|
minimizer.merge_compatible_states();
|
||||||
|
}
|
||||||
minimizer.remove_unit_reductions();
|
minimizer.remove_unit_reductions();
|
||||||
minimizer.remove_unused_states();
|
minimizer.remove_unused_states();
|
||||||
minimizer.reorder_states_by_descending_size();
|
minimizer.reorder_states_by_descending_size();
|
||||||
|
|
@ -302,9 +306,7 @@ impl Minimizer<'_> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, action1) in actions1.iter().enumerate() {
|
for (action1, action2) in actions1.iter().zip(actions2.iter()) {
|
||||||
let action2 = &actions2[i];
|
|
||||||
|
|
||||||
// Two shift actions are equivalent if their destinations are in the same group.
|
// Two shift actions are equivalent if their destinations are in the same group.
|
||||||
if let (
|
if let (
|
||||||
ParseAction::Shift {
|
ParseAction::Shift {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub struct TokenConflictMap<'a> {
|
||||||
|
|
||||||
impl<'a> TokenConflictMap<'a> {
|
impl<'a> TokenConflictMap<'a> {
|
||||||
/// Create a token conflict map based on a lexical grammar, which describes the structure
|
/// Create a token conflict map based on a lexical grammar, which describes the structure
|
||||||
/// each token, and a `following_token` map, which indicates which tokens may be appear
|
/// of each token, and a `following_token` map, which indicates which tokens may be appear
|
||||||
/// immediately after each other token.
|
/// immediately after each other token.
|
||||||
///
|
///
|
||||||
/// This analyzes the possible kinds of overlap between each pair of tokens and stores
|
/// This analyzes the possible kinds of overlap between each pair of tokens and stores
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, sync::LazyLock};
|
use std::{collections::BTreeMap, sync::LazyLock};
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env, fs,
|
||||||
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use bitflags::bitflags;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use node_types::VariableInfo;
|
use node_types::VariableInfo;
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
|
|
@ -56,7 +56,7 @@ struct JSONOutput {
|
||||||
syntax_grammar: SyntaxGrammar,
|
syntax_grammar: SyntaxGrammar,
|
||||||
lexical_grammar: LexicalGrammar,
|
lexical_grammar: LexicalGrammar,
|
||||||
inlines: InlinedProductionMap,
|
inlines: InlinedProductionMap,
|
||||||
simple_aliases: HashMap<Symbol, Alias>,
|
simple_aliases: BTreeMap<Symbol, Alias>,
|
||||||
variable_info: Vec<VariableInfo>,
|
variable_info: Vec<VariableInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,8 +80,8 @@ pub type GenerateResult<T> = Result<T, GenerateError>;
|
||||||
pub enum GenerateError {
|
pub enum GenerateError {
|
||||||
#[error("Error with specified path -- {0}")]
|
#[error("Error with specified path -- {0}")]
|
||||||
GrammarPath(String),
|
GrammarPath(String),
|
||||||
#[error("{0}")]
|
#[error(transparent)]
|
||||||
IO(String),
|
IO(IoError),
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
LoadGrammarFile(#[from] LoadGrammarError),
|
LoadGrammarFile(#[from] LoadGrammarError),
|
||||||
|
|
@ -100,9 +100,28 @@ pub enum GenerateError {
|
||||||
SuperTypeCycle(#[from] SuperTypeCycleError),
|
SuperTypeCycle(#[from] SuperTypeCycleError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for GenerateError {
|
#[derive(Debug, Error, Serialize)]
|
||||||
fn from(value: std::io::Error) -> Self {
|
pub struct IoError {
|
||||||
Self::IO(value.to_string())
|
pub error: String,
|
||||||
|
pub path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoError {
|
||||||
|
fn new(error: &std::io::Error, path: Option<&Path>) -> Self {
|
||||||
|
Self {
|
||||||
|
error: error.to_string(),
|
||||||
|
path: path.map(|p| p.to_string_lossy().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for IoError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.error)?;
|
||||||
|
if let Some(ref path) = self.path {
|
||||||
|
write!(f, " ({path})")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,18 +136,11 @@ pub enum LoadGrammarError {
|
||||||
#[error("Failed to load grammar.js -- {0}")]
|
#[error("Failed to load grammar.js -- {0}")]
|
||||||
LoadJSGrammarFile(#[from] JSError),
|
LoadJSGrammarFile(#[from] JSError),
|
||||||
#[error("Failed to load grammar.json -- {0}")]
|
#[error("Failed to load grammar.json -- {0}")]
|
||||||
IO(String),
|
IO(IoError),
|
||||||
#[error("Unknown grammar file extension: {0:?}")]
|
#[error("Unknown grammar file extension: {0:?}")]
|
||||||
FileExtension(PathBuf),
|
FileExtension(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
|
||||||
impl From<std::io::Error> for LoadGrammarError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IO(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
#[derive(Debug, Error, Serialize)]
|
#[derive(Debug, Error, Serialize)]
|
||||||
pub enum ParseVersionError {
|
pub enum ParseVersionError {
|
||||||
|
|
@ -136,8 +148,8 @@ pub enum ParseVersionError {
|
||||||
Version(String),
|
Version(String),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
JSON(String),
|
JSON(String),
|
||||||
#[error("{0}")]
|
#[error(transparent)]
|
||||||
IO(String),
|
IO(IoError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
|
|
@ -152,8 +164,21 @@ pub enum JSError {
|
||||||
JSRuntimeUtf8 { runtime: String, error: String },
|
JSRuntimeUtf8 { runtime: String, error: String },
|
||||||
#[error("`{runtime}` process exited with status {code}")]
|
#[error("`{runtime}` process exited with status {code}")]
|
||||||
JSRuntimeExit { runtime: String, code: i32 },
|
JSRuntimeExit { runtime: String, code: i32 },
|
||||||
#[error("{0}")]
|
#[error("Failed to open stdin for `{runtime}`")]
|
||||||
IO(String),
|
JSRuntimeStdin { runtime: String },
|
||||||
|
#[error("Failed to write {item} to `{runtime}`'s stdin -- {error}")]
|
||||||
|
JSRuntimeWrite {
|
||||||
|
runtime: String,
|
||||||
|
item: String,
|
||||||
|
error: String,
|
||||||
|
},
|
||||||
|
#[error("Failed to read output from `{runtime}` -- {error}")]
|
||||||
|
JSRuntimeRead { runtime: String, error: String },
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(IoError),
|
||||||
|
#[cfg(feature = "qjs-rt")]
|
||||||
|
#[error("Failed to get relative path")]
|
||||||
|
RelativePath,
|
||||||
#[error("Could not parse this package's version as semver -- {0}")]
|
#[error("Could not parse this package's version as semver -- {0}")]
|
||||||
Semver(String),
|
Semver(String),
|
||||||
#[error("Failed to serialze grammar JSON -- {0}")]
|
#[error("Failed to serialze grammar JSON -- {0}")]
|
||||||
|
|
@ -163,13 +188,6 @@ pub enum JSError {
|
||||||
QuickJS(String),
|
QuickJS(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
|
||||||
impl From<std::io::Error> for JSError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IO(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
impl From<serde_json::Error> for JSError {
|
impl From<serde_json::Error> for JSError {
|
||||||
fn from(value: serde_json::Error) -> Self {
|
fn from(value: serde_json::Error) -> Self {
|
||||||
|
|
@ -191,6 +209,19 @@ impl From<rquickjs::Error> for JSError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct OptLevel: u32 {
|
||||||
|
const MergeStates = 1 << 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OptLevel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::MergeStates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn generate_parser_in_directory<T, U, V>(
|
pub fn generate_parser_in_directory<T, U, V>(
|
||||||
|
|
@ -201,6 +232,7 @@ pub fn generate_parser_in_directory<T, U, V>(
|
||||||
report_symbol_name: Option<&str>,
|
report_symbol_name: Option<&str>,
|
||||||
js_runtime: Option<&str>,
|
js_runtime: Option<&str>,
|
||||||
generate_parser: bool,
|
generate_parser: bool,
|
||||||
|
optimizations: OptLevel,
|
||||||
) -> GenerateResult<()>
|
) -> GenerateResult<()>
|
||||||
where
|
where
|
||||||
T: Into<PathBuf>,
|
T: Into<PathBuf>,
|
||||||
|
|
@ -216,7 +248,8 @@ where
|
||||||
.try_exists()
|
.try_exists()
|
||||||
.map_err(|e| GenerateError::GrammarPath(e.to_string()))?
|
.map_err(|e| GenerateError::GrammarPath(e.to_string()))?
|
||||||
{
|
{
|
||||||
fs::create_dir_all(&path_buf)?;
|
fs::create_dir_all(&path_buf)
|
||||||
|
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(path_buf.as_path()))))?;
|
||||||
repo_path = path_buf;
|
repo_path = path_buf;
|
||||||
repo_path.join("grammar.js")
|
repo_path.join("grammar.js")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -233,15 +266,12 @@ where
|
||||||
let header_path = src_path.join("tree_sitter");
|
let header_path = src_path.join("tree_sitter");
|
||||||
|
|
||||||
// Ensure that the output directory exists
|
// Ensure that the output directory exists
|
||||||
fs::create_dir_all(&src_path)?;
|
fs::create_dir_all(&src_path)
|
||||||
|
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(src_path.as_path()))))?;
|
||||||
|
|
||||||
if grammar_path.file_name().unwrap() != "grammar.json" {
|
if grammar_path.file_name().unwrap() != "grammar.json" {
|
||||||
fs::write(src_path.join("grammar.json"), &grammar_json).map_err(|e| {
|
fs::write(src_path.join("grammar.json"), &grammar_json)
|
||||||
GenerateError::IO(format!(
|
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(src_path.as_path()))))?;
|
||||||
"Failed to write grammar.json to {} -- {e}",
|
|
||||||
src_path.display()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our job is only to generate `grammar.json` and not `parser.c`, stop here.
|
// If our job is only to generate `grammar.json` and not `parser.c`, stop here.
|
||||||
|
|
@ -278,11 +308,13 @@ where
|
||||||
abi_version,
|
abi_version,
|
||||||
semantic_version.map(|v| (v.major as u8, v.minor as u8, v.patch as u8)),
|
semantic_version.map(|v| (v.major as u8, v.minor as u8, v.patch as u8)),
|
||||||
report_symbol_name,
|
report_symbol_name,
|
||||||
|
optimizations,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
write_file(&src_path.join("parser.c"), c_code)?;
|
write_file(&src_path.join("parser.c"), c_code)?;
|
||||||
write_file(&src_path.join("node-types.json"), node_types_json)?;
|
write_file(&src_path.join("node-types.json"), node_types_json)?;
|
||||||
fs::create_dir_all(&header_path)?;
|
fs::create_dir_all(&header_path)
|
||||||
|
.map_err(|e| GenerateError::IO(IoError::new(&e, Some(header_path.as_path()))))?;
|
||||||
write_file(&header_path.join("alloc.h"), ALLOC_HEADER)?;
|
write_file(&header_path.join("alloc.h"), ALLOC_HEADER)?;
|
||||||
write_file(&header_path.join("array.h"), ARRAY_HEADER)?;
|
write_file(&header_path.join("array.h"), ARRAY_HEADER)?;
|
||||||
write_file(&header_path.join("parser.h"), PARSER_HEADER)?;
|
write_file(&header_path.join("parser.h"), PARSER_HEADER)?;
|
||||||
|
|
@ -301,6 +333,7 @@ pub fn generate_parser_for_grammar(
|
||||||
LANGUAGE_VERSION,
|
LANGUAGE_VERSION,
|
||||||
semantic_version,
|
semantic_version,
|
||||||
None,
|
None,
|
||||||
|
OptLevel::empty(),
|
||||||
)?;
|
)?;
|
||||||
Ok((input_grammar.name, parser.c_code))
|
Ok((input_grammar.name, parser.c_code))
|
||||||
}
|
}
|
||||||
|
|
@ -334,6 +367,7 @@ fn generate_parser_for_grammar_with_opts(
|
||||||
abi_version: usize,
|
abi_version: usize,
|
||||||
semantic_version: Option<(u8, u8, u8)>,
|
semantic_version: Option<(u8, u8, u8)>,
|
||||||
report_symbol_name: Option<&str>,
|
report_symbol_name: Option<&str>,
|
||||||
|
optimizations: OptLevel,
|
||||||
) -> GenerateResult<GeneratedParser> {
|
) -> GenerateResult<GeneratedParser> {
|
||||||
let JSONOutput {
|
let JSONOutput {
|
||||||
syntax_grammar,
|
syntax_grammar,
|
||||||
|
|
@ -353,6 +387,7 @@ fn generate_parser_for_grammar_with_opts(
|
||||||
&variable_info,
|
&variable_info,
|
||||||
&inlines,
|
&inlines,
|
||||||
report_symbol_name,
|
report_symbol_name,
|
||||||
|
optimizations,
|
||||||
)?;
|
)?;
|
||||||
let c_code = render_c_code(
|
let c_code = render_c_code(
|
||||||
&input_grammar.name,
|
&input_grammar.name,
|
||||||
|
|
@ -395,9 +430,8 @@ fn read_grammar_version(repo_path: &Path) -> Result<Option<Version>, ParseVersio
|
||||||
let json = path
|
let json = path
|
||||||
.exists()
|
.exists()
|
||||||
.then(|| {
|
.then(|| {
|
||||||
let contents = fs::read_to_string(path.as_path()).map_err(|e| {
|
let contents = fs::read_to_string(path.as_path())
|
||||||
ParseVersionError::IO(format!("Failed to read `{}` -- {e}", path.display()))
|
.map_err(|e| ParseVersionError::IO(IoError::new(&e, Some(path.as_path()))))?;
|
||||||
})?;
|
|
||||||
serde_json::from_str::<TreeSitterJson>(&contents).map_err(|e| {
|
serde_json::from_str::<TreeSitterJson>(&contents).map_err(|e| {
|
||||||
ParseVersionError::JSON(format!("Failed to parse `{}` -- {e}", path.display()))
|
ParseVersionError::JSON(format!("Failed to parse `{}` -- {e}", path.display()))
|
||||||
})
|
})
|
||||||
|
|
@ -431,14 +465,16 @@ pub fn load_grammar_file(
|
||||||
}
|
}
|
||||||
match grammar_path.extension().and_then(|e| e.to_str()) {
|
match grammar_path.extension().and_then(|e| e.to_str()) {
|
||||||
Some("js") => Ok(load_js_grammar_file(grammar_path, js_runtime)?),
|
Some("js") => Ok(load_js_grammar_file(grammar_path, js_runtime)?),
|
||||||
Some("json") => Ok(fs::read_to_string(grammar_path)?),
|
Some("json") => Ok(fs::read_to_string(grammar_path)
|
||||||
|
.map_err(|e| LoadGrammarError::IO(IoError::new(&e, Some(grammar_path))))?),
|
||||||
_ => Err(LoadGrammarError::FileExtension(grammar_path.to_owned()))?,
|
_ => Err(LoadGrammarError::FileExtension(grammar_path.to_owned()))?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResult<String> {
|
fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResult<String> {
|
||||||
let grammar_path = dunce::canonicalize(grammar_path)?;
|
let grammar_path = dunce::canonicalize(grammar_path)
|
||||||
|
.map_err(|e| JSError::IO(IoError::new(&e, Some(grammar_path))))?;
|
||||||
|
|
||||||
#[cfg(feature = "qjs-rt")]
|
#[cfg(feature = "qjs-rt")]
|
||||||
if js_runtime == Some("native") {
|
if js_runtime == Some("native") {
|
||||||
|
|
@ -479,7 +515,9 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
|
||||||
let mut js_stdin = js_process
|
let mut js_stdin = js_process
|
||||||
.stdin
|
.stdin
|
||||||
.take()
|
.take()
|
||||||
.ok_or_else(|| JSError::IO(format!("Failed to open stdin for `{js_runtime}`")))?;
|
.ok_or_else(|| JSError::JSRuntimeStdin {
|
||||||
|
runtime: js_runtime.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let cli_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
let cli_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
||||||
write!(
|
write!(
|
||||||
|
|
@ -489,23 +527,27 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
|
||||||
globalThis.TREE_SITTER_CLI_VERSION_PATCH = {};",
|
globalThis.TREE_SITTER_CLI_VERSION_PATCH = {};",
|
||||||
cli_version.major, cli_version.minor, cli_version.patch,
|
cli_version.major, cli_version.minor, cli_version.patch,
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| JSError::JSRuntimeWrite {
|
||||||
JSError::IO(format!(
|
runtime: js_runtime.to_string(),
|
||||||
"Failed to write tree-sitter version to `{js_runtime}`'s stdin -- {e}"
|
item: "tree-sitter version".to_string(),
|
||||||
))
|
error: e.to_string(),
|
||||||
})?;
|
|
||||||
js_stdin.write(include_bytes!("./dsl.js")).map_err(|e| {
|
|
||||||
JSError::IO(format!(
|
|
||||||
"Failed to write grammar dsl to `{js_runtime}`'s stdin -- {e}"
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
js_stdin
|
||||||
|
.write(include_bytes!("./dsl.js"))
|
||||||
|
.map_err(|e| JSError::JSRuntimeWrite {
|
||||||
|
runtime: js_runtime.to_string(),
|
||||||
|
item: "grammar dsl".to_string(),
|
||||||
|
error: e.to_string(),
|
||||||
|
})?;
|
||||||
drop(js_stdin);
|
drop(js_stdin);
|
||||||
|
|
||||||
let output = js_process
|
let output = js_process
|
||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.map_err(|e| JSError::IO(format!("Failed to read output from `{js_runtime}` -- {e}")))?;
|
.map_err(|e| JSError::JSRuntimeRead {
|
||||||
|
runtime: js_runtime.to_string(),
|
||||||
|
error: e.to_string(),
|
||||||
|
})?;
|
||||||
match output.status.code() {
|
match output.status.code() {
|
||||||
None => panic!("`{js_runtime}` process was killed"),
|
|
||||||
Some(0) => {
|
Some(0) => {
|
||||||
let stdout = String::from_utf8(output.stdout).map_err(|e| JSError::JSRuntimeUtf8 {
|
let stdout = String::from_utf8(output.stdout).map_err(|e| JSError::JSRuntimeUtf8 {
|
||||||
runtime: js_runtime.to_string(),
|
runtime: js_runtime.to_string(),
|
||||||
|
|
@ -520,9 +562,15 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
|
||||||
grammar_json = &stdout[pos + 1..];
|
grammar_json = &stdout[pos + 1..];
|
||||||
|
|
||||||
let mut stdout = std::io::stdout().lock();
|
let mut stdout = std::io::stdout().lock();
|
||||||
stdout.write_all(node_output.as_bytes())?;
|
stdout
|
||||||
stdout.write_all(b"\n")?;
|
.write_all(node_output.as_bytes())
|
||||||
stdout.flush()?;
|
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
|
||||||
|
stdout
|
||||||
|
.write_all(b"\n")
|
||||||
|
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
|
||||||
|
stdout
|
||||||
|
.flush()
|
||||||
|
.map_err(|e| JSError::IO(IoError::new(&e, None)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(serde_json::to_string_pretty(&serde_json::from_str::<
|
Ok(serde_json::to_string_pretty(&serde_json::from_str::<
|
||||||
|
|
@ -533,13 +581,16 @@ fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResu
|
||||||
runtime: js_runtime.to_string(),
|
runtime: js_runtime.to_string(),
|
||||||
code,
|
code,
|
||||||
}),
|
}),
|
||||||
|
None => Err(JSError::JSRuntimeExit {
|
||||||
|
runtime: js_runtime.to_string(),
|
||||||
|
code: -1,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "load")]
|
#[cfg(feature = "load")]
|
||||||
pub fn write_file(path: &Path, body: impl AsRef<[u8]>) -> GenerateResult<()> {
|
pub fn write_file(path: &Path, body: impl AsRef<[u8]>) -> GenerateResult<()> {
|
||||||
fs::write(path, body)
|
fs::write(path, body).map_err(|e| GenerateError::IO(IoError::new(&e, Some(path))))
|
||||||
.map_err(|e| GenerateError::IO(format!("Failed to write {:?} -- {e}", path.file_name())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -378,11 +377,11 @@ pub fn get_variable_info(
|
||||||
fn get_aliases_by_symbol(
|
fn get_aliases_by_symbol(
|
||||||
syntax_grammar: &SyntaxGrammar,
|
syntax_grammar: &SyntaxGrammar,
|
||||||
default_aliases: &AliasMap,
|
default_aliases: &AliasMap,
|
||||||
) -> HashMap<Symbol, HashSet<Option<Alias>>> {
|
) -> HashMap<Symbol, BTreeSet<Option<Alias>>> {
|
||||||
let mut aliases_by_symbol = HashMap::new();
|
let mut aliases_by_symbol = HashMap::new();
|
||||||
for (symbol, alias) in default_aliases {
|
for (symbol, alias) in default_aliases {
|
||||||
aliases_by_symbol.insert(*symbol, {
|
aliases_by_symbol.insert(*symbol, {
|
||||||
let mut aliases = HashSet::new();
|
let mut aliases = BTreeSet::new();
|
||||||
aliases.insert(Some(alias.clone()));
|
aliases.insert(Some(alias.clone()));
|
||||||
aliases
|
aliases
|
||||||
});
|
});
|
||||||
|
|
@ -391,7 +390,7 @@ fn get_aliases_by_symbol(
|
||||||
if !default_aliases.contains_key(extra_symbol) {
|
if !default_aliases.contains_key(extra_symbol) {
|
||||||
aliases_by_symbol
|
aliases_by_symbol
|
||||||
.entry(*extra_symbol)
|
.entry(*extra_symbol)
|
||||||
.or_insert_with(HashSet::new)
|
.or_insert_with(BTreeSet::new)
|
||||||
.insert(None);
|
.insert(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +399,7 @@ fn get_aliases_by_symbol(
|
||||||
for step in &production.steps {
|
for step in &production.steps {
|
||||||
aliases_by_symbol
|
aliases_by_symbol
|
||||||
.entry(step.symbol)
|
.entry(step.symbol)
|
||||||
.or_insert_with(HashSet::new)
|
.or_insert_with(BTreeSet::new)
|
||||||
.insert(
|
.insert(
|
||||||
step.alias
|
step.alias
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -531,7 +530,7 @@ pub fn generate_node_types_json(
|
||||||
|
|
||||||
let aliases_by_symbol = get_aliases_by_symbol(syntax_grammar, default_aliases);
|
let aliases_by_symbol = get_aliases_by_symbol(syntax_grammar, default_aliases);
|
||||||
|
|
||||||
let empty = HashSet::new();
|
let empty = BTreeSet::new();
|
||||||
let extra_names = syntax_grammar
|
let extra_names = syntax_grammar
|
||||||
.extra_symbols
|
.extra_symbols
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -590,7 +589,7 @@ pub fn generate_node_types_json(
|
||||||
} else if !syntax_grammar.variables_to_inline.contains(&symbol) {
|
} else if !syntax_grammar.variables_to_inline.contains(&symbol) {
|
||||||
// If a rule is aliased under multiple names, then its information
|
// If a rule is aliased under multiple names, then its information
|
||||||
// contributes to multiple entries in the final JSON.
|
// contributes to multiple entries in the final JSON.
|
||||||
for alias in aliases_by_symbol.get(&symbol).unwrap_or(&HashSet::new()) {
|
for alias in aliases_by_symbol.get(&symbol).unwrap_or(&BTreeSet::new()) {
|
||||||
let kind;
|
let kind;
|
||||||
let is_named;
|
let is_named;
|
||||||
if let Some(alias) = alias {
|
if let Some(alias) = alias {
|
||||||
|
|
@ -784,6 +783,9 @@ pub fn generate_node_types_json(
|
||||||
a_is_leaf.cmp(&b_is_leaf)
|
a_is_leaf.cmp(&b_is_leaf)
|
||||||
})
|
})
|
||||||
.then_with(|| a.kind.cmp(&b.kind))
|
.then_with(|| a.kind.cmp(&b.kind))
|
||||||
|
.then_with(|| a.named.cmp(&b.named))
|
||||||
|
.then_with(|| a.root.cmp(&b.root))
|
||||||
|
.then_with(|| a.extra.cmp(&b.extra))
|
||||||
});
|
});
|
||||||
result.dedup();
|
result.dedup();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|
@ -826,12 +828,12 @@ fn extend_sorted<'a, T>(vec: &mut Vec<T>, values: impl IntoIterator<Item = &'a T
|
||||||
where
|
where
|
||||||
T: 'a + Clone + Eq + Ord,
|
T: 'a + Clone + Eq + Ord,
|
||||||
{
|
{
|
||||||
values.into_iter().any(|value| {
|
values.into_iter().fold(false, |acc, value| {
|
||||||
if let Err(i) = vec.binary_search(value) {
|
if let Err(i) = vec.binary_search(value) {
|
||||||
vec.insert(i, value.clone());
|
vec.insert(i, value.clone());
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
acc
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -18,7 +17,7 @@ use crate::{
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
enum RuleJSON {
|
enum RuleJSON {
|
||||||
ALIAS {
|
ALIAS {
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
named: bool,
|
named: bool,
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
@ -34,46 +33,46 @@ enum RuleJSON {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
CHOICE {
|
CHOICE {
|
||||||
members: Vec<RuleJSON>,
|
members: Vec<Self>,
|
||||||
},
|
},
|
||||||
FIELD {
|
FIELD {
|
||||||
name: String,
|
name: String,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
SEQ {
|
SEQ {
|
||||||
members: Vec<RuleJSON>,
|
members: Vec<Self>,
|
||||||
},
|
},
|
||||||
REPEAT {
|
REPEAT {
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
REPEAT1 {
|
REPEAT1 {
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
PREC_DYNAMIC {
|
PREC_DYNAMIC {
|
||||||
value: i32,
|
value: i32,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
PREC_LEFT {
|
PREC_LEFT {
|
||||||
value: PrecedenceValueJSON,
|
value: PrecedenceValueJSON,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
PREC_RIGHT {
|
PREC_RIGHT {
|
||||||
value: PrecedenceValueJSON,
|
value: PrecedenceValueJSON,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
PREC {
|
PREC {
|
||||||
value: PrecedenceValueJSON,
|
value: PrecedenceValueJSON,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
TOKEN {
|
TOKEN {
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
IMMEDIATE_TOKEN {
|
IMMEDIATE_TOKEN {
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
RESERVED {
|
RESERVED {
|
||||||
context_name: String,
|
context_name: String,
|
||||||
content: Box<RuleJSON>,
|
content: Box<Self>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ use std::{
|
||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
pub use expand_tokens::ExpandTokensError;
|
pub use expand_tokens::ExpandTokensError;
|
||||||
pub use extract_tokens::ExtractTokensError;
|
pub use extract_tokens::ExtractTokensError;
|
||||||
pub use flatten_grammar::FlattenGrammarError;
|
pub use flatten_grammar::FlattenGrammarError;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use anyhow::Result;
|
|
||||||
use regex_syntax::{
|
use regex_syntax::{
|
||||||
hir::{Class, Hir, HirKind},
|
hir::{Class, Hir, HirKind},
|
||||||
ParserBuilder,
|
ParserBuilder,
|
||||||
|
|
@ -27,7 +26,7 @@ pub enum ExpandTokensError {
|
||||||
"The rule `{0}` matches the empty string.
|
"The rule `{0}` matches the empty string.
|
||||||
Tree-sitter does not support syntactic rules that match the empty string
|
Tree-sitter does not support syntactic rules that match the empty string
|
||||||
unless they are used only as the grammar's start rule.
|
unless they are used only as the grammar's start rule.
|
||||||
"
|
"
|
||||||
)]
|
)]
|
||||||
EmptyString(String),
|
EmptyString(String),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -189,7 +188,7 @@ impl NfaBuilder {
|
||||||
}
|
}
|
||||||
Rule::String(s) => {
|
Rule::String(s) => {
|
||||||
for c in s.chars().rev() {
|
for c in s.chars().rev() {
|
||||||
self.push_advance(CharacterSet::empty().add_char(c), next_state_id);
|
self.push_advance(CharacterSet::from_char(c), next_state_id);
|
||||||
next_state_id = self.nfa.last_state_id();
|
next_state_id = self.nfa.last_state_id();
|
||||||
}
|
}
|
||||||
Ok(!s.is_empty())
|
Ok(!s.is_empty())
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,7 @@ pub(super) fn extract_default_aliases(
|
||||||
SymbolType::External => &mut external_status_list[symbol.index],
|
SymbolType::External => &mut external_status_list[symbol.index],
|
||||||
SymbolType::NonTerminal => &mut non_terminal_status_list[symbol.index],
|
SymbolType::NonTerminal => &mut non_terminal_status_list[symbol.index],
|
||||||
SymbolType::Terminal => &mut terminal_status_list[symbol.index],
|
SymbolType::Terminal => &mut terminal_status_list[symbol.index],
|
||||||
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
|
SymbolType::End | SymbolType::EndOfNonTerminalExtra => panic!("Unexpected end token"),
|
||||||
panic!("Unexpected end token")
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
status.appears_unaliased = true;
|
status.appears_unaliased = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -153,7 +152,7 @@ pub(super) fn extract_tokens(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut external_tokens = Vec::new();
|
let mut external_tokens = Vec::with_capacity(grammar.external_tokens.len());
|
||||||
for external_token in grammar.external_tokens {
|
for external_token in grammar.external_tokens {
|
||||||
let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule);
|
let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule);
|
||||||
if let Rule::Symbol(symbol) = rule {
|
if let Rule::Symbol(symbol) = rule {
|
||||||
|
|
@ -591,14 +590,13 @@ mod test {
|
||||||
]);
|
]);
|
||||||
grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))];
|
grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))];
|
||||||
|
|
||||||
match extract_tokens(grammar) {
|
let result = extract_tokens(grammar);
|
||||||
Err(e) => {
|
assert!(result.is_err(), "Expected an error but got no error");
|
||||||
assert_eq!(e.to_string(), "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule");
|
let err = result.err().unwrap();
|
||||||
}
|
assert_eq!(
|
||||||
_ => {
|
err.to_string(),
|
||||||
panic!("Expected an error but got no error");
|
"Rule 'rule_1' cannot be used as both an external token and a non-terminal rule"
|
||||||
}
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use anyhow::Result;
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -279,10 +278,9 @@ mod tests {
|
||||||
fn test_grammar_with_undefined_symbols() {
|
fn test_grammar_with_undefined_symbols() {
|
||||||
let result = intern_symbols(&build_grammar(vec![Variable::named("x", Rule::named("y"))]));
|
let result = intern_symbols(&build_grammar(vec![Variable::named("x", Rule::named("y"))]));
|
||||||
|
|
||||||
match result {
|
assert!(result.is_err(), "Expected an error but got none");
|
||||||
Err(e) => assert_eq!(e.to_string(), "Undefined symbol `y`"),
|
let e = result.err().unwrap();
|
||||||
_ => panic!("Expected an error but got none"),
|
assert_eq!(e.to_string(), "Undefined symbol `y`");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_grammar(variables: Vec<Variable>) -> InputGrammar {
|
fn build_grammar(variables: Vec<Variable>) -> InputGrammar {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -71,12 +70,13 @@ impl InlinedProductionMapBuilder {
|
||||||
let production_map = production_indices_by_step_id
|
let production_map = production_indices_by_step_id
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(step_id, production_indices)| {
|
.map(|(step_id, production_indices)| {
|
||||||
let production = step_id.variable_index.map_or_else(
|
let production =
|
||||||
|| &productions[step_id.production_index],
|
core::ptr::from_ref::<Production>(step_id.variable_index.map_or_else(
|
||||||
|variable_index| {
|
|| &productions[step_id.production_index],
|
||||||
&grammar.variables[variable_index].productions[step_id.production_index]
|
|variable_index| {
|
||||||
},
|
&grammar.variables[variable_index].productions[step_id.production_index]
|
||||||
) as *const Production;
|
},
|
||||||
|
));
|
||||||
((production, step_id.step_index as u32), production_indices)
|
((production, step_id.step_index as u32), production_indices)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -549,10 +549,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(error) = process_inlines(&grammar, &lexical_grammar) {
|
let result = process_inlines(&grammar, &lexical_grammar);
|
||||||
assert_eq!(error.to_string(), "Token `something` cannot be inlined");
|
assert!(result.is_err(), "expected an error, but got none");
|
||||||
} else {
|
let err = result.err().unwrap();
|
||||||
panic!("expected an error, but got none");
|
assert_eq!(err.to_string(), "Token `something` cannot be inlined",);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use rquickjs::{
|
||||||
Context, Ctx, Function, Module, Object, Runtime, Type, Value,
|
Context, Ctx, Function, Module, Object, Runtime, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{JSError, JSResult};
|
use super::{IoError, JSError, JSResult};
|
||||||
|
|
||||||
const DSL: &[u8] = include_bytes!("dsl.js");
|
const DSL: &[u8] = include_bytes!("dsl.js");
|
||||||
|
|
||||||
|
|
@ -95,9 +95,27 @@ impl Console {
|
||||||
Type::Module => "module".to_string(),
|
Type::Module => "module".to_string(),
|
||||||
Type::BigInt => v.get::<String>().unwrap_or_else(|_| "BigInt".to_string()),
|
Type::BigInt => v.get::<String>().unwrap_or_else(|_| "BigInt".to_string()),
|
||||||
Type::Unknown => "unknown".to_string(),
|
Type::Unknown => "unknown".to_string(),
|
||||||
|
Type::Array => {
|
||||||
|
let js_vals = v
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter::<Value<'_>>()
|
||||||
|
.filter_map(|x| x.ok())
|
||||||
|
.map(|x| {
|
||||||
|
if x.is_string() {
|
||||||
|
format!("'{}'", Self::format_args(&[x]))
|
||||||
|
} else {
|
||||||
|
Self::format_args(&[x])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
format!("[ {js_vals} ]")
|
||||||
|
}
|
||||||
Type::Symbol
|
Type::Symbol
|
||||||
| Type::Object
|
| Type::Object
|
||||||
| Type::Array
|
| Type::Proxy
|
||||||
| Type::Function
|
| Type::Function
|
||||||
| Type::Constructor
|
| Type::Constructor
|
||||||
| Type::Promise
|
| Type::Promise
|
||||||
|
|
@ -197,11 +215,11 @@ fn try_resolve_path(path: &Path) -> rquickjs::Result<PathBuf> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn require_from_module<'a>(
|
fn require_from_module<'js>(
|
||||||
ctx: Ctx<'a>,
|
ctx: Ctx<'js>,
|
||||||
module_path: String,
|
module_path: String,
|
||||||
from_module: &str,
|
from_module: &str,
|
||||||
) -> rquickjs::Result<Value<'a>> {
|
) -> rquickjs::Result<Value<'js>> {
|
||||||
let current_module = PathBuf::from(from_module);
|
let current_module = PathBuf::from(from_module);
|
||||||
let current_dir = if current_module.is_file() {
|
let current_dir = if current_module.is_file() {
|
||||||
current_module.parent().unwrap_or(Path::new("."))
|
current_module.parent().unwrap_or(Path::new("."))
|
||||||
|
|
@ -216,13 +234,13 @@ fn require_from_module<'a>(
|
||||||
load_module_from_content(&ctx, &resolved_path, &contents)
|
load_module_from_content(&ctx, &resolved_path, &contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_module_from_content<'a>(
|
fn load_module_from_content<'js>(
|
||||||
ctx: &Ctx<'a>,
|
ctx: &Ctx<'js>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
) -> rquickjs::Result<Value<'a>> {
|
) -> rquickjs::Result<Value<'js>> {
|
||||||
if path.extension().is_some_and(|ext| ext == "json") {
|
if path.extension().is_some_and(|ext| ext == "json") {
|
||||||
return ctx.eval::<Value, _>(format!("JSON.parse({contents:?})"));
|
return ctx.eval::<Value<'js>, _>(format!("JSON.parse({contents:?})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let exports = Object::new(ctx.clone())?;
|
let exports = Object::new(ctx.clone())?;
|
||||||
|
|
@ -238,7 +256,7 @@ fn load_module_from_content<'a>(
|
||||||
let module_path = filename.clone();
|
let module_path = filename.clone();
|
||||||
let require = Function::new(
|
let require = Function::new(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
move |ctx_inner: Ctx<'a>, target_path: String| -> rquickjs::Result<Value<'a>> {
|
move |ctx_inner: Ctx<'js>, target_path: String| -> rquickjs::Result<Value<'js>> {
|
||||||
require_from_module(ctx_inner, target_path, &module_path)
|
require_from_module(ctx_inner, target_path, &module_path)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -246,8 +264,8 @@ fn load_module_from_content<'a>(
|
||||||
let wrapper =
|
let wrapper =
|
||||||
format!("(function(exports, require, module, __filename, __dirname) {{ {contents} }})");
|
format!("(function(exports, require, module, __filename, __dirname) {{ {contents} }})");
|
||||||
|
|
||||||
let module_func = ctx.eval::<Function, _>(wrapper)?;
|
let module_func = ctx.eval::<Function<'js>, _>(wrapper)?;
|
||||||
module_func.call::<_, Value>((exports, require, module_obj.clone(), filename, dirname))?;
|
module_func.call::<_, Value<'js>>((exports, require, module_obj.clone(), filename, dirname))?;
|
||||||
|
|
||||||
module_obj.get("exports")
|
module_obj.get("exports")
|
||||||
}
|
}
|
||||||
|
|
@ -261,15 +279,16 @@ pub fn execute_native_runtime(grammar_path: &Path) -> JSResult<String> {
|
||||||
let context = Context::full(&runtime)?;
|
let context = Context::full(&runtime)?;
|
||||||
|
|
||||||
let resolver = FileResolver::default()
|
let resolver = FileResolver::default()
|
||||||
|
.with_path("./node_modules")
|
||||||
.with_path("./")
|
.with_path("./")
|
||||||
.with_pattern("{}.mjs");
|
.with_pattern("{}.mjs");
|
||||||
let loader = ScriptLoader::default().with_extension("mjs");
|
let loader = ScriptLoader::default().with_extension("mjs");
|
||||||
runtime.set_loader(resolver, loader);
|
runtime.set_loader(resolver, loader);
|
||||||
|
|
||||||
let cwd = std::env::current_dir()?;
|
let cwd = std::env::current_dir().map_err(|e| JSError::IO(IoError::new(&e, None)))?;
|
||||||
let relative_path = pathdiff::diff_paths(grammar_path, &cwd)
|
let relative_path = pathdiff::diff_paths(grammar_path, &cwd)
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
.ok_or_else(|| JSError::IO("Failed to get relative path".to_string()))?;
|
.ok_or(JSError::RelativePath)?;
|
||||||
|
|
||||||
context.with(|ctx| -> JSResult<String> {
|
context.with(|ctx| -> JSResult<String> {
|
||||||
let globals = ctx.globals();
|
let globals = ctx.globals();
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ macro_rules! add {
|
||||||
|
|
||||||
macro_rules! add_whitespace {
|
macro_rules! add_whitespace {
|
||||||
($this:tt) => {{
|
($this:tt) => {{
|
||||||
|
// 4 bytes per char, 2 spaces per indent level
|
||||||
|
$this.buffer.reserve(4 * 2 * $this.indent_level);
|
||||||
for _ in 0..$this.indent_level {
|
for _ in 0..$this.indent_level {
|
||||||
write!(&mut $this.buffer, " ").unwrap();
|
write!(&mut $this.buffer, " ").unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -688,13 +690,14 @@ impl Generator {
|
||||||
flat_field_map.push((field_name.clone(), *location));
|
flat_field_map.push((field_name.clone(), *location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let field_map_len = flat_field_map.len();
|
||||||
field_map_ids.push((
|
field_map_ids.push((
|
||||||
self.get_field_map_id(
|
self.get_field_map_id(
|
||||||
flat_field_map.clone(),
|
flat_field_map,
|
||||||
&mut flat_field_maps,
|
&mut flat_field_maps,
|
||||||
&mut next_flat_field_map_index,
|
&mut next_flat_field_map_index,
|
||||||
),
|
),
|
||||||
flat_field_map.len(),
|
field_map_len,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -962,10 +965,7 @@ impl Generator {
|
||||||
large_char_set_ix = Some(char_set_ix);
|
large_char_set_ix = Some(char_set_ix);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut line_break = "\n".to_string();
|
let line_break = format!("\n{}", " ".repeat(self.indent_level + 2));
|
||||||
for _ in 0..self.indent_level + 2 {
|
|
||||||
line_break.push_str(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_positive_condition = large_char_set_ix.is_some() || !asserted_chars.is_empty();
|
let has_positive_condition = large_char_set_ix.is_some() || !asserted_chars.is_empty();
|
||||||
let has_negative_condition = !negated_chars.is_empty();
|
let has_negative_condition = !negated_chars.is_empty();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::BTreeMap, fmt};
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use smallbitvec::SmallBitVec;
|
use smallbitvec::SmallBitVec;
|
||||||
|
|
@ -34,7 +34,7 @@ pub enum Precedence {
|
||||||
Name(String),
|
Name(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AliasMap = HashMap<Symbol, Alias>;
|
pub type AliasMap = BTreeMap<Symbol, Alias>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
|
||||||
pub struct MetadataParams {
|
pub struct MetadataParams {
|
||||||
|
|
@ -60,15 +60,15 @@ pub enum Rule {
|
||||||
Pattern(String, String),
|
Pattern(String, String),
|
||||||
NamedSymbol(String),
|
NamedSymbol(String),
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
Choice(Vec<Rule>),
|
Choice(Vec<Self>),
|
||||||
Metadata {
|
Metadata {
|
||||||
params: MetadataParams,
|
params: MetadataParams,
|
||||||
rule: Box<Rule>,
|
rule: Box<Self>,
|
||||||
},
|
},
|
||||||
Repeat(Box<Rule>),
|
Repeat(Box<Self>),
|
||||||
Seq(Vec<Rule>),
|
Seq(Vec<Self>),
|
||||||
Reserved {
|
Reserved {
|
||||||
rule: Box<Rule>,
|
rule: Box<Self>,
|
||||||
context_name: String,
|
context_name: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ struct HighlightIterLayer<'a> {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct _QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
|
pub struct _QueryCaptures<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
|
||||||
ptr: *mut ffi::TSQueryCursor,
|
ptr: *mut ffi::TSQueryCursor,
|
||||||
query: &'query Query,
|
query: &'query Query,
|
||||||
text_provider: T,
|
text_provider: T,
|
||||||
|
|
@ -225,7 +225,7 @@ impl<'tree> _QueryMatch<'_, 'tree> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
|
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
|
||||||
for _QueryCaptures<'query, 'tree, T, I>
|
for _QueryCaptures<'query, 'tree, T, I>
|
||||||
{
|
{
|
||||||
type Item = (QueryMatch<'query, 'tree>, usize);
|
type Item = (QueryMatch<'query, 'tree>, usize);
|
||||||
|
|
@ -344,11 +344,13 @@ impl HighlightConfiguration {
|
||||||
locals_query: &str,
|
locals_query: &str,
|
||||||
) -> Result<Self, QueryError> {
|
) -> Result<Self, QueryError> {
|
||||||
// Concatenate the query strings, keeping track of the start offset of each section.
|
// Concatenate the query strings, keeping track of the start offset of each section.
|
||||||
let mut query_source = String::new();
|
let mut query_source = String::with_capacity(
|
||||||
|
injection_query.len() + locals_query.len() + highlights_query.len(),
|
||||||
|
);
|
||||||
query_source.push_str(injection_query);
|
query_source.push_str(injection_query);
|
||||||
let locals_query_offset = query_source.len();
|
let locals_query_offset = injection_query.len();
|
||||||
query_source.push_str(locals_query);
|
query_source.push_str(locals_query);
|
||||||
let highlights_query_offset = query_source.len();
|
let highlights_query_offset = injection_query.len() + locals_query.len();
|
||||||
query_source.push_str(highlights_query);
|
query_source.push_str(highlights_query);
|
||||||
|
|
||||||
// Construct a single query by concatenating the three query strings, but record the
|
// Construct a single query by concatenating the three query strings, but record the
|
||||||
|
|
@ -592,6 +594,7 @@ impl<'a> HighlightIterLayer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
|
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
|
||||||
// prevents them from being moved. But both of these values are really just
|
// prevents them from being moved. But both of these values are really just
|
||||||
// pointers, so it's actually ok to move them.
|
// pointers, so it's actually ok to move them.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tree-sitter-language"
|
name = "tree-sitter-language"
|
||||||
description = "The tree-sitter Language type, used by the library and by language implementations"
|
description = "The tree-sitter Language type, used by the library and by language implementations"
|
||||||
version = "0.1.4"
|
version = "0.1.7"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
rust-version = "1.77"
|
rust-version = "1.77"
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,15 @@ typedef long unsigned int size_t;
|
||||||
|
|
||||||
typedef long unsigned int uintptr_t;
|
typedef long unsigned int uintptr_t;
|
||||||
|
|
||||||
#define UINT16_MAX 65535
|
#define INT8_MAX 127
|
||||||
|
#define INT16_MAX 32767
|
||||||
|
#define INT32_MAX 2147483647L
|
||||||
|
#define INT64_MAX 9223372036854775807LL
|
||||||
|
|
||||||
|
#define UINT8_MAX 255
|
||||||
|
#define UINT16_MAX 65535
|
||||||
#define UINT32_MAX 4294967295U
|
#define UINT32_MAX 4294967295U
|
||||||
|
#define UINT64_MAX 18446744073709551615ULL
|
||||||
|
|
||||||
#if defined(__wasm32__)
|
#if defined(__wasm32__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,6 @@ void *memset(void *dst, int value, size_t count);
|
||||||
|
|
||||||
int strncmp(const char *left, const char *right, size_t n);
|
int strncmp(const char *left, const char *right, size_t n);
|
||||||
|
|
||||||
|
size_t strlen(const char *str);
|
||||||
|
|
||||||
#endif // TREE_SITTER_WASM_STRING_H_
|
#endif // TREE_SITTER_WASM_STRING_H_
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool left_justify; // -
|
bool left_justify; // -
|
||||||
|
|
@ -105,12 +106,6 @@ static int ptr_to_str(void *ptr, char *buffer) {
|
||||||
return 2 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t strlen(const char *str) {
|
|
||||||
const char *s = str;
|
|
||||||
while (*s) s++;
|
|
||||||
return s - str;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *strncpy(char *dest, const char *src, size_t n) {
|
char *strncpy(char *dest, const char *src, size_t n) {
|
||||||
char *d = dest;
|
char *d = dest;
|
||||||
const char *s = src;
|
const char *s = src;
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,9 @@ int strncmp(const char *left, const char *right, size_t n) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t strlen(const char *str) {
|
||||||
|
const char *s = str;
|
||||||
|
while (*s) s++;
|
||||||
|
return s - str;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ wasm = ["tree-sitter/wasm"]
|
||||||
default = ["tree-sitter-highlight", "tree-sitter-tags"]
|
default = ["tree-sitter-highlight", "tree-sitter-tags"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
|
||||||
cc.workspace = true
|
cc.workspace = true
|
||||||
etcetera.workspace = true
|
etcetera.workspace = true
|
||||||
fs4.workspace = true
|
fs4.workspace = true
|
||||||
|
|
@ -41,6 +40,7 @@ semver.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
||||||
tree-sitter = { workspace = true }
|
tree-sitter = { workspace = true }
|
||||||
tree-sitter-highlight = { workspace = true, optional = true }
|
tree-sitter-highlight = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
4.0.12
|
4.0.15
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
1
crates/loader/wasi-sdk-version
Normal file
1
crates/loader/wasi-sdk-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
29.0
|
||||||
|
|
@ -313,6 +313,7 @@ impl TagsContext {
|
||||||
)
|
)
|
||||||
.ok_or(Error::Cancelled)?;
|
.ok_or(Error::Cancelled)?;
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
// The `matches` iterator borrows the `Tree`, which prevents it from being
|
// The `matches` iterator borrows the `Tree`, which prevents it from being
|
||||||
// moved. But the tree is really just a pointer, so it's actually ok to
|
// moved. But the tree is really just a pointer, so it's actually ok to
|
||||||
// move it.
|
// move it.
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,13 @@ anstyle.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
bindgen = { version = "0.72.0" }
|
bindgen = { version = "0.72.0" }
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
etcetera.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
semver.workspace = true
|
semver.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
tree-sitter-cli = { path = "../cli/" }
|
||||||
|
tree-sitter-loader = { path = "../loader/" }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
notify-debouncer-full = "0.6.0"
|
notify-debouncer-full = "0.6.0"
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue