mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:26:23 -04:00
Compare commits
No commits in common. "master" and "v0.26.0-pre" have entirely different histories.
master
...
v0.26.0-pr
|
|
@ -1,2 +1,6 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
|
||||
[env]
|
||||
# See: https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993
|
||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||
|
|
|
|||
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
.git
|
||||
5
.envrc
5
.envrc
|
|
@ -1,4 +1 @@
|
|||
source_up_if_exists
|
||||
if [ -z "${IN_NIX_SHELL:-}" ]; then
|
||||
use flake .
|
||||
fi
|
||||
use flake
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -2,14 +2,6 @@ name: Bug Report
|
|||
description: Report a problem
|
||||
type: Bug
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: AI Policy
|
||||
description: Review our [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy).
|
||||
options:
|
||||
- label: I have read the AI Policy and this issue complies with it.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Problem"
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -2,14 +2,6 @@ name: Feature request
|
|||
description: Request an enhancement
|
||||
type: Feature
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: AI Policy
|
||||
description: Review our [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy).
|
||||
options:
|
||||
- label: I have read the AI Policy and this issue complies with it.
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
|
|
|
|||
6
.github/actions/cache/action.yml
vendored
6
.github/actions/cache/action.yml
vendored
|
|
@ -10,7 +10,7 @@ outputs:
|
|||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
|
|
@ -21,9 +21,5 @@ runs:
|
|||
'lib/src/parser.h',
|
||||
'lib/src/array.h',
|
||||
'lib/src/alloc.h',
|
||||
'lib/src/wasm-stdlib/external_scanner_stdlib.h',
|
||||
'crates/loader/wasi-sdk-version',
|
||||
'crates/loader/binaryen-version',
|
||||
'test/fixtures/grammars/*/**/src/*.c',
|
||||
'test/fixtures/fixtures.json',
|
||||
'.github/actions/cache/action.yml') }}
|
||||
|
|
|
|||
20
.github/cliff.toml
vendored
20
.github/cliff.toml
vendored
|
|
@ -43,16 +43,16 @@ commit_preprocessors = [
|
|||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ group = "<!-- 0 -->Breaking", message = "!:" },
|
||||
{ group = "<!-- 1 -->Features", message = "^feat" },
|
||||
{ group = "<!-- 2 -->Bug Fixes", message = "^fix" },
|
||||
{ group = "<!-- 3 -->Performance", message = "^perf" },
|
||||
{ group = "<!-- 4 -->Documentation", message = "^doc" },
|
||||
{ group = "<!-- 5 -->Refactor", message = "^refactor" },
|
||||
{ group = "<!-- 6 -->Testing", message = "^test" },
|
||||
{ group = "<!-- 7 -->Build System and CI", message = "^build" },
|
||||
{ group = "<!-- 7 -->Build System and CI", message = "^ci" },
|
||||
{ group = "<!-- 8 -->Other", message = ".*" },
|
||||
{ message = "!:", group = "<!-- 0 -->Breaking" },
|
||||
{ message = "^feat", group = "<!-- 1 -->Features" },
|
||||
{ message = "^fix", group = "<!-- 2 -->Bug Fixes" },
|
||||
{ message = "^perf", group = "<!-- 3 -->Performance" },
|
||||
{ message = "^doc", group = "<!-- 4 -->Documentation" },
|
||||
{ message = "^refactor", group = "<!-- 5 -->Refactor" },
|
||||
{ message = "^test", group = "<!-- 6 -->Testing" },
|
||||
{ message = "^build", group = "<!-- 7 -->Build System and CI" },
|
||||
{ message = "^ci", group = "<!-- 7 -->Build System and CI" },
|
||||
{ message = ".*", group = "<!-- 8 -->Other" },
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
|
|
|
|||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
|
|
@ -4,8 +4,6 @@ updates:
|
|||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 3
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
labels:
|
||||
|
|
@ -14,16 +12,10 @@ updates:
|
|||
groups:
|
||||
cargo:
|
||||
patterns: ["*"]
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 3
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
labels:
|
||||
|
|
@ -32,17 +24,13 @@ updates:
|
|||
groups:
|
||||
actions:
|
||||
patterns: ["*"]
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
versioning-strategy: increase
|
||||
directories:
|
||||
- "/crates/npm"
|
||||
- "/crates/eslint"
|
||||
- "/lib/binding_web"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 3
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
labels:
|
||||
|
|
|
|||
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
|
|
@ -1,6 +0,0 @@
|
|||
### AI Policy
|
||||
|
||||
- [ ] I have read the [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy) and this PR complies with it.
|
||||
- [ ] If AI tools were used: I have disclosed the tool and extent of usage below.
|
||||
|
||||
<!-- If you used AI tools, state which tool and how it was used. Delete this section if not applicable. -->
|
||||
3
.github/scripts/cross.sh
vendored
Executable file
3
.github/scripts/cross.sh
vendored
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash -eu
|
||||
|
||||
exec docker run --rm -v /home/runner:/home/runner -w "$PWD" "$CROSS_IMAGE" "$@"
|
||||
9
.github/scripts/make.sh
vendored
Executable file
9
.github/scripts/make.sh
vendored
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash -eu
|
||||
|
||||
tree_sitter="$ROOT"/target/"$TARGET"/release/tree-sitter
|
||||
|
||||
if [[ $BUILD_CMD == cross ]]; then
|
||||
cross.sh make CC="$CC" AR="$AR" "$@"
|
||||
else
|
||||
exec make "$@"
|
||||
fi
|
||||
16
.github/scripts/reviewers_remove.js
vendored
Normal file
16
.github/scripts/reviewers_remove.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
module.exports = async ({ github, context }) => {
|
||||
const requestedReviewers = await github.rest.pulls.listRequestedReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
const reviewers = requestedReviewers.data.users.map((e) => e.login);
|
||||
|
||||
github.rest.pulls.removeRequestedReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
reviewers: reviewers,
|
||||
});
|
||||
};
|
||||
9
.github/scripts/tree-sitter.sh
vendored
Executable file
9
.github/scripts/tree-sitter.sh
vendored
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash -eu
|
||||
|
||||
tree_sitter="$ROOT"/target/"$TARGET"/release/tree-sitter
|
||||
|
||||
if [[ $BUILD_CMD == cross ]]; then
|
||||
cross.sh "$CROSS_RUNNER" "$tree_sitter" "$@"
|
||||
else
|
||||
exec "$tree_sitter" "$@"
|
||||
fi
|
||||
35
.github/scripts/wasm_stdlib.js
vendored
35
.github/scripts/wasm_stdlib.js
vendored
|
|
@ -1,35 +0,0 @@
|
|||
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 files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const changedFiles = files.map(file => file.filename);
|
||||
|
||||
const wasmStdLibSources = [
|
||||
'lib/src/wasm-stdlib/external_scanner_allocator.c',
|
||||
'lib/src/wasm-stdlib/imports.txt',
|
||||
'lib/src/wasm-stdlib/libc.c',
|
||||
'lib/src/wasm-stdlib/stdio.c'
|
||||
];
|
||||
const dirChanged = changedFiles.some(file =>
|
||||
wasmStdLibSources.includes(file) ||
|
||||
file.startsWith('lib/src/wasm-stdlib/libc/ctype/') ||
|
||||
file.startsWith('lib/src/wasm-stdlib/libc/string/')
|
||||
);
|
||||
|
||||
if (!dirChanged) return;
|
||||
|
||||
const wasmStdLibHeader = 'lib/src/wasm-stdlib/external_scanner_stdlib.h';
|
||||
const requiredChanged = changedFiles.includes(wasmStdLibHeader);
|
||||
|
||||
if (!requiredChanged) core.setFailed(`Changes detected in the Wasm stdlib sources but ${wasmStdLibHeader} was not modified.`);
|
||||
};
|
||||
9
.github/workflows/backport.yml
vendored
9
.github/workflows/backport.yml
vendored
|
|
@ -14,20 +14,17 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Create app token
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.BACKPORT_APP }}
|
||||
private-key: ${{ secrets.BACKPORT_KEY }}
|
||||
|
||||
- name: Create backport PR
|
||||
uses: korthout/backport-action@v4.6.0
|
||||
uses: korthout/backport-action@v3
|
||||
with:
|
||||
pull_title: "${pull_title}"
|
||||
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
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
|
|||
326
.github/workflows/build.yml
vendored
326
.github/workflows/build.yml
vendored
|
|
@ -1,5 +1,10 @@
|
|||
name: Build & Test
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-D warnings"
|
||||
CROSS_DEBUG: 1
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
|
|
@ -26,46 +31,39 @@ jobs:
|
|||
- windows-x86
|
||||
- macos-arm64
|
||||
- macos-x64
|
||||
- illumos-x64
|
||||
- wasm32
|
||||
|
||||
include:
|
||||
# When adding a new `target`:
|
||||
# 1. Define a new platform alias above
|
||||
# 2. Add a new record to the matrix map in `crates/cli/npm/install.js`
|
||||
# 3. Consider adding the mapping at the end of 'crates/cli/Cargo.toml' for cargo-binstall support
|
||||
- { platform: linux-arm64 , target: aarch64-unknown-linux-gnu , os: ubuntu-24.04-arm }
|
||||
- { platform: linux-arm , target: armv7-unknown-linux-gnueabihf , os: ubuntu-24.04-arm }
|
||||
- { platform: linux-x64 , target: x86_64-unknown-linux-gnu , os: ubuntu-24.04 }
|
||||
- { platform: linux-x86 , target: i686-unknown-linux-gnu , os: ubuntu-24.04 }
|
||||
- { platform: linux-powerpc64 , target: powerpc64-unknown-linux-gnu , os: ubuntu-24.04 }
|
||||
- { platform: windows-arm64 , target: aarch64-pc-windows-msvc , os: windows-11-arm }
|
||||
- { platform: windows-x64 , target: x86_64-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-x64 , target: x86_64-apple-darwin , os: macos-15-intel }
|
||||
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-24.04 }
|
||||
# 2. Add a new record to the matrix map in `cli/npm/install.js`
|
||||
- { platform: linux-arm64 , target: aarch64-unknown-linux-gnu , os: ubuntu-24.04-arm , features: wasm }
|
||||
- { platform: linux-arm , target: armv7-unknown-linux-gnueabihf , os: ubuntu-latest , use-cross: true }
|
||||
- { platform: linux-x64 , target: x86_64-unknown-linux-gnu , os: ubuntu-22.04 , features: wasm }
|
||||
- { platform: linux-x86 , target: i686-unknown-linux-gnu , os: ubuntu-latest , use-cross: true }
|
||||
- { platform: linux-powerpc64 , target: powerpc64-unknown-linux-gnu , os: ubuntu-latest , use-cross: true }
|
||||
- { platform: windows-arm64 , target: aarch64-pc-windows-msvc , os: windows-latest }
|
||||
- { platform: windows-x64 , target: x86_64-pc-windows-msvc , os: windows-latest , features: wasm }
|
||||
- { platform: windows-x86 , target: i686-pc-windows-msvc , os: windows-latest }
|
||||
- { platform: macos-arm64 , target: aarch64-apple-darwin , os: macos-latest , features: wasm }
|
||||
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-13 , features: wasm }
|
||||
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-latest , no-run: true }
|
||||
|
||||
# illumos is not supported OOTB, it runs in a vm
|
||||
- { platform: illumos-x64 , target: x86_64-unknown-illumos , os: ubuntu-24.04 , vm: true , no-run: true }
|
||||
# Cross compilers for C library
|
||||
- { platform: linux-arm64 , cc: aarch64-linux-gnu-gcc , ar: aarch64-linux-gnu-ar }
|
||||
- { platform: linux-arm , cc: arm-linux-gnueabihf-gcc , ar: arm-linux-gnueabihf-ar }
|
||||
- { platform: linux-x86 , cc: i686-linux-gnu-gcc , ar: i686-linux-gnu-ar }
|
||||
- { platform: linux-powerpc64 , cc: powerpc64-linux-gnu-gcc , ar: powerpc64-linux-gnu-ar }
|
||||
|
||||
# Extra features
|
||||
- { platform: linux-arm64 , features: wasm }
|
||||
- { platform: linux-x64 , features: wasm , run-wasm-test: true }
|
||||
- { platform: macos-arm64 , features: wasm , run-wasm-test: true }
|
||||
- { platform: macos-x64 , features: wasm }
|
||||
# Prevent race condition (see #2041)
|
||||
- { platform: windows-x64 , rust-test-threads: 1 }
|
||||
- { platform: windows-x86 , rust-test-threads: 1 }
|
||||
|
||||
# Cross-compilation
|
||||
- { platform: linux-arm , cross: true }
|
||||
- { platform: linux-x86 , cross: true }
|
||||
- { platform: linux-powerpc64 , cross: true }
|
||||
|
||||
# Compile-only
|
||||
- { platform: wasm32 , no-run: true }
|
||||
# Can't natively run CLI on Github runner's host
|
||||
- { platform: windows-arm64 , no-run: true }
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -D warnings
|
||||
BUILD_CMD: cargo
|
||||
SUFFIX: ${{ contains(matrix.target, 'windows') && '.exe' || '' }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
@ -73,83 +71,101 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up cross-compilation
|
||||
if: matrix.cross
|
||||
run: |
|
||||
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
|
||||
|
||||
- name: Get emscripten version
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
- name: Read Emscripten version
|
||||
run: printf 'EMSCRIPTEN_VERSION=%s\n' "$(<crates/loader/emscripten-version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Emscripten SDK
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: emsdk
|
||||
key: emsdk-${{ env.EMSCRIPTEN_VERSION }}-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Install Emscripten
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
run: |
|
||||
if [[ ! -d emsdk ]]; then
|
||||
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
|
||||
fi
|
||||
cd emsdk
|
||||
./emsdk install ${{ env.EMSCRIPTEN_VERSION }}
|
||||
./emsdk activate ${{ env.EMSCRIPTEN_VERSION }}
|
||||
echo "$PWD" >> "$GITHUB_PATH"
|
||||
echo "$PWD/upstream/emscripten" >> "$GITHUB_PATH"
|
||||
echo "EMSDK=$PWD" >> "$GITHUB_ENV"
|
||||
echo "EMSDK_NODE=$PWD/node/$(ls node)/bin/node" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Node.js
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
uses: actions/setup-node@v7.0.0
|
||||
if: ${{ !matrix.no-run && !matrix.use-cross }}
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: lib/binding_web/package-lock.json
|
||||
version: ${{ env.EMSCRIPTEN_VERSION }}
|
||||
|
||||
- name: Set up Rust
|
||||
if: ${{ !matrix.vm }}
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install Rust Wasm test target
|
||||
if: matrix.run-wasm-test
|
||||
run: rustup toolchain install nightly --profile minimal --component rust-src
|
||||
|
||||
- name: Install cross-compilation toolchain
|
||||
if: matrix.cross
|
||||
- name: Install cross
|
||||
if: ${{ matrix.use-cross }}
|
||||
run: |
|
||||
sudo apt-get update -qy
|
||||
if [[ $PLATFORM == linux-arm ]]; then
|
||||
sudo apt-get install -qy {binutils,gcc}-arm-linux-gnueabihf qemu-user
|
||||
elif [[ $PLATFORM == linux-x86 ]]; then
|
||||
sudo apt-get install -qy {binutils,gcc}-i686-linux-gnu
|
||||
elif [[ $PLATFORM == linux-powerpc64 ]]; then
|
||||
sudo apt-get install -qy {binutils,gcc}-powerpc64-linux-gnu qemu-user
|
||||
if [ ! -x "$(command -v cross)" ]; then
|
||||
# TODO: Remove 'RUSTFLAGS=""' once https://github.com/cross-rs/cross/issues/1561 is resolved
|
||||
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross
|
||||
fi
|
||||
|
||||
- name: Configure cross
|
||||
if: ${{ matrix.use-cross }}
|
||||
run: |
|
||||
printf '%s\n' > Cross.toml \
|
||||
'[target.${{ matrix.target }}]' \
|
||||
'image = "ghcr.io/cross-rs/${{ matrix.target }}:edge"' \
|
||||
'[build]' \
|
||||
'pre-build = [' \
|
||||
' "dpkg --add-architecture $CROSS_DEB_ARCH",' \
|
||||
' "curl -fsSL https://deb.nodesource.com/setup_22.x | bash -",' \
|
||||
' "apt-get update && apt-get -y install libssl-dev nodejs"' \
|
||||
']'
|
||||
cat - Cross.toml <<< 'Cross.toml:'
|
||||
printf '%s\n' >> $GITHUB_ENV \
|
||||
"CROSS_CONFIG=$PWD/Cross.toml" \
|
||||
"CROSS_IMAGE=ghcr.io/cross-rs/${{ matrix.target }}:edge"
|
||||
|
||||
- name: Set up environment
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
RUST_TEST_THREADS: ${{ matrix.rust-test-threads }}
|
||||
USE_CROSS: ${{ matrix.use-cross }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
CC: ${{ matrix.cc }}
|
||||
AR: ${{ matrix.ar }}
|
||||
run: |
|
||||
PATH="$PWD/.github/scripts:$PATH"
|
||||
printf '%s/.github/scripts\n' "$PWD" >> $GITHUB_PATH
|
||||
|
||||
printf '%s\n' >> $GITHUB_ENV \
|
||||
'TREE_SITTER=tree-sitter.sh' \
|
||||
"TARGET=$TARGET" \
|
||||
"ROOT=$PWD"
|
||||
|
||||
[[ -n $RUST_TEST_THREADS ]] && \
|
||||
printf 'RUST_TEST_THREADS=%s\n' "$RUST_TEST_THREADS" >> $GITHUB_ENV
|
||||
|
||||
[[ -n $CC ]] && printf 'CC=%s\n' "$CC" >> $GITHUB_ENV
|
||||
[[ -n $AR ]] && printf 'AR=%s\n' "$AR" >> $GITHUB_ENV
|
||||
|
||||
if [[ $USE_CROSS == true ]]; then
|
||||
printf 'BUILD_CMD=cross\n' >> $GITHUB_ENV
|
||||
runner=$(cross.sh bash -c "env | sed -n 's/^CARGO_TARGET_.*_RUNNER=//p'")
|
||||
[[ -n $runner ]] && printf 'CROSS_RUNNER=%s\n' "$runner" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# TODO: Remove RUSTFLAGS="--cap-lints allow" once we use a wasmtime release that addresses
|
||||
# the `mismatched-lifetime-syntaxes` lint
|
||||
- name: Build wasmtime library (Windows x64 MSYS2)
|
||||
if: ${{ !matrix.use-cross && contains(matrix.features, 'wasm') && matrix.platform == 'windows-x64' }}
|
||||
run: |
|
||||
mkdir -p target
|
||||
WASMTIME_VERSION=$(cargo metadata --format-version=1 --locked --features wasm | \
|
||||
jq -r '.packages[] | select(.name == "wasmtime-c-api-impl") | .version')
|
||||
curl -LSs "$WASMTIME_REPO/archive/refs/tags/v${WASMTIME_VERSION}.tar.gz" | tar xzf - -C target
|
||||
cd target/wasmtime-${WASMTIME_VERSION}
|
||||
cmake -S crates/c-api -B target/c-api \
|
||||
-DCMAKE_INSTALL_PREFIX="$PWD/artifacts" \
|
||||
-DWASMTIME_DISABLE_ALL_FEATURES=ON \
|
||||
-DWASMTIME_FEATURE_CRANELIFT=ON \
|
||||
-DWASMTIME_TARGET='x86_64-pc-windows-gnu'
|
||||
cmake --build target/c-api && cmake --install target/c-api
|
||||
printf 'CMAKE_PREFIX_PATH=%s\n' "$PWD/artifacts" >> $GITHUB_ENV
|
||||
env:
|
||||
WASMTIME_REPO: https://github.com/bytecodealliance/wasmtime
|
||||
RUSTFLAGS: "--cap-lints allow"
|
||||
|
||||
- name: Install MinGW and Clang (Windows x64 MSYS2)
|
||||
if: matrix.platform == 'windows-x64'
|
||||
if: ${{ matrix.platform == 'windows-x64' }}
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
update: true
|
||||
install: |
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-clang
|
||||
|
|
@ -157,7 +173,7 @@ jobs:
|
|||
mingw-w64-x86_64-cmake
|
||||
|
||||
- name: Build C library (Windows x64 MSYS2 CMake)
|
||||
if: matrix.platform == 'windows-x64'
|
||||
if: ${{ matrix.platform == 'windows-x64' }}
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -G Ninja -S . -B build/static \
|
||||
|
|
@ -167,6 +183,7 @@ jobs:
|
|||
-DTREE_SITTER_FEATURE_WASM=$WASM \
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
cmake --build build/static
|
||||
rm -rf build/static
|
||||
|
||||
cmake -G Ninja -S . -B build/shared \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
|
|
@ -175,50 +192,39 @@ jobs:
|
|||
-DTREE_SITTER_FEATURE_WASM=$WASM \
|
||||
-DCMAKE_C_COMPILER=clang
|
||||
cmake --build build/shared
|
||||
rm -rf \
|
||||
build/{static,shared} \
|
||||
"${CMAKE_PREFIX_PATH}/artifacts" \
|
||||
target/wasmtime-${WASMTIME_VERSION}
|
||||
rm -rf build/shared
|
||||
env:
|
||||
WASM: ${{ contains(matrix.features, 'wasm') && 'ON' || 'OFF' }}
|
||||
|
||||
- name: Download wasmtime C API
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
# TODO: Remove RUSTFLAGS="--cap-lints allow" once we use a wasmtime release that addresses
|
||||
# the `mismatched-lifetime-syntaxes` lint
|
||||
- name: Build wasmtime library
|
||||
if: ${{ !matrix.use-cross && contains(matrix.features, 'wasm') }}
|
||||
run: |
|
||||
mkdir -p target
|
||||
WASMTIME_VERSION=$(cargo metadata --format-version=1 --locked --features wasm | \
|
||||
jq -r '.packages[] | select(.name == "wasmtime-c-api-impl") | .version')
|
||||
case '${{ matrix.target }}' in
|
||||
x86_64-unknown-linux-gnu) WT_TARGET=x86_64-linux ;;
|
||||
aarch64-unknown-linux-gnu) WT_TARGET=aarch64-linux ;;
|
||||
x86_64-apple-darwin) WT_TARGET=x86_64-macos ;;
|
||||
aarch64-apple-darwin) WT_TARGET=aarch64-macos ;;
|
||||
esac
|
||||
curl -LSs "$WASMTIME_REPO/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WT_TARGET}-c-api.tar.xz" \
|
||||
| tar xJf - -C target
|
||||
printf 'CMAKE_PREFIX_PATH=%s\n' "$PWD/target/wasmtime-v${WASMTIME_VERSION}-${WT_TARGET}-c-api" >> $GITHUB_ENV
|
||||
curl -LSs "$WASMTIME_REPO/archive/refs/tags/v${WASMTIME_VERSION}.tar.gz" | tar xzf - -C target
|
||||
cd target/wasmtime-${WASMTIME_VERSION}
|
||||
cmake -S crates/c-api -B target/c-api \
|
||||
-DCMAKE_INSTALL_PREFIX="$PWD/artifacts" \
|
||||
-DWASMTIME_DISABLE_ALL_FEATURES=ON \
|
||||
-DWASMTIME_FEATURE_CRANELIFT=ON \
|
||||
-DWASMTIME_TARGET='${{ matrix.target }}'
|
||||
cmake --build target/c-api && cmake --install target/c-api
|
||||
printf 'CMAKE_PREFIX_PATH=%s\n' "$PWD/artifacts" >> $GITHUB_ENV
|
||||
env:
|
||||
WASMTIME_REPO: https://github.com/bytecodealliance/wasmtime
|
||||
RUSTFLAGS: "--cap-lints allow"
|
||||
|
||||
- name: Build C library (make)
|
||||
if: runner.os != 'Windows' && !matrix.vm
|
||||
run: |
|
||||
if [[ $PLATFORM == linux-arm ]]; then
|
||||
CC=arm-linux-gnueabihf-gcc; AR=arm-linux-gnueabihf-ar
|
||||
elif [[ $PLATFORM == linux-x86 ]]; then
|
||||
CC=i686-linux-gnu-gcc; AR=i686-linux-gnu-ar
|
||||
elif [[ $PLATFORM == linux-powerpc64 ]]; then
|
||||
CC=powerpc64-linux-gnu-gcc; AR=powerpc64-linux-gnu-ar
|
||||
else
|
||||
CC=gcc; AR=ar
|
||||
fi
|
||||
make -j CFLAGS="$CFLAGS" CC=$CC AR=$AR
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
run: make.sh -j CFLAGS="$CFLAGS"
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
|
||||
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
|
||||
|
||||
- name: Build C library (CMake)
|
||||
if: "!matrix.cross && !matrix.vm"
|
||||
if: ${{ !matrix.use-cross }}
|
||||
run: |
|
||||
cmake -S . -B build/static \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
|
|
@ -234,25 +240,12 @@ jobs:
|
|||
-DTREE_SITTER_FEATURE_WASM=$WASM
|
||||
cmake --build build/shared --verbose
|
||||
env:
|
||||
CC: ${{ contains(matrix.platform, 'linux') && 'clang' || '' }}
|
||||
WASM: ${{ contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test) && 'ON' || 'OFF' }}
|
||||
CC: ${{ contains(matrix.target, 'linux') && 'clang' || '' }}
|
||||
WASM: ${{ contains(matrix.features, 'wasm') && 'ON' || 'OFF' }}
|
||||
|
||||
- name: Build C library and Rust crate (illumos gmake)
|
||||
if: matrix.platform == 'illumos-x64'
|
||||
uses: vmactions/omnios-vm@v1.3.6
|
||||
with:
|
||||
release: r151056-build
|
||||
copyback: false
|
||||
prepare: |
|
||||
pkg install -q build-essential || [ $? -eq 4 ]
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
gmake -j
|
||||
cargo build -p tree-sitter
|
||||
|
||||
- name: Build Wasm library
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
- name: Build wasm library
|
||||
# No reason to build on the same Github runner hosts many times
|
||||
if: ${{ !matrix.no-run && !matrix.use-cross }}
|
||||
shell: bash
|
||||
run: |
|
||||
cd lib/binding_web
|
||||
|
|
@ -263,58 +256,57 @@ jobs:
|
|||
npm run build:debug
|
||||
|
||||
- name: Check no_std builds
|
||||
if: inputs.run-test && !matrix.no-run
|
||||
working-directory: lib
|
||||
if: ${{ !matrix.no-run && inputs.run-test }}
|
||||
shell: bash
|
||||
run: cargo check --no-default-features --target='${{ matrix.target }}'
|
||||
run: |
|
||||
cd lib
|
||||
$BUILD_CMD check --no-default-features --target=${{ matrix.target }}
|
||||
|
||||
- name: Build target
|
||||
if: "!inputs.run-test && !matrix.vm"
|
||||
run: cargo build --release --target='${{ matrix.target }}' --features='${{ (matrix.run-wasm-test || !inputs.run-test) && matrix.features || '' }}' $PACKAGE
|
||||
env:
|
||||
PACKAGE: ${{ matrix.platform == 'wasm32' && '-p tree-sitter' || '' }}
|
||||
run: |
|
||||
PACKAGE=""
|
||||
if [[ "${{ matrix.target }}" == "wasm32-unknown-unknown" ]]; then
|
||||
PACKAGE="-p tree-sitter"
|
||||
fi
|
||||
$BUILD_CMD build --release --target=${{ matrix.target }} --features=${{ matrix.features }} $PACKAGE
|
||||
|
||||
- name: Cache fixtures
|
||||
id: cache
|
||||
if: inputs.run-test && !matrix.no-run
|
||||
if: ${{ !matrix.no-run && inputs.run-test }}
|
||||
uses: ./.github/actions/cache
|
||||
|
||||
- name: Fetch fixtures
|
||||
if: inputs.run-test && !matrix.no-run
|
||||
run: cargo run -p xtask --target='${{ matrix.target }}' -- fetch-fixtures
|
||||
if: ${{ !matrix.no-run && inputs.run-test }}
|
||||
run: $BUILD_CMD run -p xtask --target=${{ matrix.target }} -- fetch-fixtures
|
||||
|
||||
- name: Generate fixtures
|
||||
if: inputs.run-test && !matrix.no-run && steps.cache.outputs.cache-hit != 'true'
|
||||
run: cargo run -p xtask --target='${{ matrix.target }}' -- generate-fixtures
|
||||
if: ${{ !matrix.no-run && inputs.run-test && steps.cache.outputs.cache-hit != 'true' }}
|
||||
run: $BUILD_CMD run -p xtask --target=${{ matrix.target }} -- generate-fixtures
|
||||
|
||||
- name: Generate Wasm fixtures
|
||||
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test && steps.cache.outputs.cache-hit != 'true'
|
||||
run: cargo run -p xtask --target='${{ matrix.target }}' -- generate-fixtures --wasm
|
||||
if: ${{ !matrix.no-run && !matrix.use-cross && inputs.run-test && steps.cache.outputs.cache-hit != 'true' }}
|
||||
run: $BUILD_CMD run -p xtask --target=${{ matrix.target }} -- generate-fixtures --wasm
|
||||
|
||||
- name: Run main tests
|
||||
if: inputs.run-test && !matrix.no-run
|
||||
run: cargo test --workspace --target='${{ matrix.target }}' --features='${{ (matrix.run-wasm-test || !inputs.run-test) && matrix.features || '' }}'
|
||||
if: ${{ !matrix.no-run && inputs.run-test }}
|
||||
run: $BUILD_CMD test --target=${{ matrix.target }} --features=${{ matrix.features }}
|
||||
|
||||
- name: Run Wasm tests
|
||||
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test
|
||||
run: cargo run -p xtask --target='${{ matrix.target }}' -- test-wasm
|
||||
|
||||
- name: Run Rust Wasm web test
|
||||
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test
|
||||
run: cargo run -p xtask --target='${{ matrix.target }}' -- test-rust-wasm-web
|
||||
- name: Run wasm tests
|
||||
if: ${{ !matrix.no-run && !matrix.use-cross && inputs.run-test }}
|
||||
run: $BUILD_CMD run -p xtask --target=${{ matrix.target }} -- test-wasm
|
||||
|
||||
- name: Upload CLI artifact
|
||||
if: "!inputs.run-test && !matrix.no-run"
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ matrix.platform != 'wasm32' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tree-sitter.${{ matrix.platform }}
|
||||
path: target/${{ matrix.target }}/release/tree-sitter${{ contains(matrix.target, 'windows') && '.exe' || '' }}
|
||||
path: target/${{ matrix.target }}/release/tree-sitter${{ env.SUFFIX }}
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Wasm artifacts
|
||||
if: "!inputs.run-test && matrix.platform == 'linux-x64'"
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ matrix.platform == 'linux-x64' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tree-sitter.wasm
|
||||
path: |
|
||||
|
|
|
|||
43
.github/workflows/ci.yml
vendored
43
.github/workflows/ci.yml
vendored
|
|
@ -1,14 +1,21 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-[0-9]+.[0-9]+'
|
||||
paths-ignore:
|
||||
- docs/**
|
||||
- "**/README.md"
|
||||
- CONTRIBUTING.md
|
||||
- LICENSE
|
||||
- cli/src/templates
|
||||
push:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- docs/**
|
||||
- "**/README.md"
|
||||
- CONTRIBUTING.md
|
||||
- LICENSE
|
||||
- cli/src/templates
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
|
@ -19,7 +26,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
@ -27,25 +34,13 @@ jobs:
|
|||
toolchain: stable
|
||||
components: clippy, rustfmt
|
||||
|
||||
- name: Lint Rust files
|
||||
run: make lint
|
||||
|
||||
- name: Install Taplo
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: taplo@0.10.0
|
||||
|
||||
- name: Lint TOML files
|
||||
run: make lint-toml
|
||||
|
||||
- name: Lint web files
|
||||
run: make lint-web
|
||||
- name: Lint files
|
||||
run: |
|
||||
make lint
|
||||
make lint-web
|
||||
|
||||
sanitize:
|
||||
uses: ./.github/workflows/sanitize.yml
|
||||
|
||||
build:
|
||||
uses: ./.github/workflows/build.yml
|
||||
|
||||
check-wasm-stdlib:
|
||||
uses: ./.github/workflows/wasm_stdlib.yml
|
||||
|
|
|
|||
22
.github/workflows/crate_versions.yml
vendored
22
.github/workflows/crate_versions.yml
vendored
|
|
@ -1,22 +0,0 @@
|
|||
name: Crate Versions Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-crates:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.pull_request.labels.*.name, 'ci:check release') || github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Check crates against crates.io
|
||||
uses: katyo/publish-crates@v2
|
||||
with:
|
||||
dry-run: true
|
||||
17
.github/workflows/docs.yml
vendored
17
.github/workflows/docs.yml
vendored
|
|
@ -3,7 +3,6 @@ on:
|
|||
push:
|
||||
branches: [master]
|
||||
paths: [docs/**]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
|
|
@ -16,29 +15,35 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install mdbook
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
jq_expr='.assets[] | select(.name | contains("x86_64-unknown-linux-gnu")) | .browser_download_url'
|
||||
url=$(gh api repos/rust-lang/mdbook/releases/tags/v0.5.4 --jq "$jq_expr")
|
||||
url=$(gh api repos/rust-lang/mdbook/releases/latest --jq "$jq_expr")
|
||||
mkdir mdbook
|
||||
curl -sSL "$url" | tar -xz -C mdbook
|
||||
printf '%s/mdbook\n' "$PWD" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install mdbook-admonish
|
||||
run: cargo install mdbook-admonish
|
||||
|
||||
- name: Build Book
|
||||
run: mdbook build docs
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: docs/book
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@v4
|
||||
|
|
|
|||
67
.github/workflows/nvim_ts.yml
vendored
67
.github/workflows/nvim_ts.yml
vendored
|
|
@ -3,7 +3,6 @@ name: nvim-treesitter parser tests
|
|||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**'
|
||||
- 'crates/cli/**'
|
||||
- 'crates/config/**'
|
||||
- 'crates/generate/**'
|
||||
|
|
@ -26,54 +25,34 @@ jobs:
|
|||
name: ${{ matrix.os }} - ${{ matrix.type }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim.exe' || 'nvim' }}
|
||||
NVIM_TAG: stable
|
||||
NVIM_DIR: neovim
|
||||
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim-win64\\bin\\nvim.exe' || 'nvim' }}
|
||||
NVIM_TS_DIR: nvim-treesitter
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- run: cargo build --profile optimize
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Clone Neovim
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
repository: neovim/neovim
|
||||
ref: ${{ env.NVIM_TAG }}
|
||||
path: ${{ env.NVIM_DIR }}
|
||||
|
||||
- if: runner.os != 'Windows'
|
||||
name: Setup environment (Posix)
|
||||
run: |
|
||||
echo ${{ github.workspace }}/target/optimize >> "$GITHUB_PATH"
|
||||
echo ${{ github.workspace }}/neovim/build/bin >> "$GITHUB_PATH"
|
||||
echo "VIMRUNTIME=${{ github.workspace }}/neovim/runtime" >> "$GITHUB_ENV"
|
||||
|
||||
- if: runner.os == 'Windows'
|
||||
name: Setup environment (why can't you just be normal?!)
|
||||
run: |
|
||||
${{ env.NVIM_DIR }}/.github/scripts/env.ps1
|
||||
echo ${{ github.workspace }}/target/optimize >> "$env:GITHUB_PATH"
|
||||
echo ${{ github.workspace }}/neovim/build/bin >> "$env:GITHUB_PATH"
|
||||
echo "VIMRUNTIME=${{ github.workspace }}/neovim/runtime" >> "$env:GITHUB_ENV"
|
||||
|
||||
- name: Build Neovim
|
||||
working-directory: ${{ env.NVIM_DIR }}
|
||||
run: |
|
||||
cmake -S cmake.deps -B .deps -G Ninja -D CMAKE_BUILD_TYPE=Release -D TREESITTER_URL=https://github.com/tree-sitter/tree-sitter/archive/${{ github.event.pull_request.head.sha }}.tar.gz -D DEPS_IGNORE_SHA=TRUE
|
||||
cmake --build .deps --config Release
|
||||
cmake -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release
|
||||
|
||||
- name: Clone nvim-treesitter
|
||||
uses: actions/checkout@v7.0.1
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: nvim-treesitter/nvim-treesitter
|
||||
path: ${{ env.NVIM_TS_DIR }}
|
||||
ref: main
|
||||
|
||||
- if: runner.os != 'Windows'
|
||||
run: echo ${{ github.workspace }}/target/release >> $GITHUB_PATH
|
||||
|
||||
- if: runner.os == 'Windows'
|
||||
run: echo ${{ github.workspace }}/target/release >> $env:GITHUB_PATH
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- run: cargo build --release
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Install and prepare Neovim
|
||||
run: bash ./scripts/ci-install.sh
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
|
||||
- if: matrix.type == 'generate'
|
||||
name: Generate and compile parsers
|
||||
run: $NVIM -l ./scripts/install-parsers.lua --generate --max-jobs=10
|
||||
run: $NVIM -l ./scripts/install-parsers.lua --generate --max-jobs=2
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
||||
|
|
@ -84,13 +63,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- if: "!cancelled()"
|
||||
name: Test parsers
|
||||
run: $NVIM -l ./scripts/check-parsers.lua
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
||||
- if: "!cancelled()"
|
||||
name: Test queries
|
||||
name: Check query files
|
||||
run: $NVIM -l ./scripts/check-queries.lua
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
|
|
|||
73
.github/workflows/release.yml
vendored
73
.github/workflows/release.yml
vendored
|
|
@ -1,14 +1,7 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '5 5 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Tag name for release'
|
||||
required: false
|
||||
default: nightly
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
|
|
@ -24,26 +17,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
attestations: write
|
||||
contents: write
|
||||
steps:
|
||||
- if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.inputs.tag_name }}
|
||||
run: echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- if: github.event_name == 'schedule'
|
||||
run: echo 'TAG_NAME=nightly' >> $GITHUB_ENV
|
||||
|
||||
- if: github.event_name == 'push'
|
||||
run: echo "TAG_NAME=${GITHUB_REF_NAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
|
|
@ -62,84 +42,51 @@ jobs:
|
|||
|
||||
for platform in $(cd artifacts; ls | sed 's/^tree-sitter\.//'); do
|
||||
exe=$(ls artifacts/tree-sitter.$platform/tree-sitter*)
|
||||
chmod +x $exe
|
||||
gzip --stdout --name $exe > target/tree-sitter-$platform.gz
|
||||
zip -j9 target/tree-sitter-cli-$platform.zip $exe
|
||||
done
|
||||
rm -rf artifacts
|
||||
ls -l target/
|
||||
|
||||
- name: Generate attestations
|
||||
uses: actions/attest-build-provenance@v4
|
||||
with:
|
||||
subject-path: |
|
||||
target/tree-sitter-*.gz
|
||||
target/tree-sitter-cli-*.zip
|
||||
target/web-tree-sitter.tar.gz
|
||||
|
||||
- if: env.TAG_NAME == 'nightly'
|
||||
run: |
|
||||
echo 'PRERELEASE=--prerelease' >> $GITHUB_ENV
|
||||
gh release delete nightly --yes || true
|
||||
git push https://${GITHUB_ACTOR}:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY} :nightly || true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Create release
|
||||
run: |-
|
||||
gh release create ${{ env.TAG_NAME }} $PRERELEASE \
|
||||
gh release create ${{ github.ref_name }} \
|
||||
target/tree-sitter-*.gz \
|
||||
target/tree-sitter-cli-*.zip \
|
||||
target/web-tree-sitter.tar.gz
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
crates_io:
|
||||
name: Publish packages to Crates.io
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
|
||||
runs-on: ubuntu-latest
|
||||
environment: crates
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Rust
|
||||
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
|
||||
uses: katyo/publish-crates@v2
|
||||
with:
|
||||
registry-token: ${{ steps.auth.outputs.token }}
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
npm:
|
||||
name: Publish packages to npmjs.com
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
needs: release
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
directory: [crates/cli/npm, lib/binding_web]
|
||||
directory: [cli/npm, lib/binding_web]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v7.0.0
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 20
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Set up Rust
|
||||
|
|
@ -159,3 +106,5 @@ jobs:
|
|||
- name: Publish to npmjs.com
|
||||
working-directory: ${{ matrix.directory }}
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
|
|
|||
8
.github/workflows/response.yml
vendored
8
.github/workflows/response.yml
vendored
|
|
@ -17,13 +17,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
sparse-checkout: .github/scripts/close_unresponsive.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/close_unresponsive.js')
|
||||
|
|
@ -35,13 +35,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
sparse-checkout: .github/scripts/remove_response_label.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/remove_response_label.js')
|
||||
|
|
|
|||
26
.github/workflows/reviewers_remove.yml
vendored
26
.github/workflows/reviewers_remove.yml
vendored
|
|
@ -11,21 +11,15 @@ jobs:
|
|||
remove-reviewers:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove reviewers
|
||||
uses: actions/github-script@v9
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
sparse-checkout: .github/scripts/reviewers_remove.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const requestedReviewers = await github.rest.pulls.listRequestedReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
const reviewers = requestedReviewers.data.users.map((e) => e.login);
|
||||
|
||||
github.rest.pulls.removeRequestedReviewers({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
reviewers: reviewers,
|
||||
});
|
||||
const script = require('./.github/scripts/reviewers_remove.js')
|
||||
await script({github, context})
|
||||
|
|
|
|||
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
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install UBSAN library
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libubsan1
|
||||
|
|
|
|||
4
.github/workflows/spam.yml
vendored
4
.github/workflows/spam.yml
vendored
|
|
@ -16,13 +16,13 @@ jobs:
|
|||
if: github.event.label.name == 'spam'
|
||||
steps:
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
sparse-checkout: .github/scripts/close_spam.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/close_spam.js')
|
||||
|
|
|
|||
10
.github/workflows/wasm_exports.yml
vendored
10
.github/workflows/wasm_exports.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Check Wasm Exports
|
||||
name: Check WASM Exports
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
@ -31,11 +31,11 @@ jobs:
|
|||
- name: Build C library (make)
|
||||
run: make -j CFLAGS="$CFLAGS"
|
||||
env:
|
||||
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
|
||||
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
|
||||
|
||||
- name: Build Wasm Library
|
||||
- name: Build WASM Library
|
||||
working-directory: lib/binding_web
|
||||
run: npm ci && npm run build:debug
|
||||
|
||||
- name: Check Wasm exports
|
||||
- name: Check WASM exports
|
||||
run: cargo xtask check-wasm-exports
|
||||
|
|
|
|||
19
.github/workflows/wasm_stdlib.yml
vendored
19
.github/workflows/wasm_stdlib.yml
vendored
|
|
@ -1,19 +0,0 @@
|
|||
name: Check Wasm Stdlib build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Check directory changes
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const scriptPath = `${process.env.GITHUB_WORKSPACE}/.github/scripts/wasm_stdlib.js`;
|
||||
const script = require(scriptPath);
|
||||
return script({ github, context, core });
|
||||
19
.taplo.toml
19
.taplo.toml
|
|
@ -1,19 +0,0 @@
|
|||
[formatting]
|
||||
column_width = 100
|
||||
compact_arrays = false
|
||||
reorder_inline_tables = true
|
||||
reorder_keys = true
|
||||
|
||||
[[rule]]
|
||||
include = [ "**/Cargo.toml" ]
|
||||
keys = [ "package" ]
|
||||
|
||||
[rule.formatting]
|
||||
reorder_keys = false
|
||||
|
||||
[[rule]]
|
||||
include = [ "**/Cargo.toml" ]
|
||||
keys = [ "profile" ]
|
||||
|
||||
[rule.formatting]
|
||||
reorder_keys = false
|
||||
11
.zed/settings.json
Normal file
11
.zed/settings.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"lsp": {
|
||||
"rust-analyzer": {
|
||||
"initialization_options": {
|
||||
"cargo": {
|
||||
"features": "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(tree-sitter
|
||||
VERSION "0.28.0"
|
||||
VERSION "0.26.0"
|
||||
DESCRIPTION "An incremental parsing system for programming tools"
|
||||
HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/"
|
||||
LANGUAGES C)
|
||||
|
|
@ -33,8 +33,7 @@ if(MSVC)
|
|||
else()
|
||||
target_compile_options(tree-sitter PRIVATE
|
||||
-Wall -Wextra -Wshadow -Wpedantic
|
||||
-Werror=incompatible-pointer-types
|
||||
-Werror=strict-aliasing -Wstrict-aliasing=2)
|
||||
-Werror=incompatible-pointer-types)
|
||||
endif()
|
||||
|
||||
if(TREE_SITTER_FEATURE_WASM)
|
||||
|
|
@ -82,7 +81,7 @@ set_target_properties(tree-sitter
|
|||
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
||||
DEFINE_SYMBOL "")
|
||||
|
||||
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE _BSD_SOURCE _DARWIN_C_SOURCE)
|
||||
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
|
|
|||
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
132
Cargo.toml
132
Cargo.toml
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
default-members = [ "crates/cli" ]
|
||||
default-members = ["crates/cli"]
|
||||
members = [
|
||||
"crates/cli",
|
||||
"crates/config",
|
||||
|
|
@ -14,22 +14,25 @@ members = [
|
|||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = [ "Max Brunsfeld <maxbrunsfeld@gmail.com>", "Amaan Qureshi <amaanq12@gmail.com>" ]
|
||||
categories = [ "command-line-utilities", "parsing" ]
|
||||
edition = "2024"
|
||||
version = "0.27.0"
|
||||
authors = [
|
||||
"Max Brunsfeld <maxbrunsfeld@gmail.com>",
|
||||
"Amaan Qureshi <amaanq12@gmail.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
rust-version = "1.84"
|
||||
homepage = "https://tree-sitter.github.io/tree-sitter"
|
||||
keywords = [ "incremental", "parsing" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tree-sitter/tree-sitter"
|
||||
rust-version = "1.90"
|
||||
version = "0.28.0"
|
||||
license = "MIT"
|
||||
keywords = ["incremental", "parsing"]
|
||||
categories = ["command-line-utilities", "parsing"]
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
dbg_macro = "deny"
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
todo = "deny"
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
|
||||
# The lints below are a specific subset of the pedantic+nursery lints
|
||||
# that we explicitly allow in the tree-sitter codebase because they either:
|
||||
|
|
@ -38,31 +41,52 @@ todo = "deny"
|
|||
# 2. Are unnecessary, or
|
||||
# 3. Worsen the code
|
||||
|
||||
branches_sharing_code = "allow"
|
||||
cast_lossless = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
checked_conversions = "allow"
|
||||
cognitive_complexity = "allow"
|
||||
collection_is_never_read = "allow"
|
||||
fallible_impl_from = "allow"
|
||||
fn_params_excessive_bools = "allow"
|
||||
inline_always = "allow"
|
||||
if_not_else = "allow"
|
||||
items_after_statements = "allow"
|
||||
match_wildcard_for_single_variants = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
module_name_repetitions = "allow"
|
||||
multiple_crate_versions = "allow"
|
||||
needless_for_each = "allow"
|
||||
obfuscated_if_else = "allow"
|
||||
option_if_let_else = "allow"
|
||||
or_fun_call = "allow"
|
||||
range_plus_one = "allow"
|
||||
redundant_clone = "allow"
|
||||
redundant_closure_for_method_calls = "allow"
|
||||
ref_option = "allow"
|
||||
similar_names = "allow"
|
||||
string_lit_as_bytes = "allow"
|
||||
struct_excessive_bools = "allow"
|
||||
struct_field_names = "allow"
|
||||
transmute_undefined_repr = "allow"
|
||||
too_many_lines = "allow"
|
||||
tuple_array_conversions = "allow"
|
||||
unnecessary_wraps = "allow"
|
||||
unused_self = "allow"
|
||||
used_underscore_items = "allow"
|
||||
|
||||
[workspace.lints.rust]
|
||||
mismatched_lifetime_syntaxes = "allow"
|
||||
|
||||
[profile.optimize]
|
||||
inherits = "release"
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
lto = true # Link-time optimization.
|
||||
opt-level = 3 # Optimization level 3.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
|
||||
[profile.size]
|
||||
inherits = "optimize"
|
||||
|
|
@ -70,69 +94,67 @@ opt-level = "s" # Optimize for size.
|
|||
|
||||
[profile.release-dev]
|
||||
inherits = "release"
|
||||
codegen-units = 256
|
||||
lto = false
|
||||
debug = true
|
||||
debug-assertions = true
|
||||
incremental = true
|
||||
lto = false
|
||||
overflow-checks = true
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
|
||||
[workspace.dependencies]
|
||||
ansi_colours = "1.2.3"
|
||||
anstyle = "1.0.14"
|
||||
anyhow = "1.0.102"
|
||||
bstr = "1.12.1"
|
||||
cc = "1.2.63"
|
||||
clap = { features = [
|
||||
anstyle = "1.0.11"
|
||||
anyhow = "1.0.99"
|
||||
bstr = "1.12.0"
|
||||
cc = "1.2.37"
|
||||
clap = { version = "4.5.45", features = [
|
||||
"cargo",
|
||||
"derive",
|
||||
"env",
|
||||
"help",
|
||||
"string",
|
||||
"unstable-styles",
|
||||
], version = "4.5.58" }
|
||||
clap_complete = "4.6.3"
|
||||
clap_complete_nushell = "4.5.10"
|
||||
crc32fast = "1.5.0"
|
||||
ctor = "0.6.3"
|
||||
ctrlc = { features = [ "termination" ], version = "3.5.2" }
|
||||
dialoguer = { features = [ "fuzzy-select" ], version = "0.12.0" }
|
||||
etcetera = "0.11.0"
|
||||
] }
|
||||
clap_complete = "4.5.57"
|
||||
clap_complete_nushell = "4.5.8"
|
||||
ctor = "0.2.9"
|
||||
ctrlc = { version = "3.5.0", features = ["termination"] }
|
||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||
etcetera = "0.10.0"
|
||||
fs4 = "0.12.0"
|
||||
glob = "0.3.3"
|
||||
hashbrown = { default-features = false, version = "0.17.1" }
|
||||
heck = "0.5.0"
|
||||
html-escape = "0.2.13"
|
||||
indexmap = "2.13.0"
|
||||
indoc = "2.0.7"
|
||||
libloading = "0.9.0"
|
||||
log = { features = [ "std" ], version = "0.4.30" }
|
||||
memchr = "2.8.1"
|
||||
indexmap = "2.11.1"
|
||||
indoc = "2.0.6"
|
||||
libloading = "0.8.8"
|
||||
log = { version = "0.4.28", features = ["std"] }
|
||||
memchr = "2.7.5"
|
||||
once_cell = "1.21.3"
|
||||
pretty_assertions = "1.4.1"
|
||||
rand = "0.10.1"
|
||||
regex = "1.12.3"
|
||||
regex-syntax = "0.8.9"
|
||||
rand = "0.8.5"
|
||||
regex = "1.11.2"
|
||||
regex-syntax = "0.8.6"
|
||||
rustc-hash = "2.1.1"
|
||||
schemars = "1.2.1"
|
||||
semver = { features = [ "serde" ], version = "1.0.27" }
|
||||
serde = { features = [ "derive" ], version = "1.0.228" }
|
||||
serde_json = { features = [ "preserve_order" ], version = "1.0.150" }
|
||||
semver = { version = "1.0.27", features = ["serde"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = { version = "1.0.145", features = ["preserve_order"] }
|
||||
similar = "2.7.0"
|
||||
smallbitvec = "2.6.0"
|
||||
streaming-iterator = "0.1.9"
|
||||
tempfile = "3.25.0"
|
||||
thiserror = "2.0.18"
|
||||
tempfile = "3.22.0"
|
||||
thiserror = "2.0.16"
|
||||
tiny_http = "0.12.0"
|
||||
topological-sort = "0.2.2"
|
||||
unindent = "0.2.4"
|
||||
walkdir = "2.5.0"
|
||||
wasmparser = "0.244.0"
|
||||
webbrowser = "1.2.1"
|
||||
wasmparser = "0.229.0"
|
||||
webbrowser = "1.0.5"
|
||||
|
||||
tree-sitter = { path = "./lib", version = "0.28.0" }
|
||||
tree-sitter-config = { path = "./crates/config", version = "0.28.0" }
|
||||
tree-sitter-generate = { default-features = false, path = "./crates/generate", version = "0.28.0" }
|
||||
tree-sitter-highlight = { path = "./crates/highlight", version = "0.28.0" }
|
||||
tree-sitter-loader = { path = "./crates/loader", version = "0.28.0" }
|
||||
tree-sitter-tags = { path = "./crates/tags", version = "0.28.0" }
|
||||
|
||||
tree-sitter-language = { path = "./crates/language", version = "0.1.8" }
|
||||
tree-sitter = { version = "0.27.0", path = "./lib" }
|
||||
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-config = { version = "0.27.0", path = "./crates/config" }
|
||||
tree-sitter-highlight = { version = "0.27.0", path = "./crates/highlight" }
|
||||
tree-sitter-tags = { version = "0.27.0", path = "./crates/tags" }
|
||||
|
|
|
|||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
FROM rust:1.76-buster
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD cargo test --all-features
|
||||
18
Makefile
18
Makefile
|
|
@ -1,4 +1,4 @@
|
|||
VERSION := 0.28.0
|
||||
VERSION := 0.26.0
|
||||
DESCRIPTION := An incremental parsing system for programming tools
|
||||
HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
|
||||
|
||||
|
|
@ -22,9 +22,9 @@ OBJ := $(SRC:.c=.o)
|
|||
|
||||
# define default flags, and override to append mandatory flags
|
||||
ARFLAGS := rcs
|
||||
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
|
||||
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
|
||||
override CFLAGS += -std=c11 -fPIC -fvisibility=hidden
|
||||
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_DARWIN_C_SOURCE
|
||||
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE
|
||||
override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include
|
||||
|
||||
# ABI versioning
|
||||
|
|
@ -75,10 +75,6 @@ tree-sitter.pc: lib/tree-sitter.pc.in
|
|||
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
|
||||
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
|
||||
|
||||
shared: libtree-sitter.$(SOEXT)
|
||||
|
||||
static: libtree-sitter.a
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJ) tree-sitter.pc libtree-sitter.a libtree-sitter.$(SOEXT) libtree-stitter.dll.a
|
||||
|
||||
|
|
@ -106,7 +102,7 @@ uninstall:
|
|||
'$(DESTDIR)$(PCLIBDIR)'/tree-sitter.pc
|
||||
rmdir '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter
|
||||
|
||||
.PHONY: all shared static install uninstall clean
|
||||
.PHONY: all install uninstall clean
|
||||
|
||||
|
||||
##### Dev targets #####
|
||||
|
|
@ -122,6 +118,7 @@ test-wasm:
|
|||
|
||||
lint:
|
||||
cargo update --workspace --locked --quiet
|
||||
cargo check --workspace --all-targets
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
|
|
@ -129,13 +126,8 @@ lint-web:
|
|||
npm --prefix lib/binding_web ci
|
||||
npm --prefix lib/binding_web run lint
|
||||
|
||||
lint-toml:
|
||||
taplo check
|
||||
taplo format --check --diff
|
||||
|
||||
format:
|
||||
cargo fmt --all
|
||||
taplo format
|
||||
|
||||
changelog:
|
||||
@git-cliff --config .github/cliff.toml --prepend CHANGELOG.md --latest --github-token $(shell gh auth token)
|
||||
|
|
|
|||
33
Package.swift
Normal file
33
Package.swift
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// swift-tools-version: 5.8
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TreeSitter",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "TreeSitter",
|
||||
targets: ["TreeSitter"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "TreeSitter",
|
||||
path: "lib",
|
||||
exclude: [
|
||||
"src/unicode/ICU_SHA",
|
||||
"src/unicode/README.md",
|
||||
"src/unicode/LICENSE",
|
||||
"src/wasm/stdlib-symbols.txt",
|
||||
"src/lib.c",
|
||||
],
|
||||
sources: ["src"],
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
.headerSearchPath("src"),
|
||||
.define("_POSIX_C_SOURCE", to: "200112L"),
|
||||
.define("_DEFAULT_SOURCE"),
|
||||
]),
|
||||
],
|
||||
cLanguageStandard: .c11
|
||||
)
|
||||
51
build.zig
51
build.zig
|
|
@ -4,56 +4,49 @@ pub fn build(b: *std.Build) !void {
|
|||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
var threaded: std.Io.Threaded = .init(b.allocator, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
const wasm = b.option(bool, "enable-wasm", "Enable Wasm support") orelse false;
|
||||
const shared = b.option(bool, "build-shared", "Build a shared library") orelse false;
|
||||
const amalgamated = b.option(bool, "amalgamated", "Build using an amalgamated source") orelse false;
|
||||
|
||||
var tree_sitter = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
});
|
||||
const lib: *std.Build.Step.Compile = b.addLibrary(.{
|
||||
.name = "tree-sitter",
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = tree_sitter,
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
}),
|
||||
});
|
||||
|
||||
if (amalgamated) {
|
||||
tree_sitter.addCSourceFile(.{
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("lib/src/lib.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
} else {
|
||||
const files = try findSourceFiles(b, io);
|
||||
const files = try findSourceFiles(b);
|
||||
defer b.allocator.free(files);
|
||||
tree_sitter.addCSourceFiles(.{
|
||||
lib.addCSourceFiles(.{
|
||||
.root = b.path("lib/src"),
|
||||
.files = files,
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
}
|
||||
|
||||
tree_sitter.addIncludePath(b.path("lib/include"));
|
||||
tree_sitter.addIncludePath(b.path("lib/src"));
|
||||
tree_sitter.addIncludePath(b.path("lib/src/wasm"));
|
||||
lib.addIncludePath(b.path("lib/include"));
|
||||
lib.addIncludePath(b.path("lib/src"));
|
||||
lib.addIncludePath(b.path("lib/src/wasm"));
|
||||
|
||||
tree_sitter.addCMacro("_POSIX_C_SOURCE", "200112L");
|
||||
tree_sitter.addCMacro("_DEFAULT_SOURCE", "");
|
||||
tree_sitter.addCMacro("_BSD_SOURCE", "");
|
||||
tree_sitter.addCMacro("_DARWIN_C_SOURCE", "");
|
||||
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
|
||||
lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
|
||||
|
||||
if (wasm) {
|
||||
if (b.lazyDependency(wasmtimeDep(target.result), .{})) |wasmtime| {
|
||||
tree_sitter.addCMacro("TREE_SITTER_FEATURE_WASM", "");
|
||||
tree_sitter.addSystemIncludePath(wasmtime.path("include"));
|
||||
tree_sitter.addLibraryPath(wasmtime.path("lib"));
|
||||
if (shared) tree_sitter.linkSystemLibrary("wasmtime", .{});
|
||||
lib.root_module.addCMacro("TREE_SITTER_FEATURE_WASM", "");
|
||||
lib.addSystemIncludePath(wasmtime.path("include"));
|
||||
lib.addLibraryPath(wasmtime.path("lib"));
|
||||
if (shared) lib.linkSystemLibrary("wasmtime");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,14 +120,14 @@ pub fn wasmtimeDep(target: std.Target) []const u8 {
|
|||
);
|
||||
}
|
||||
|
||||
fn findSourceFiles(b: *std.Build, io: std.Io) ![]const []const u8 {
|
||||
fn findSourceFiles(b: *std.Build) ![]const []const u8 {
|
||||
var sources: std.ArrayListUnmanaged([]const u8) = .empty;
|
||||
|
||||
var dir = try b.build_root.handle.openDir(io, "lib/src", .{ .iterate = true });
|
||||
var dir = try b.build_root.handle.openDir("lib/src", .{ .iterate = true });
|
||||
var iter = dir.iterate();
|
||||
defer dir.close(io);
|
||||
defer dir.close();
|
||||
|
||||
while (try iter.next(io)) |entry| {
|
||||
while (try iter.next()) |entry| {
|
||||
if (entry.kind != .file) continue;
|
||||
const file = entry.name;
|
||||
const ext = std.fs.path.extension(file);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.{
|
||||
.name = .tree_sitter,
|
||||
.fingerprint = 0x841224b447ac0d4f,
|
||||
.version = "0.28.0",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.version = "0.26.0",
|
||||
.minimum_zig_version = "0.14.1",
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
|
@ -13,83 +13,83 @@
|
|||
},
|
||||
.dependencies = .{
|
||||
.wasmtime_c_api_aarch64_android = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAIp_mQVzQOITXcYcWxYLJkvB1W1SvLlrdiU2G7fj",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-aarch64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAIfPIgdw2YnV3QyiFQ2NHdrxrXzzCdjYJyxJDOta",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_aarch64_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAMztsgU5Aj4oI3MRHXJVe5rW72op-kT_78I3kZVM",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-aarch64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAIt97QZi7Pf7nNJ2mVY6uxA80Klyuvvtop3pLMRK",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_aarch64_macos = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AANZxOwT27sdrKxDDGGKsiwtcZlHy204xAWNIgDBH",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-aarch64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAAO48QQf91w9RmmUDHTja8DrXZA1n6Bmc8waW3qe",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_aarch64_musl = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAJL1zQW9yxC98uc60lSuVUHhH77QHTto0zwIQnBj",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-aarch64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAI196wa9pwADoA2RbCDp5F7bKQg1iOPq6gIh8-FH",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_aarch64_windows = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAHRCtQU93hJcRFOgVcof3IQpRV9stT2Pp54wpJc2",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-aarch64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAC9u4wXfqd1Q6XyQaC8_DbQZClXux60Vu5743N05",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_armv7_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-armv7-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAJaW6gT8QdULOU0jxX4a_DOCA5YD6cxWBC8IqhQF",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-armv7-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAHXe8gWs3s83Cc5G6SIq0_jWxj8fGTT5xG4vb6-x",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_i686_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-i686-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AANguMgVX4XMhdOVkdj4yfFKXrG8RTgZDs3nQB8J8",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-i686-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAN2pzgUUfulRCYnipSfis9IIYHoTHVlieLRmKuct",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_i686_windows = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-i686-windows-c-api.zip",
|
||||
.hash = "N-V-__8AANY9ggXg4rK2_1o3EIlrCq124l5RfykPv-DPforq",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-i686-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAJu0YAUUTFBLxFIOi-MSQVezA6MMkpoFtuaf2Quf",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_riscv64gc_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-riscv64gc-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAPDtCAdQ0dD9Rs-qWl-kPr2c7L3PVsUVjwy12Iz1",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-riscv64gc-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAG8m-gc3E3AIImtTZ3l1c7HC6HUWazQ9OH5KACX4",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_s390x_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-s390x-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AANA3BwY1ZOoGCWCR_tTY9G1vfIX128RGVxufN3ov",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-s390x-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAH314gd-gE4IBp2uvAL3gHeuW1uUZjMiLLeUdXL_",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_android = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAF4AIgY0ltjevj1ybGfvMU1ErPRnNve5X1TmvCru",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAIPNRwfNkznebrcGb0IKUe7f35bkuZEYOjcx6q3f",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_linux = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAIR0cAbjf3DkrTbu81Oq_zociz-0lCpb5DR0lIC9",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAI8EDwcyTtk_Afhk47SEaqfpoRqGkJeZpGs69ChF",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_macos = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAFJ4lgRgCBnYdz8-Yfc4hLve45Hv-0RICAEuCp4s",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAGtGNgVaOpHSxC22IjrampbRIy6lLwscdcAE8nG1",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_mingw = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-mingw-c-api.zip",
|
||||
.hash = "N-V-__8AAMxZxQZUpp1cU8J5zgLiMNq4e4dy0hcchlQFy03J",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-mingw-c-api.zip",
|
||||
.hash = "N-V-__8AAPS2PAbVix50L6lnddlgazCPTz3whLUFk1qnRtnZ",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_musl = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAN5pWgZrZBt8VYWkN82WjyFe3DkGcE7uLn3jpt38",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAF-WEQe0nzvi09PgusM5i46FIuCKJmIDWUleWgQ3",
|
||||
.lazy = true,
|
||||
},
|
||||
.wasmtime_c_api_x86_64_windows = .{
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAEIJkgaVHFETgakbognNUpFELuV17vpjw6NjsWhQ",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v33.0.2/wasmtime-v33.0.2-x86_64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAKGNXwbpJQsn0_6kwSIVDDWifSg8cBzf7T2RzsC9",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ description = "CLI tool for developing, testing, and using Tree-sitter parsers"
|
|||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme = "README.md"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/tree-sitter-cli"
|
||||
license.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
include = [ "build.rs", "README.md", "LICENSE", "benches/*", "src/**" ]
|
||||
include = ["build.rs", "README.md", "LICENSE", "benches/*", "src/**"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -20,18 +21,18 @@ workspace = true
|
|||
path = "src/tree_sitter_cli.rs"
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "tree-sitter"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = [ "qjs-rt" ]
|
||||
qjs-rt = [ "tree-sitter-generate/qjs-rt" ]
|
||||
wasm = [ "tree-sitter/wasm", "tree-sitter-loader/wasm" ]
|
||||
default = ["qjs-rt"]
|
||||
wasm = ["tree-sitter/wasm", "tree-sitter-loader/wasm"]
|
||||
qjs-rt = ["tree-sitter-generate/qjs-rt"]
|
||||
|
||||
[dependencies]
|
||||
ansi_colours.workspace = true
|
||||
|
|
@ -41,7 +42,6 @@ bstr.workspace = true
|
|||
clap.workspace = true
|
||||
clap_complete.workspace = true
|
||||
clap_complete_nushell.workspace = true
|
||||
crc32fast.workspace = true
|
||||
ctor.workspace = true
|
||||
ctrlc.workspace = true
|
||||
dialoguer.workspace = true
|
||||
|
|
@ -53,63 +53,28 @@ log.workspace = true
|
|||
memchr.workspace = true
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
similar.workspace = true
|
||||
streaming-iterator.workspace = true
|
||||
thiserror.workspace = true
|
||||
tiny_http.workspace = true
|
||||
walkdir.workspace = true
|
||||
wasmparser.workspace = true
|
||||
webbrowser.workspace = true
|
||||
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-generate.workspace = true
|
||||
tree-sitter-config.workspace = true
|
||||
tree-sitter-generate = { features = [ "load" ], workspace = true }
|
||||
tree-sitter-highlight.workspace = true
|
||||
tree-sitter-loader.workspace = true
|
||||
tree-sitter-tags.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
encoding_rs = "0.8.35"
|
||||
tree_sitter_proc_macro = { package = "tree-sitter-tests-proc-macro", path = "src/tests/proc_macro" }
|
||||
widestring = "1.2.1"
|
||||
widestring = "1.2.0"
|
||||
tree_sitter_proc_macro = { path = "src/tests/proc_macro", package = "tree-sitter-tests-proc-macro" }
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
tempfile.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
[package.metadata.binstall]
|
||||
pkg-fmt = "zip"
|
||||
|
||||
[package.metadata.binstall.overrides.aarch64-unknown-linux-gnu]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-arm64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.armv7-unknown-linux-gnueabihf]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-arm{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-unknown-linux-gnu]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-x64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.i686-unknown-linux-gnu]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-x86{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.powerpc64-unknown-linux-gnu]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-powerpc64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.aarch64-pc-windows-msvc]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-arm64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-x64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.i686-pc-windows-msvc]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-x86{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.aarch64-apple-darwin]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-arm64{ archive-suffix }"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-apple-darwin]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-x64{ archive-suffix }"
|
||||
|
|
|
|||
|
|
@ -7,22 +7,22 @@
|
|||
[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
|
||||
|
||||
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
|
||||
|
||||
You can install the `tree-sitter-cli` with [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall):
|
||||
You can install the `tree-sitter-cli` with `cargo`:
|
||||
|
||||
```sh
|
||||
cargo binstall tree-sitter-cli
|
||||
```
|
||||
|
||||
or you can build it from source:
|
||||
```sh
|
||||
cargo install --locked tree-sitter-cli
|
||||
```
|
||||
|
||||
or with `npm`:
|
||||
|
||||
```sh
|
||||
npm install tree-sitter-cli
|
||||
```
|
||||
|
||||
You can also download a pre-built binary for your platform from [the releases page].
|
||||
|
||||
### Dependencies
|
||||
|
|
@ -34,11 +34,9 @@ The `tree-sitter` binary itself has no dependencies, but specific commands have
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,13 @@ use std::{
|
|||
collections::BTreeMap,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::LazyLock,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use log::info;
|
||||
use tree_sitter::{Language, Parser, Query};
|
||||
#[cfg(feature = "wasm")]
|
||||
use tree_sitter::{WasmStore, wasmtime};
|
||||
use tree_sitter_loader::{CompileConfig, Loader};
|
||||
|
||||
include!("../src/tests/helpers/dirs.rs");
|
||||
|
|
@ -20,18 +18,14 @@ static LANGUAGE_FILTER: LazyLock<Option<String>> =
|
|||
static EXAMPLE_FILTER: LazyLock<Option<String>> =
|
||||
LazyLock::new(|| env::var("TREE_SITTER_BENCHMARK_EXAMPLE_FILTER").ok());
|
||||
static REPETITION_COUNT: LazyLock<usize> = LazyLock::new(|| {
|
||||
env::var("TREE_SITTER_BENCHMARK_REPETITION_COUNT").map_or(5, |s| s.parse::<usize>().unwrap())
|
||||
env::var("TREE_SITTER_BENCHMARK_REPETITION_COUNT")
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(5)
|
||||
});
|
||||
static WASM: LazyLock<bool> = LazyLock::new(|| env::var_os("TREE_SITTER_BENCHMARK_WASM").is_some());
|
||||
static TEST_LOADER: LazyLock<Loader> =
|
||||
LazyLock::new(|| Loader::with_parser_lib_path(SCRATCH_DIR.clone()));
|
||||
#[cfg(feature = "wasm")]
|
||||
static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(Default::default);
|
||||
|
||||
#[expect(
|
||||
clippy::type_complexity,
|
||||
reason = "complex map type reflects benchmark data structure"
|
||||
)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
|
||||
BTreeMap<PathBuf, (Vec<PathBuf>, Vec<PathBuf>)>,
|
||||
> = LazyLock::new(|| {
|
||||
|
|
@ -43,14 +37,22 @@ static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
|
|||
if let Ok(example_files) = fs::read_dir(dir.join("examples")) {
|
||||
example_paths.extend(example_files.filter_map(|p| {
|
||||
let p = p.unwrap().path();
|
||||
if p.is_file() { Some(p) } else { None }
|
||||
if p.is_file() {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if let Ok(query_files) = fs::read_dir(dir.join("queries")) {
|
||||
query_paths.extend(query_files.filter_map(|p| {
|
||||
let p = p.unwrap().path();
|
||||
if p.is_file() { Some(p) } else { None }
|
||||
if p.is_file() {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
|
|
@ -69,8 +71,6 @@ static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
|
|||
});
|
||||
|
||||
fn main() {
|
||||
tree_sitter_cli::logger::init();
|
||||
|
||||
let max_path_length = EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR
|
||||
.values()
|
||||
.flat_map(|(e, q)| {
|
||||
|
|
@ -81,7 +81,7 @@ fn main() {
|
|||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
info!("Benchmarking with {} repetitions", *REPETITION_COUNT);
|
||||
eprintln!("Benchmarking with {} repetitions", *REPETITION_COUNT);
|
||||
|
||||
let mut parser = Parser::new();
|
||||
let mut all_normal_speeds = Vec::new();
|
||||
|
|
@ -92,26 +92,22 @@ fn main() {
|
|||
{
|
||||
let language_name = language_path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter.as_str()
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter.as_str() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
info!("\nLanguage: {language_name}");
|
||||
let language = if *WASM {
|
||||
get_wasm_language(language_name, &mut parser)
|
||||
} else {
|
||||
get_language(language_path)
|
||||
};
|
||||
eprintln!("\nLanguage: {language_name}");
|
||||
let language = get_language(language_path);
|
||||
parser.set_language(&language).unwrap();
|
||||
|
||||
info!(" Constructing Queries");
|
||||
eprintln!(" Constructing Queries");
|
||||
for path in query_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parse(path, max_path_length, |source| {
|
||||
|
|
@ -121,13 +117,13 @@ fn main() {
|
|||
});
|
||||
}
|
||||
|
||||
info!(" Parsing Valid Code:");
|
||||
eprintln!(" Parsing Valid Code:");
|
||||
let mut normal_speeds = Vec::new();
|
||||
for example_path in example_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !example_path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !example_path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
normal_speeds.push(parse(example_path, max_path_length, |code| {
|
||||
|
|
@ -135,17 +131,17 @@ fn main() {
|
|||
}));
|
||||
}
|
||||
|
||||
info!(" Parsing Invalid Code (mismatched languages):");
|
||||
eprintln!(" Parsing Invalid Code (mismatched languages):");
|
||||
let mut error_speeds = Vec::new();
|
||||
for (other_language_path, (example_paths, _)) in
|
||||
EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR.iter()
|
||||
{
|
||||
if other_language_path != language_path {
|
||||
for example_path in example_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !example_path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !example_path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
error_speeds.push(parse(example_path, max_path_length, |code| {
|
||||
|
|
@ -156,30 +152,30 @@ fn main() {
|
|||
}
|
||||
|
||||
if let Some((average_normal, worst_normal)) = aggregate(&normal_speeds) {
|
||||
info!(" Average Speed (normal): {average_normal} bytes/ms");
|
||||
info!(" Worst Speed (normal): {worst_normal} bytes/ms");
|
||||
eprintln!(" Average Speed (normal): {average_normal} bytes/ms");
|
||||
eprintln!(" Worst Speed (normal): {worst_normal} bytes/ms");
|
||||
}
|
||||
|
||||
if let Some((average_error, worst_error)) = aggregate(&error_speeds) {
|
||||
info!(" Average Speed (errors): {average_error} bytes/ms");
|
||||
info!(" Worst Speed (errors): {worst_error} bytes/ms");
|
||||
eprintln!(" Average Speed (errors): {average_error} bytes/ms");
|
||||
eprintln!(" Worst Speed (errors): {worst_error} bytes/ms");
|
||||
}
|
||||
|
||||
all_normal_speeds.extend(normal_speeds);
|
||||
all_error_speeds.extend(error_speeds);
|
||||
}
|
||||
|
||||
info!("\n Overall");
|
||||
eprintln!("\n Overall");
|
||||
if let Some((average_normal, worst_normal)) = aggregate(&all_normal_speeds) {
|
||||
info!(" Average Speed (normal): {average_normal} bytes/ms");
|
||||
info!(" Worst Speed (normal): {worst_normal} bytes/ms");
|
||||
eprintln!(" Average Speed (normal): {average_normal} bytes/ms");
|
||||
eprintln!(" Worst Speed (normal): {worst_normal} bytes/ms");
|
||||
}
|
||||
|
||||
if let Some((average_error, worst_error)) = aggregate(&all_error_speeds) {
|
||||
info!(" Average Speed (errors): {average_error} bytes/ms");
|
||||
info!(" Worst Speed (errors): {worst_error} bytes/ms");
|
||||
eprintln!(" Average Speed (errors): {average_error} bytes/ms");
|
||||
eprintln!(" Worst Speed (errors): {worst_error} bytes/ms");
|
||||
}
|
||||
info!("");
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
fn aggregate(speeds: &[usize]) -> Option<(usize, usize)> {
|
||||
|
|
@ -198,6 +194,12 @@ fn aggregate(speeds: &[usize]) -> Option<(usize, usize)> {
|
|||
}
|
||||
|
||||
fn parse(path: &Path, max_path_length: usize, mut action: impl FnMut(&[u8])) -> usize {
|
||||
eprint!(
|
||||
" {:width$}\t",
|
||||
path.file_name().unwrap().to_str().unwrap(),
|
||||
width = max_path_length
|
||||
);
|
||||
|
||||
let source_code = fs::read(path)
|
||||
.with_context(|| format!("Failed to read {}", path.display()))
|
||||
.unwrap();
|
||||
|
|
@ -208,9 +210,8 @@ fn parse(path: &Path, max_path_length: usize, mut action: impl FnMut(&[u8])) ->
|
|||
let duration = time.elapsed() / (*REPETITION_COUNT as u32);
|
||||
let duration_ns = duration.as_nanos();
|
||||
let speed = ((source_code.len() as u128) * 1_000_000) / duration_ns;
|
||||
info!(
|
||||
" {:max_path_length$}\ttime {:>7.2} ms\t\tspeed {speed:>6} bytes/ms",
|
||||
path.file_name().unwrap().to_str().unwrap(),
|
||||
eprintln!(
|
||||
"time {:>7.2} ms\t\tspeed {speed:>6} bytes/ms",
|
||||
(duration_ns as f64) / 1e6,
|
||||
);
|
||||
speed as usize
|
||||
|
|
@ -223,32 +224,3 @@ fn get_language(path: &Path) -> Language {
|
|||
.with_context(|| format!("Failed to load language at path {}", src_path.display()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
fn get_wasm_language(language_name: &str, parser: &mut Parser) -> Language {
|
||||
let wasm_language_name = language_name.replace('-', "_");
|
||||
let wasm_path = ROOT_DIR
|
||||
.join("target")
|
||||
.join("release")
|
||||
.join(format!("tree-sitter-{language_name}.wasm"));
|
||||
let wasm = fs::read(&wasm_path)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to read {}. Generate Wasm fixtures with `cargo xtask generate-fixtures --wasm`",
|
||||
wasm_path.display()
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let mut store = WasmStore::new(&WASM_ENGINE).expect("Failed to create Wasm store");
|
||||
let language = store
|
||||
.load_language(&wasm_language_name, &wasm)
|
||||
.with_context(|| format!("Failed to load Wasm language at {}", wasm_path.display()))
|
||||
.unwrap();
|
||||
parser.set_wasm_store(store).unwrap();
|
||||
language
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
fn get_wasm_language(_language_name: &str, _parser: &mut Parser) -> Language {
|
||||
panic!("Wasm benchmarking requires the `wasm` feature");
|
||||
}
|
||||
|
|
|
|||
828
crates/cli/eslint/package-lock.json
generated
828
crates/cli/eslint/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,7 @@
|
|||
"tree-sitter"
|
||||
],
|
||||
"dependencies": {
|
||||
"eslint-plugin-jsdoc": "^62.7.0"
|
||||
"eslint-plugin-jsdoc": "^50.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">= 9"
|
||||
|
|
|
|||
84
crates/cli/npm/dsl.d.ts
vendored
84
crates/cli/npm/dsl.d.ts
vendored
|
|
@ -3,22 +3,18 @@ type BlankRule = { type: 'BLANK' };
|
|||
type ChoiceRule = { type: 'CHOICE'; members: Rule[] };
|
||||
type FieldRule = { type: 'FIELD'; name: string; content: Rule };
|
||||
type ImmediateTokenRule = { type: 'IMMEDIATE_TOKEN'; content: Rule };
|
||||
type PatternRule = { type: 'PATTERN'; value: string; flags?: string };
|
||||
type PrecedenceValue = string | number;
|
||||
type PatternRule = { type: 'PATTERN'; value: string };
|
||||
type PrecDynamicRule = { type: 'PREC_DYNAMIC'; content: Rule; value: number };
|
||||
type PrecLeftRule = { type: 'PREC_LEFT'; content: Rule; value: PrecedenceValue };
|
||||
type PrecRightRule = { type: 'PREC_RIGHT'; content: Rule; value: PrecedenceValue };
|
||||
type PrecRule = { type: 'PREC'; content: Rule; value: PrecedenceValue };
|
||||
type PrecLeftRule = { type: 'PREC_LEFT'; content: Rule; value: number };
|
||||
type PrecRightRule = { type: 'PREC_RIGHT'; content: Rule; value: number };
|
||||
type PrecRule = { type: 'PREC'; content: Rule; value: number };
|
||||
type Repeat1Rule = { type: 'REPEAT1'; content: Rule };
|
||||
type RepeatRule = { type: 'REPEAT'; content: Rule };
|
||||
type ReservedRule = { type: 'RESERVED'; content: Rule; context_name: string };
|
||||
type SeqRule = { type: 'SEQ'; members: Rule[] };
|
||||
type StringRule = { type: 'STRING'; value: string };
|
||||
type SymbolRule<Name extends string> = { type: 'SYMBOL'; name: Name };
|
||||
type PrecedenceEntry = StringRule | SymbolRule<string>;
|
||||
type TokenRule = { type: 'TOKEN'; content: Rule };
|
||||
type EOFRule = { type: 'EOF' };
|
||||
|
||||
|
||||
type Rule =
|
||||
| AliasRule
|
||||
|
|
@ -33,12 +29,10 @@ type Rule =
|
|||
| PrecRule
|
||||
| Repeat1Rule
|
||||
| RepeatRule
|
||||
| ReservedRule
|
||||
| SeqRule
|
||||
| StringRule
|
||||
| SymbolRule<string>
|
||||
| TokenRule
|
||||
| EOFRule;
|
||||
| TokenRule;
|
||||
|
||||
declare class RustRegex {
|
||||
value: string;
|
||||
|
|
@ -91,8 +85,8 @@ interface Grammar<
|
|||
*/
|
||||
precedences?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: PrecedenceEntry[][],
|
||||
) => (string | PrecedenceEntry)[][],
|
||||
previous: Rule[][],
|
||||
) => RuleOrLiteral[][],
|
||||
|
||||
/**
|
||||
* An array of arrays of rule names. Each inner array represents a set of
|
||||
|
|
@ -106,8 +100,8 @@ interface Grammar<
|
|||
*/
|
||||
conflicts?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[][],
|
||||
) => SymbolRule<string>[][];
|
||||
previous: Rule[][],
|
||||
) => RuleOrLiteral[][];
|
||||
|
||||
/**
|
||||
* An array of token names which can be returned by an _external scanner_.
|
||||
|
|
@ -132,11 +126,9 @@ interface Grammar<
|
|||
* specify extras: `$ => []` in your grammar.
|
||||
*
|
||||
* @param $ grammar rules
|
||||
* @param previous array of extras from the base grammar
|
||||
*/
|
||||
extras?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: Rule[],
|
||||
) => RuleOrLiteral[];
|
||||
|
||||
/**
|
||||
|
|
@ -149,8 +141,8 @@ interface Grammar<
|
|||
*/
|
||||
inline?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[],
|
||||
) => SymbolRule<string>[];
|
||||
previous: Rule[],
|
||||
) => RuleOrLiteral[];
|
||||
|
||||
/**
|
||||
* A list of hidden rule names that should be considered supertypes in the
|
||||
|
|
@ -162,8 +154,8 @@ interface Grammar<
|
|||
*/
|
||||
supertypes?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[],
|
||||
) => SymbolRule<string>[];
|
||||
previous: Rule[],
|
||||
) => RuleOrLiteral[];
|
||||
|
||||
/**
|
||||
* The name of a token that will match keywords for the purpose of the
|
||||
|
|
@ -173,47 +165,24 @@ interface Grammar<
|
|||
*
|
||||
* @see https://tree-sitter.github.io/tree-sitter/creating-parsers/3-writing-the-grammar#keyword-extraction
|
||||
*/
|
||||
word?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
) => SymbolRule<string>;
|
||||
word?: ($: GrammarSymbols<RuleName | BaseGrammarRuleName>) => RuleOrLiteral;
|
||||
|
||||
|
||||
/**
|
||||
* Mapping of names to reserved word sets. The first reserved word set is the
|
||||
* global word set, meaning it applies to every rule in every parse state.
|
||||
* The other word sets can be used with the `reserved` function. Each callback
|
||||
* receives the base grammar's reserved word set of the same name as its second
|
||||
* argument, or `undefined` if no matching set exists.
|
||||
* The other word sets can be used with the `reserved` function.
|
||||
*/
|
||||
reserved?: Record<
|
||||
string,
|
||||
(
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: Rule[] | undefined,
|
||||
) => RuleOrLiteral[]
|
||||
($: GrammarSymbols<RuleName | BaseGrammarRuleName>) => RuleOrLiteral[]
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type of grammar(). The runtime evaluates and normalizes the grammar
|
||||
* beneath a "grammar" key. Optional input fields become required output fields
|
||||
* with default values when not provided.
|
||||
*/
|
||||
type GrammarSchema<RuleName extends string> = {
|
||||
grammar: {
|
||||
name: string;
|
||||
/** Base grammar name when extending; undefined for root grammars. */
|
||||
inherits: string | undefined;
|
||||
rules: Record<RuleName, Rule>;
|
||||
precedences: PrecedenceEntry[][];
|
||||
conflicts: string[][];
|
||||
externals: Rule[];
|
||||
extras: Rule[];
|
||||
inline: string[];
|
||||
supertypes: string[];
|
||||
word: string | undefined;
|
||||
reserved: Record<string, Rule[]>;
|
||||
};
|
||||
[K in keyof Grammar<RuleName>]: K extends 'rules'
|
||||
? Record<RuleName, Rule>
|
||||
: Grammar<RuleName>[K];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -341,7 +310,7 @@ declare const prec: {
|
|||
*
|
||||
* @see https://www.gnu.org/software/bison/manual/html_node/Generalized-LR-Parsing.html
|
||||
*/
|
||||
dynamic(value: number, rule: RuleOrLiteral): PrecDynamicRule;
|
||||
dynamic(value: string | number, rule: RuleOrLiteral): PrecDynamicRule;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -412,19 +381,6 @@ declare const token: {
|
|||
immediate(rule: RuleOrLiteral): ImmediateTokenRule;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the end of input. May only appear as the final symbol of a
|
||||
* (possibly nested) sequence; a production ending in `eof()` reduces only
|
||||
* when the lookahead is end-of-input, rather than shifting a token.
|
||||
*
|
||||
* Choice branches that continue past `eof()` are dropped as unreachable,
|
||||
* and `eof()` is not allowed inside `token()`.
|
||||
*
|
||||
* Useful when a rule should match either an explicit terminator (e.g. a
|
||||
* newline) or the end of the file.
|
||||
*/
|
||||
declare function eof(): EOFRule;
|
||||
|
||||
/**
|
||||
* Creates a new language grammar with the provided schema.
|
||||
*
|
||||
|
|
|
|||
4
crates/cli/npm/package-lock.json
generated
4
crates/cli/npm/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.0",
|
||||
"author": {
|
||||
"name": "Max Brunsfeld",
|
||||
"email": "maxbrunsfeld@gmail.com"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
installShellFiles,
|
||||
}:
|
||||
let
|
||||
canRunHost = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
|
||||
isCross = stdenv.targetPlatform == stdenv.buildPlatform;
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "tree-sitter-cli";
|
||||
|
|
@ -28,7 +28,7 @@ rustPlatform.buildRustPackage {
|
|||
pkg-config
|
||||
nodejs_22
|
||||
]
|
||||
++ lib.optionals canRunHost [ installShellFiles ];
|
||||
++ lib.optionals (!isCross) [ installShellFiles ];
|
||||
|
||||
cargoLock.lockFile = ../../Cargo.lock;
|
||||
|
||||
|
|
@ -42,9 +42,9 @@ rustPlatform.buildRustPackage {
|
|||
'';
|
||||
|
||||
preCheck = "export HOME=$TMPDIR";
|
||||
doCheck = canRunHost;
|
||||
doCheck = !isCross;
|
||||
|
||||
postInstall = lib.optionalString canRunHost ''
|
||||
postInstall = lib.optionalString (!isCross) ''
|
||||
installShellCompletion --cmd tree-sitter \
|
||||
--bash <($out/bin/tree-sitter complete --shell bash) \
|
||||
--zsh <($out/bin/tree-sitter complete --shell zsh) \
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ use std::{
|
|||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use log::{error, info};
|
||||
use rand::RngExt;
|
||||
use rand::Rng;
|
||||
use regex::Regex;
|
||||
use tree_sitter::{Language, Parser};
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ use crate::{
|
|||
random::Rand,
|
||||
},
|
||||
parse::perform_edit,
|
||||
test::{DiffKey, TestDiff, TestEntry, TestExpectation, parse_tests, render_test_output},
|
||||
test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields, TestEntry},
|
||||
};
|
||||
|
||||
pub static LOG_ENABLED: LazyLock<bool> = LazyLock::new(|| env::var("TREE_SITTER_LOG").is_ok());
|
||||
|
|
@ -44,13 +43,11 @@ pub static EXAMPLE_EXCLUDE: LazyLock<Option<Regex>> =
|
|||
|
||||
pub static START_SEED: LazyLock<usize> = LazyLock::new(new_seed);
|
||||
|
||||
pub const DEFAULT_EDIT_COUNT: usize = 3;
|
||||
pub static EDIT_COUNT: LazyLock<usize> =
|
||||
LazyLock::new(|| int_env_var("TREE_SITTER_EDITS").unwrap_or(DEFAULT_EDIT_COUNT));
|
||||
LazyLock::new(|| int_env_var("TREE_SITTER_EDITS").unwrap_or(3));
|
||||
|
||||
pub const DEFAULT_ITERATION_COUNT: usize = 10;
|
||||
pub static ITERATION_COUNT: LazyLock<usize> =
|
||||
LazyLock::new(|| int_env_var("TREE_SITTER_ITERATIONS").unwrap_or(DEFAULT_ITERATION_COUNT));
|
||||
LazyLock::new(|| int_env_var("TREE_SITTER_ITERATIONS").unwrap_or(10));
|
||||
|
||||
fn int_env_var(name: &'static str) -> Option<usize> {
|
||||
env::var(name).ok().and_then(|e| e.parse().ok())
|
||||
|
|
@ -63,9 +60,9 @@ fn regex_env_var(name: &'static str) -> Option<Regex> {
|
|||
#[must_use]
|
||||
pub fn new_seed() -> usize {
|
||||
int_env_var("TREE_SITTER_SEED").unwrap_or_else(|| {
|
||||
let mut rng = rand::rng();
|
||||
let seed = rng.random_range(0..=usize::MAX);
|
||||
eprintln!("fuzz seed: {seed}");
|
||||
let mut rng = rand::thread_rng();
|
||||
let seed = rng.gen::<usize>();
|
||||
eprintln!("Seed: {seed}");
|
||||
seed
|
||||
})
|
||||
}
|
||||
|
|
@ -97,7 +94,9 @@ pub fn fuzz_language_corpus(
|
|||
.iter()
|
||||
.any(|lang| lang.as_ref() == language_name)
|
||||
}
|
||||
TestEntry::Group { children, .. } => {
|
||||
TestEntry::Group {
|
||||
ref mut children, ..
|
||||
} => {
|
||||
children.retain_mut(|child| retain(child, language_name));
|
||||
!children.is_empty()
|
||||
}
|
||||
|
|
@ -109,16 +108,12 @@ pub fn fuzz_language_corpus(
|
|||
let corpus_dir = grammar_dir.join(subdir).join("test").join("corpus");
|
||||
|
||||
if !corpus_dir.exists() || !corpus_dir.is_dir() {
|
||||
error!(
|
||||
"No corpus directory found, ensure that you have a `test/corpus` directory in your grammar directory with at least one test file."
|
||||
);
|
||||
eprintln!("No corpus directory found, ensure that you have a `test/corpus` directory in your grammar directory with at least one test file.");
|
||||
return;
|
||||
}
|
||||
|
||||
if std::fs::read_dir(&corpus_dir).unwrap().count() == 0 {
|
||||
error!(
|
||||
"No corpus files found in `test/corpus`, ensure that you have at least one test file in your corpus directory."
|
||||
);
|
||||
eprintln!("No corpus files found in `test/corpus`, ensure that you have at least one test file in your corpus directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +139,7 @@ pub fn fuzz_language_corpus(
|
|||
.take()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(tests.iter().filter(|t| t.skip()).map(get_test_name))
|
||||
.chain(tests.iter().filter(|x| x.skip).map(get_test_name))
|
||||
.map(|x| (x, 0))
|
||||
.collect::<HashMap<String, usize>>();
|
||||
|
||||
|
|
@ -154,7 +149,7 @@ pub fn fuzz_language_corpus(
|
|||
let dump_edits = env::var("TREE_SITTER_DUMP_EDITS").is_ok();
|
||||
|
||||
if log_seed {
|
||||
info!(" start seed: {start_seed}");
|
||||
println!(" start seed: {start_seed}");
|
||||
}
|
||||
|
||||
println!();
|
||||
|
|
@ -169,11 +164,34 @@ pub fn fuzz_language_corpus(
|
|||
println!(" {test_index}. {test_name}");
|
||||
|
||||
let passed = allocations::record_checked(|| {
|
||||
let check_output = !test.error();
|
||||
test.check_initial_parse(language, &test_name, check_output)
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(language).unwrap();
|
||||
set_included_ranges(&mut parser, &test.input, test.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
|
||||
if test.error {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect initial parse for {test_name}");
|
||||
print_diff_key();
|
||||
print_diff(&actual_output, &test.output, true);
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
error!("{e}");
|
||||
eprintln!("Error: {e}");
|
||||
false
|
||||
});
|
||||
|
||||
|
|
@ -198,11 +216,11 @@ pub fn fuzz_language_corpus(
|
|||
let mut input = test.input.clone();
|
||||
|
||||
if options.log_graphs {
|
||||
info!("{}\n", String::from_utf8_lossy(&input));
|
||||
eprintln!("{}\n", String::from_utf8_lossy(&input));
|
||||
}
|
||||
|
||||
// Perform a random series of edits and reparse.
|
||||
let edit_count = rand.unsigned(options.edits);
|
||||
let edit_count = rand.unsigned(*EDIT_COUNT);
|
||||
let mut undo_stack = Vec::with_capacity(edit_count);
|
||||
for _ in 0..=edit_count {
|
||||
let edit = get_random_edit(&mut rand, &input);
|
||||
|
|
@ -211,7 +229,7 @@ pub fn fuzz_language_corpus(
|
|||
}
|
||||
|
||||
if log_seed {
|
||||
info!(" {test_index}.{trial:<2} seed: {seed}");
|
||||
println!(" {test_index}.{trial:<2} seed: {seed}");
|
||||
}
|
||||
|
||||
if dump_edits {
|
||||
|
|
@ -225,7 +243,7 @@ pub fn fuzz_language_corpus(
|
|||
}
|
||||
|
||||
if options.log_graphs {
|
||||
info!("{}\n", String::from_utf8_lossy(&input));
|
||||
eprintln!("{}\n", String::from_utf8_lossy(&input));
|
||||
}
|
||||
|
||||
set_included_ranges(&mut parser, &input, test.template_delimiters);
|
||||
|
|
@ -234,7 +252,7 @@ pub fn fuzz_language_corpus(
|
|||
// Check that the new tree is consistent.
|
||||
check_consistent_sizes(&tree2, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree, &tree2, &input) {
|
||||
error!("\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
println!("\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n",);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -243,19 +261,22 @@ pub fn fuzz_language_corpus(
|
|||
perform_edit(&mut tree2, &mut input, &edit).unwrap();
|
||||
}
|
||||
if options.log_graphs {
|
||||
info!("{}\n", String::from_utf8_lossy(&input));
|
||||
eprintln!("{}\n", String::from_utf8_lossy(&input));
|
||||
}
|
||||
|
||||
set_included_ranges(&mut parser, &test.input, test.template_delimiters);
|
||||
let tree3 = parser.parse(&input, Some(&tree2)).unwrap();
|
||||
|
||||
// Verify that the final tree matches the expectation from the corpus.
|
||||
let actual_output = render_test_output(&input, &tree3, test.cst, test.has_fields).unwrap();
|
||||
let mut actual_output = tree3.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output && !test.error() {
|
||||
if actual_output != test.output && !test.error {
|
||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
print_diff_key();
|
||||
print_diff(&actual_output, &test.output, true);
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -263,13 +284,13 @@ pub fn fuzz_language_corpus(
|
|||
// Check that the edited tree is consistent.
|
||||
check_consistent_sizes(&tree3, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree2, &tree3, &input) {
|
||||
error!("Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
println!("Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}).unwrap_or_else(|e| {
|
||||
error!("{e}");
|
||||
eprintln!("Error: {e}");
|
||||
false
|
||||
});
|
||||
|
||||
|
|
@ -281,17 +302,17 @@ pub fn fuzz_language_corpus(
|
|||
}
|
||||
|
||||
if failure_count != 0 {
|
||||
info!("{failure_count} {language_name} corpus tests failed fuzzing");
|
||||
eprintln!("{failure_count} {language_name} corpus tests failed fuzzing");
|
||||
}
|
||||
|
||||
skipped.retain(|_, v| *v == 0);
|
||||
|
||||
if !skipped.is_empty() {
|
||||
info!("Non matchable skip definitions:");
|
||||
println!("Non matchable skip definitions:");
|
||||
for k in skipped.keys() {
|
||||
info!(" {k}");
|
||||
println!(" {k}");
|
||||
}
|
||||
panic!("Non matchable skip definitions need to be removed");
|
||||
panic!("Non matchable skip definitions needs to be removed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,56 +321,12 @@ pub struct FlattenedTest {
|
|||
pub input: Vec<u8>,
|
||||
pub output: String,
|
||||
pub languages: Vec<Box<str>>,
|
||||
pub expectation: TestExpectation,
|
||||
pub error: bool,
|
||||
pub skip: bool,
|
||||
pub has_fields: bool,
|
||||
pub cst: bool,
|
||||
pub template_delimiters: Option<(&'static str, &'static str)>,
|
||||
}
|
||||
|
||||
impl FlattenedTest {
|
||||
#[must_use]
|
||||
fn skip(&self) -> bool {
|
||||
self.expectation == TestExpectation::Skip
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn error(&self) -> bool {
|
||||
self.expectation == TestExpectation::Error
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn check_initial_parse(
|
||||
&self,
|
||||
language: &Language,
|
||||
display_name: &str,
|
||||
check_output: bool,
|
||||
) -> bool {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(language).unwrap();
|
||||
set_included_ranges(&mut parser, &self.input, self.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&self.input, None).unwrap();
|
||||
|
||||
if !check_output {
|
||||
return true;
|
||||
}
|
||||
|
||||
let actual_output =
|
||||
render_test_output(&self.input, &tree, self.cst, self.has_fields).unwrap();
|
||||
|
||||
if actual_output == self.output {
|
||||
true
|
||||
} else {
|
||||
println!("Incorrect initial parse for {display_name}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &self.output));
|
||||
println!();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn flatten_tests(
|
||||
test: TestEntry,
|
||||
|
|
@ -382,20 +359,20 @@ pub fn flatten_tests(
|
|||
if !include.is_match(&name) {
|
||||
return;
|
||||
}
|
||||
} else if let Some(exclude) = exclude
|
||||
&& exclude.is_match(&name)
|
||||
{
|
||||
return;
|
||||
} else if let Some(exclude) = exclude {
|
||||
if exclude.is_match(&name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
result.push(FlattenedTest {
|
||||
name,
|
||||
input,
|
||||
output,
|
||||
languages: attributes.languages,
|
||||
expectation: attributes.expectation,
|
||||
has_fields,
|
||||
cst: attributes.cst,
|
||||
languages: attributes.languages,
|
||||
error: attributes.error,
|
||||
skip: attributes.skip,
|
||||
template_delimiters: None,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,19 @@ use std::{
|
|||
collections::HashMap,
|
||||
os::raw::c_void,
|
||||
sync::{
|
||||
Mutex,
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
#[ctor::ctor]
|
||||
unsafe fn initialize_allocation_recording() {
|
||||
unsafe {
|
||||
tree_sitter::set_allocator(Some(tree_sitter::Allocator {
|
||||
malloc: ts_record_malloc,
|
||||
calloc: ts_record_calloc,
|
||||
realloc: ts_record_realloc,
|
||||
free: ts_record_free,
|
||||
}));
|
||||
}
|
||||
tree_sitter::set_allocator(
|
||||
Some(ts_record_malloc),
|
||||
Some(ts_record_calloc),
|
||||
Some(ts_record_realloc),
|
||||
Some(ts_record_free),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
|
@ -35,7 +33,7 @@ thread_local! {
|
|||
static RECORDER: AllocationRecorder = AllocationRecorder::default();
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
extern "C" {
|
||||
fn malloc(size: usize) -> *mut c_void;
|
||||
fn calloc(count: usize, size: usize) -> *mut c_void;
|
||||
fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
|
||||
|
|
@ -105,11 +103,9 @@ fn record_dealloc(ptr: *mut c_void) {
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = malloc(size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
let result = malloc(size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -118,11 +114,9 @@ pub unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = calloc(count, size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
let result = calloc(count, size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -131,16 +125,14 @@ pub unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_v
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = realloc(ptr, size);
|
||||
if ptr.is_null() {
|
||||
record_alloc(result);
|
||||
} else if !core::ptr::eq(ptr, result) {
|
||||
record_dealloc(ptr);
|
||||
record_alloc(result);
|
||||
}
|
||||
result
|
||||
let result = realloc(ptr, size);
|
||||
if ptr.is_null() {
|
||||
record_alloc(result);
|
||||
} else if !core::ptr::eq(ptr, result) {
|
||||
record_dealloc(ptr);
|
||||
record_alloc(result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -148,8 +140,6 @@ pub unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mu
|
|||
/// The caller must ensure that `ptr` was allocated by a previous call
|
||||
/// to `ts_record_malloc`, `ts_record_calloc`, or `ts_record_realloc`.
|
||||
pub unsafe extern "C" fn ts_record_free(ptr: *mut c_void) {
|
||||
unsafe {
|
||||
record_dealloc(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
record_dealloc(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,10 @@
|
|||
use tree_sitter::{LogType, Node, Parser, Point, Range, Tree};
|
||||
|
||||
use super::{LOG_ENABLED, LOG_GRAPH_ENABLED, scope_sequence::ScopeSequence};
|
||||
use super::{scope_sequence::ScopeSequence, LOG_ENABLED, LOG_GRAPH_ENABLED};
|
||||
use crate::util;
|
||||
|
||||
struct SizeCheckFrame<'a> {
|
||||
node: Node<'a>,
|
||||
end_byte: usize,
|
||||
end_point: Point,
|
||||
child_count: u32,
|
||||
child_index: u32,
|
||||
last_child_end_byte: usize,
|
||||
last_child_end_point: Point,
|
||||
some_child_has_changes: bool,
|
||||
actual_named_child_count: usize,
|
||||
}
|
||||
|
||||
impl SizeCheckFrame<'_> {
|
||||
fn new<'a>(node: Node<'a>, line_offsets: &[usize]) -> SizeCheckFrame<'a> {
|
||||
pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
||||
fn check(node: Node, line_offsets: &[usize]) {
|
||||
let start_byte = node.start_byte();
|
||||
let end_byte = node.end_byte();
|
||||
let start_point = node.start_position();
|
||||
|
|
@ -30,21 +18,37 @@ impl SizeCheckFrame<'_> {
|
|||
);
|
||||
assert_eq!(end_byte, line_offsets[end_point.row] + end_point.column);
|
||||
|
||||
SizeCheckFrame {
|
||||
node,
|
||||
end_byte,
|
||||
end_point,
|
||||
child_count: node.child_count(),
|
||||
child_index: 0,
|
||||
last_child_end_byte: start_byte,
|
||||
last_child_end_point: start_point,
|
||||
some_child_has_changes: false,
|
||||
actual_named_child_count: 0,
|
||||
let mut last_child_end_byte = start_byte;
|
||||
let mut last_child_end_point = start_point;
|
||||
let mut some_child_has_changes = false;
|
||||
let mut actual_named_child_count = 0;
|
||||
for i in 0..node.child_count() {
|
||||
let child = node.child(i as u32).unwrap();
|
||||
assert!(child.start_byte() >= last_child_end_byte);
|
||||
assert!(child.start_position() >= last_child_end_point);
|
||||
check(child, line_offsets);
|
||||
if child.has_changes() {
|
||||
some_child_has_changes = true;
|
||||
}
|
||||
if child.is_named() {
|
||||
actual_named_child_count += 1;
|
||||
}
|
||||
last_child_end_byte = child.end_byte();
|
||||
last_child_end_point = child.end_position();
|
||||
}
|
||||
|
||||
assert_eq!(actual_named_child_count, node.named_child_count());
|
||||
|
||||
if node.child_count() > 0 {
|
||||
assert!(end_byte >= last_child_end_byte);
|
||||
assert!(end_point >= last_child_end_point);
|
||||
}
|
||||
|
||||
if some_child_has_changes {
|
||||
assert!(node.has_changes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
||||
let mut line_offsets = vec![0];
|
||||
for (i, c) in input.iter().enumerate() {
|
||||
if *c == b'\n' {
|
||||
|
|
@ -52,41 +56,7 @@ pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
|||
}
|
||||
}
|
||||
|
||||
let mut stack: Vec<SizeCheckFrame> = vec![SizeCheckFrame::new(tree.root_node(), &line_offsets)];
|
||||
while let Some(top) = stack.last_mut() {
|
||||
if top.child_index < top.child_count {
|
||||
let i = top.child_index;
|
||||
let child = top.node.child(i).unwrap();
|
||||
|
||||
assert!(child.start_byte() >= top.last_child_end_byte);
|
||||
assert!(child.start_position() >= top.last_child_end_point);
|
||||
if child.has_changes() {
|
||||
top.some_child_has_changes = true;
|
||||
}
|
||||
if child.is_named() {
|
||||
top.actual_named_child_count += 1;
|
||||
}
|
||||
top.last_child_end_byte = child.end_byte();
|
||||
top.last_child_end_point = child.end_position();
|
||||
top.child_index += 1;
|
||||
|
||||
stack.push(SizeCheckFrame::new(child, &line_offsets));
|
||||
continue;
|
||||
}
|
||||
|
||||
let frame = stack.pop().unwrap();
|
||||
assert_eq!(
|
||||
frame.actual_named_child_count,
|
||||
frame.node.named_child_count()
|
||||
);
|
||||
if frame.child_count > 0 {
|
||||
assert!(frame.end_byte >= frame.last_child_end_byte);
|
||||
assert!(frame.end_point >= frame.last_child_end_point);
|
||||
}
|
||||
if frame.some_child_has_changes {
|
||||
assert!(frame.node.has_changes());
|
||||
}
|
||||
}
|
||||
check(tree.root_node(), &line_offsets);
|
||||
}
|
||||
|
||||
pub fn check_changed_ranges(old_tree: &Tree, new_tree: &Tree, input: &[u8]) -> Result<(), String> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use rand::{RngExt, SeedableRng, distr::Alphanumeric, rngs::StdRng};
|
||||
use rand::{
|
||||
distributions::Alphanumeric,
|
||||
prelude::{Rng, SeedableRng, StdRng},
|
||||
};
|
||||
|
||||
const OPERATORS: &[char] = &[
|
||||
'+', '-', '<', '>', '(', ')', '*', '/', '&', '|', '!', ',', '.', '%',
|
||||
|
|
@ -13,7 +16,7 @@ impl Rand {
|
|||
}
|
||||
|
||||
pub fn unsigned(&mut self, max: usize) -> usize {
|
||||
self.0.random_range(0..=max)
|
||||
self.0.gen_range(0..=max)
|
||||
}
|
||||
|
||||
pub fn words(&mut self, max_count: usize) -> Vec<u8> {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use tree_sitter::{Point, Range, Tree};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScopeSequence<'a>(Vec<ScopeStack<'a>>);
|
||||
pub struct ScopeSequence(Vec<ScopeStack>);
|
||||
|
||||
type ScopeStack<'a> = Vec<&'a str>;
|
||||
type ScopeStack = Vec<&'static str>;
|
||||
|
||||
impl<'a> ScopeSequence<'a> {
|
||||
impl ScopeSequence {
|
||||
#[must_use]
|
||||
pub fn new(tree: &'a Tree) -> Self {
|
||||
pub fn new(tree: &Tree) -> Self {
|
||||
let mut result = Self(Vec::new());
|
||||
let mut scope_stack = Vec::new();
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ impl<'a> ScopeSequence<'a> {
|
|||
for i in 0..(self.0.len().max(other.0.len())) {
|
||||
let stack = &self.0.get(i);
|
||||
let other_stack = &other.0.get(i);
|
||||
if *stack != *other_stack && !b"\r\n".contains(&text[i]) {
|
||||
if *stack != *other_stack && ![b'\r', b'\n'].contains(&text[i]) {
|
||||
let containing_range = known_changed_ranges
|
||||
.iter()
|
||||
.find(|range| range.start_point <= position && position < range.end_point);
|
||||
|
|
|
|||
|
|
@ -4,18 +4,16 @@ use std::{
|
|||
fs,
|
||||
io::{self, Write as _},
|
||||
path::{self, Path, PathBuf},
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
str,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use ansi_colours::{ansi256_from_rgb, rgb_from_ansi256};
|
||||
use anstyle::{Ansi256Color, AnsiColor, Color, Effects, RgbColor};
|
||||
use anyhow::Result;
|
||||
use clap::ValueEnum;
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap};
|
||||
use serde_json::{Value, json};
|
||||
use tree_sitter::ffi::{self, TSInputEncoding};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer};
|
||||
use tree_sitter_loader::Loader;
|
||||
|
||||
|
|
@ -26,9 +24,8 @@ pub const HTML_HEAD_HEADER: &str = "
|
|||
<style>
|
||||
body {
|
||||
font-family: monospace
|
||||
}";
|
||||
|
||||
pub const HTML_LINE_NUMBER_STYLE: &str = " .line-number {
|
||||
}
|
||||
.line-number {
|
||||
user-select: none;
|
||||
text-align: right;
|
||||
color: rgba(27,31,35,.3);
|
||||
|
|
@ -36,7 +33,8 @@ pub const HTML_LINE_NUMBER_STYLE: &str = " .line-number {
|
|||
}
|
||||
.line {
|
||||
white-space: pre;
|
||||
}";
|
||||
}
|
||||
</style>";
|
||||
|
||||
pub const HTML_BODY_HEADER: &str = "
|
||||
</head>
|
||||
|
|
@ -191,14 +189,20 @@ fn parse_style(style: &mut Style, json: Value) {
|
|||
if let Value::Object(entries) = json {
|
||||
for (property_name, value) in entries {
|
||||
match property_name.as_str() {
|
||||
"bold" if value == Value::Bool(true) => {
|
||||
style.ansi = style.ansi.bold();
|
||||
"bold" => {
|
||||
if value == Value::Bool(true) {
|
||||
style.ansi = style.ansi.bold();
|
||||
}
|
||||
}
|
||||
"italic" if value == Value::Bool(true) => {
|
||||
style.ansi = style.ansi.italic();
|
||||
"italic" => {
|
||||
if value == Value::Bool(true) {
|
||||
style.ansi = style.ansi.italic();
|
||||
}
|
||||
}
|
||||
"underline" if value == Value::Bool(true) => {
|
||||
style.ansi = style.ansi.underline();
|
||||
"underline" => {
|
||||
if value == Value::Bool(true) {
|
||||
style.ansi = style.ansi.underline();
|
||||
}
|
||||
}
|
||||
"color" => {
|
||||
if let Some(color) = parse_color(value) {
|
||||
|
|
@ -216,11 +220,11 @@ fn parse_style(style: &mut Style, json: Value) {
|
|||
style.css = None;
|
||||
}
|
||||
|
||||
if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color()
|
||||
&& !terminal_supports_truecolor()
|
||||
{
|
||||
let ansi256 = Color::Ansi256(Ansi256Color(ansi256_from_rgb((red, green, blue))));
|
||||
style.ansi = style.ansi.fg_color(Some(ansi256));
|
||||
if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color() {
|
||||
if !terminal_supports_truecolor() {
|
||||
let ansi256 = Color::Ansi256(Ansi256Color(ansi256_from_rgb((red, green, blue))));
|
||||
style.ansi = style.ansi.fg_color(Some(ansi256));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,40 +312,15 @@ fn terminal_supports_truecolor() -> bool {
|
|||
.is_ok_and(|truecolor| truecolor == "truecolor" || truecolor == "24bit")
|
||||
}
|
||||
|
||||
/// The kind of HTML emitted when highlighting to HTML.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
pub enum HtmlOutput {
|
||||
/// A complete, self-contained document wrapping a plain
|
||||
/// `<div class="highlight"><pre><code>` block.
|
||||
Document,
|
||||
/// A complete document with a line-number column (a `<table>` layout).
|
||||
#[value(name = "line-numbers")]
|
||||
NumberedDocument,
|
||||
/// Only the code markup, without the surrounding document.
|
||||
Fragment,
|
||||
}
|
||||
|
||||
/// How token colors are applied in HTML output.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
pub enum HtmlStyling {
|
||||
/// `class="..."` spans plus a generated `<style>` carrying the theme's colors.
|
||||
Classes,
|
||||
/// `style="..."` spans with the colors inlined.
|
||||
Inline,
|
||||
/// `class="..."` spans with no colors emitted (supply your own stylesheet).
|
||||
Minimal,
|
||||
}
|
||||
|
||||
pub struct HighlightOptions {
|
||||
pub theme: Theme,
|
||||
pub check: bool,
|
||||
pub captures_path: Option<PathBuf>,
|
||||
/// `None` for regular output, `Some((layout, style))` when emitting HTML.
|
||||
pub html: Option<(HtmlOutput, HtmlStyling)>,
|
||||
pub inline_styles: bool,
|
||||
pub html: bool,
|
||||
pub quiet: bool,
|
||||
pub print_time: bool,
|
||||
pub cancellation_flag: Arc<AtomicUsize>,
|
||||
pub encoding: Option<TSInputEncoding>,
|
||||
}
|
||||
|
||||
pub fn highlight(
|
||||
|
|
@ -369,75 +348,46 @@ pub fn highlight(
|
|||
config.nonconformant_capture_names(&HashSet::new())
|
||||
};
|
||||
if names.is_empty() {
|
||||
info!("All highlight captures conform to standards.");
|
||||
eprintln!("All highlight captures conform to standards.");
|
||||
} else {
|
||||
warn!(
|
||||
"Non-standard highlight {} detected:\n* {}",
|
||||
eprintln!(
|
||||
"Non-standard highlight {} detected:",
|
||||
if names.len() > 1 {
|
||||
"captures"
|
||||
} else {
|
||||
"capture"
|
||||
},
|
||||
names.join("\n* ")
|
||||
}
|
||||
);
|
||||
for name in names {
|
||||
eprintln!("* {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source = fs::read(path)?;
|
||||
|
||||
fn is_utf16_le_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFF, 0xFE]
|
||||
}
|
||||
|
||||
fn is_utf16_be_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFE, 0xFF]
|
||||
}
|
||||
|
||||
let encoding = match opts.encoding {
|
||||
None if source.len() >= 2 => {
|
||||
if is_utf16_le_bom(&source[0..2]) {
|
||||
Some(ffi::TSInputEncodingUTF16LE)
|
||||
} else if is_utf16_be_bom(&source[0..2]) {
|
||||
Some(ffi::TSInputEncodingUTF16BE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => opts.encoding,
|
||||
};
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let time = Instant::now();
|
||||
let mut highlighter = Highlighter::new();
|
||||
let events = highlighter.highlight(
|
||||
config,
|
||||
&source,
|
||||
encoding,
|
||||
Some(&opts.cancellation_flag),
|
||||
|string| loader.highlight_config_for_injection_string(string),
|
||||
)?;
|
||||
let events =
|
||||
highlighter.highlight(config, &source, Some(&opts.cancellation_flag), |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})?;
|
||||
let theme = &opts.theme;
|
||||
|
||||
// A fragment is pure code markup, so it must not be prefixed with the filename.
|
||||
let html_fragment = opts
|
||||
.html
|
||||
.is_some_and(|(layout, _)| layout == HtmlOutput::Fragment);
|
||||
if !opts.quiet && print_name && !html_fragment {
|
||||
if !opts.quiet && print_name {
|
||||
writeln!(&mut stdout, "{name}")?;
|
||||
}
|
||||
|
||||
if let Some((layout, style)) = opts.html {
|
||||
if !opts.quiet && layout != HtmlOutput::Fragment {
|
||||
if opts.html {
|
||||
if !opts.quiet {
|
||||
writeln!(&mut stdout, "{HTML_HEAD_HEADER}")?;
|
||||
if layout == HtmlOutput::NumberedDocument {
|
||||
writeln!(&mut stdout, "{HTML_LINE_NUMBER_STYLE}")?;
|
||||
}
|
||||
if style == HtmlStyling::Classes {
|
||||
for (name, style) in theme.highlight_names.iter().zip(&theme.styles) {
|
||||
if let Some(css) = &style.css {
|
||||
writeln!(&mut stdout, " .{name} {{ {css}; }}")?;
|
||||
}
|
||||
writeln!(&mut stdout, " <style>")?;
|
||||
let names = theme.highlight_names.iter();
|
||||
let styles = theme.styles.iter();
|
||||
for (name, style) in names.zip(styles) {
|
||||
if let Some(css) = &style.css {
|
||||
writeln!(&mut stdout, " .{name} {{ {css}; }}")?;
|
||||
}
|
||||
}
|
||||
writeln!(&mut stdout, " </style>")?;
|
||||
|
|
@ -446,7 +396,7 @@ pub fn highlight(
|
|||
|
||||
let mut renderer = HtmlRenderer::new();
|
||||
renderer.render(events, &source, &move |highlight, output| {
|
||||
if style == HtmlStyling::Inline {
|
||||
if opts.inline_styles {
|
||||
output.extend(b"style='");
|
||||
output.extend(
|
||||
theme.styles[highlight.0]
|
||||
|
|
@ -454,6 +404,7 @@ pub fn highlight(
|
|||
.as_ref()
|
||||
.map_or_else(|| "".as_bytes(), |css_style| css_style.as_bytes()),
|
||||
);
|
||||
output.extend(b"'");
|
||||
} else {
|
||||
output.extend(b"class='");
|
||||
let mut parts = theme.highlight_names[highlight.0].split('.').peekable();
|
||||
|
|
@ -463,34 +414,21 @@ pub fn highlight(
|
|||
output.extend(b" ");
|
||||
}
|
||||
}
|
||||
output.extend(b"'");
|
||||
}
|
||||
output.extend(b"'");
|
||||
})?;
|
||||
|
||||
if !opts.quiet {
|
||||
if layout == HtmlOutput::NumberedDocument {
|
||||
writeln!(&mut stdout, "<table>")?;
|
||||
for (i, line) in renderer.lines().enumerate() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
"<tr><td class=line-number>{}</td><td class=line>{line}</td></tr>",
|
||||
i + 1,
|
||||
)?;
|
||||
}
|
||||
writeln!(&mut stdout, "</table>")?;
|
||||
} else {
|
||||
let mut body = renderer.lines().collect::<String>();
|
||||
if body.ends_with('\n') {
|
||||
body.pop();
|
||||
}
|
||||
writeln!(&mut stdout, "<table>")?;
|
||||
for (i, line) in renderer.lines().enumerate() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
"<div class=\"highlight\">\n<pre><code>{body}</code></pre>\n</div>",
|
||||
"<tr><td class=line-number>{}</td><td class=line>{line}</td></tr>",
|
||||
i + 1,
|
||||
)?;
|
||||
}
|
||||
if layout != HtmlOutput::Fragment {
|
||||
writeln!(&mut stdout, "{HTML_FOOTER}")?;
|
||||
}
|
||||
writeln!(&mut stdout, "</table>")?;
|
||||
writeln!(&mut stdout, "{HTML_FOOTER}")?;
|
||||
}
|
||||
} else {
|
||||
let mut style_stack = vec![theme.default_style().ansi];
|
||||
|
|
@ -513,7 +451,7 @@ pub fn highlight(
|
|||
}
|
||||
|
||||
if opts.print_time {
|
||||
info!("Time: {}ms", time.elapsed().as_millis());
|
||||
eprintln!("Time: {}ms", time.elapsed().as_millis());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -537,7 +475,7 @@ mod tests {
|
|||
assert_eq!(style.css, None);
|
||||
|
||||
// darkcyan is an ANSI color and is preserved
|
||||
unsafe { env::set_var("COLORTERM", "") };
|
||||
env::set_var("COLORTERM", "");
|
||||
parse_style(&mut style, Value::String(DARK_CYAN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -546,7 +484,7 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #00af87".to_string()));
|
||||
|
||||
// junglegreen is not an ANSI color and is preserved when the terminal supports it
|
||||
unsafe { env::set_var("COLORTERM", "truecolor") };
|
||||
env::set_var("COLORTERM", "truecolor");
|
||||
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -555,7 +493,7 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #26a69a".to_string()));
|
||||
|
||||
// junglegreen gets approximated as cadetblue when the terminal does not support it
|
||||
unsafe { env::set_var("COLORTERM", "") };
|
||||
env::set_var("COLORTERM", "");
|
||||
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -564,9 +502,9 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #26a69a".to_string()));
|
||||
|
||||
if let Ok(environment_variable) = original_environment_variable {
|
||||
unsafe { env::set_var("COLORTERM", environment_variable) };
|
||||
env::set_var("COLORTERM", environment_variable);
|
||||
} else {
|
||||
unsafe { env::remove_var("COLORTERM") };
|
||||
env::remove_var("COLORTERM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,16 +3,15 @@ use std::{
|
|||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc,
|
||||
mpsc, Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use glob::glob;
|
||||
|
||||
use crate::test::{TestEntry, parse_tests};
|
||||
use crate::test::{parse_tests, TestEntry};
|
||||
|
||||
pub enum CliInput {
|
||||
Paths(Vec<PathBuf>),
|
||||
|
|
@ -147,6 +146,7 @@ pub fn get_input(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_test_info(
|
||||
test_entry: &TestEntry,
|
||||
target_test: u32,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use std::io::Write;
|
||||
use log::{LevelFilter, Log, Metadata, Record};
|
||||
|
||||
use log::{Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
use crate::paint::{Paint, RED, YELLOW};
|
||||
|
||||
struct Logger;
|
||||
#[allow(dead_code)]
|
||||
struct Logger {
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
impl Log for Logger {
|
||||
fn enabled(&self, _: &Metadata) -> bool {
|
||||
|
|
@ -12,32 +11,20 @@ impl Log for Logger {
|
|||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
match record.level() {
|
||||
Level::Error => eprintln!("{} {}", Paint(RED, "Error:"), record.args()),
|
||||
Level::Warn => eprintln!("{} {}", Paint(YELLOW, "Warning:"), record.args()),
|
||||
Level::Info | Level::Debug => eprintln!("{}", record.args()),
|
||||
Level::Trace => eprintln!(
|
||||
"[{}] {}",
|
||||
record
|
||||
.module_path()
|
||||
.unwrap_or_default()
|
||||
.trim_start_matches("rust_tree_sitter_cli::"),
|
||||
record.args()
|
||||
),
|
||||
}
|
||||
eprintln!(
|
||||
"[{}] {}",
|
||||
record
|
||||
.module_path()
|
||||
.unwrap_or_default()
|
||||
.trim_start_matches("rust_tree_sitter_cli::"),
|
||||
record.args()
|
||||
);
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
let _ = stderr.flush();
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
log::set_boxed_logger(Box::new(Logger {})).unwrap();
|
||||
log::set_boxed_logger(Box::new(Logger { filter: None })).unwrap();
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
}
|
||||
|
||||
pub fn enable_debug() {
|
||||
log::set_max_level(LevelFilter::Debug);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,27 +0,0 @@
|
|||
use anstyle::{AnsiColor, Color, Style};
|
||||
|
||||
pub const RED: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)));
|
||||
pub const YELLOW: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
|
||||
|
||||
/// Wraps a `Display` value with a style; emits ANSI codes only when
|
||||
/// [`color_enabled`] is true.
|
||||
pub struct Paint<T>(pub Style, pub T);
|
||||
|
||||
pub fn color_enabled() -> bool {
|
||||
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*ENABLED.get_or_init(|| std::env::var_os("NO_COLOR").is_none_or(|v| v.is_empty()))
|
||||
}
|
||||
|
||||
pub fn paint<T>(color: Option<impl Into<Color>>, text: T) -> Paint<T> {
|
||||
Paint(Style::new().fg_color(color.map(Into::into)), text)
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Display> std::fmt::Display for Paint<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if color_enabled() {
|
||||
write!(f, "{}{}{:#}", self.0, self.1, self.0)
|
||||
} else {
|
||||
self.1.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,19 +8,18 @@ use std::{
|
|||
};
|
||||
|
||||
use anstyle::{AnsiColor, Color, RgbColor};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::ValueEnum;
|
||||
use log::info;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::{
|
||||
InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree, TreeCursor,
|
||||
ffi,
|
||||
ffi, InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree,
|
||||
TreeCursor,
|
||||
};
|
||||
|
||||
use crate::{fuzz::edits::Edit, paint::paint, util};
|
||||
use super::util;
|
||||
use crate::{fuzz::edits::Edit, test::paint};
|
||||
|
||||
#[derive(Debug, Default, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Stats {
|
||||
pub successful_parses: usize,
|
||||
pub total_parses: usize,
|
||||
|
|
@ -231,21 +230,10 @@ impl ParseSummary {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Debug, Default)]
|
||||
pub struct ParseStats {
|
||||
pub parse_summaries: Vec<ParseSummary>,
|
||||
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)]
|
||||
|
|
@ -286,10 +274,6 @@ pub fn parse_file_at_path(
|
|||
max_path_length: usize,
|
||||
opts: &mut ParseFileOptions,
|
||||
) -> Result<()> {
|
||||
#[expect(
|
||||
clippy::collection_is_never_read,
|
||||
reason = "value is held for its Drop side effect"
|
||||
)]
|
||||
let mut _log_session = None;
|
||||
parser.set_language(language)?;
|
||||
let mut source_code = fs::read(path).with_context(|| format!("Error reading {name:?}"))?;
|
||||
|
|
@ -301,6 +285,7 @@ pub fn parse_file_at_path(
|
|||
// Log to stderr if `--debug` was passed
|
||||
else if opts.debug != ParseDebugType::Quiet {
|
||||
let mut curr_version: usize = 0;
|
||||
let use_color = std::env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
let debug = opts.debug;
|
||||
parser.set_logger(Some(Box::new(move |log_type, message| {
|
||||
if debug == ParseDebugType::Normal {
|
||||
|
|
@ -309,13 +294,13 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
writeln!(&mut io::stderr(), "{message}").unwrap();
|
||||
} else {
|
||||
#[rustfmt::skip]
|
||||
let colors = &[
|
||||
AnsiColor::White, AnsiColor::Red, AnsiColor::Blue, AnsiColor::Green,
|
||||
AnsiColor::Cyan, AnsiColor::Yellow, AnsiColor::Magenta,
|
||||
AnsiColor::BrightWhite, AnsiColor::BrightRed, AnsiColor::BrightBlue,
|
||||
AnsiColor::BrightGreen, AnsiColor::BrightCyan, AnsiColor::BrightYellow,
|
||||
AnsiColor::BrightMagenta,
|
||||
AnsiColor::White,
|
||||
AnsiColor::Red,
|
||||
AnsiColor::Blue,
|
||||
AnsiColor::Green,
|
||||
AnsiColor::Cyan,
|
||||
AnsiColor::Yellow,
|
||||
];
|
||||
if message.starts_with("process version:") {
|
||||
let comma_idx = message.find(',').unwrap();
|
||||
|
|
@ -323,21 +308,30 @@ pub fn parse_file_at_path(
|
|||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
let color = Some(colors[curr_version % colors.len()]);
|
||||
let prefix = if log_type == LogType::Lex { " " } else { "" };
|
||||
writeln!(&mut io::stderr(), "{prefix}{}", paint(color, message)).unwrap();
|
||||
let color = if use_color {
|
||||
Some(colors[curr_version])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut out = if log_type == LogType::Lex {
|
||||
" ".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
out += &paint(color, message);
|
||||
writeln!(&mut io::stderr(), "{out}").unwrap();
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
let parse_time = Instant::now();
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn is_utf16_le_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFF, 0xFE]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn is_utf16_be_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFE, 0xFF]
|
||||
}
|
||||
|
|
@ -362,13 +356,13 @@ pub fn parse_file_at_path(
|
|||
// after the specified number of microseconds.
|
||||
let start_time = Instant::now();
|
||||
let progress_callback = &mut |_: &ParseState| {
|
||||
if let Some(cancellation_flag) = opts.cancellation_flag
|
||||
&& cancellation_flag.load(Ordering::SeqCst) != 0
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
if let Some(cancellation_flag) = opts.cancellation_flag {
|
||||
if cancellation_flag.load(Ordering::SeqCst) != 0 {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
if opts.timeout > 0 && start_time.elapsed().as_micros() > u128::from(opts.timeout) {
|
||||
if opts.timeout > 0 && start_time.elapsed().as_micros() > opts.timeout as u128 {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
|
|
@ -380,10 +374,8 @@ pub fn parse_file_at_path(
|
|||
let tree = match encoding {
|
||||
Some(encoding) if encoding == ffi::TSInputEncodingUTF16LE => {
|
||||
let source_code_utf16 = source_code
|
||||
.as_chunks::<2>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|&chunk| u16::from_le_bytes(chunk))
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
|
||||
.collect::<Vec<_>>();
|
||||
parser.parse_utf16_le_with_options(
|
||||
&mut |i, _| {
|
||||
|
|
@ -399,10 +391,8 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
Some(encoding) if encoding == ffi::TSInputEncodingUTF16BE => {
|
||||
let source_code_utf16 = source_code
|
||||
.as_chunks::<2>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|&chunk| u16::from_be_bytes(chunk))
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
|
||||
.collect::<Vec<_>>();
|
||||
parser.parse_utf16_be_with_options(
|
||||
&mut |i, _| {
|
||||
|
|
@ -431,11 +421,11 @@ pub fn parse_file_at_path(
|
|||
let parse_duration = parse_time.elapsed();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = io::BufWriter::with_capacity(64 * 1024, stdout.lock());
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
if let Some(mut tree) = tree {
|
||||
if opts.debug_graph && !opts.edits.is_empty() {
|
||||
info!("BEFORE:\n{}", String::from_utf8_lossy(&source_code));
|
||||
println!("BEFORE:\n{}", String::from_utf8_lossy(&source_code));
|
||||
}
|
||||
|
||||
let edit_time = Instant::now();
|
||||
|
|
@ -445,7 +435,7 @@ pub fn parse_file_at_path(
|
|||
tree = parser.parse(&source_code, Some(&tree)).unwrap();
|
||||
|
||||
if opts.debug_graph {
|
||||
info!("AFTER {i}:\n{}", String::from_utf8_lossy(&source_code));
|
||||
println!("AFTER {i}:\n{}", String::from_utf8_lossy(&source_code));
|
||||
}
|
||||
}
|
||||
let edit_duration = edit_time.elapsed();
|
||||
|
|
@ -508,27 +498,21 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
}
|
||||
cursor.reset(tree.root_node());
|
||||
writeln!(&mut stdout)?;
|
||||
println!();
|
||||
}
|
||||
|
||||
if opts.output == ParseOutput::Cst {
|
||||
render_cst(&source_code, &tree, &mut cursor, opts, &mut stdout)?;
|
||||
println!();
|
||||
}
|
||||
|
||||
if opts.output == ParseOutput::Xml {
|
||||
let mut needs_newline = false;
|
||||
let mut indent_level = 2;
|
||||
let mut indent_level = 0;
|
||||
let mut did_visit_children = false;
|
||||
let mut had_named_children = false;
|
||||
let mut tags = Vec::<&str>::new();
|
||||
|
||||
// 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())?;
|
||||
|
||||
writeln!(&mut stdout, "<?xml version=\"1.0\"?>")?;
|
||||
loop {
|
||||
let node = cursor.node();
|
||||
let is_named = node.is_named();
|
||||
|
|
@ -542,10 +526,10 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
write!(&mut stdout, "</{}>", tag.expect("there is a tag"))?;
|
||||
// we only write a line in the case where it's the last sibling
|
||||
if let Some(parent) = node.parent()
|
||||
&& parent.child(parent.child_count() - 1).unwrap() == node
|
||||
{
|
||||
stdout.write_all(b"\n")?;
|
||||
if let Some(parent) = node.parent() {
|
||||
if parent.child(parent.child_count() as u32 - 1).unwrap() == node {
|
||||
stdout.write_all(b"\n")?;
|
||||
}
|
||||
}
|
||||
needs_newline = true;
|
||||
}
|
||||
|
|
@ -579,11 +563,11 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
let start = node.start_position();
|
||||
let end = node.end_position();
|
||||
write!(
|
||||
&mut stdout,
|
||||
" srow=\"{}\" scol=\"{}\" erow=\"{}\" ecol=\"{}\">",
|
||||
start.row, start.column, end.row, end.column
|
||||
)?;
|
||||
write!(&mut stdout, " srow=\"{}\"", start.row)?;
|
||||
write!(&mut stdout, " scol=\"{}\"", start.column)?;
|
||||
write!(&mut stdout, " erow=\"{}\"", end.row)?;
|
||||
write!(&mut stdout, " ecol=\"{}\"", end.column)?;
|
||||
write!(&mut stdout, ">")?;
|
||||
tags.push(node.kind());
|
||||
needs_newline = true;
|
||||
}
|
||||
|
|
@ -607,14 +591,8 @@ 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());
|
||||
println!();
|
||||
}
|
||||
|
||||
if opts.output == ParseOutput::Dot {
|
||||
|
|
@ -672,9 +650,10 @@ pub fn parse_file_at_path(
|
|||
width = max_path_length
|
||||
)?;
|
||||
if let Some(node) = first_error {
|
||||
let node_kind = node.kind();
|
||||
let mut node_text = String::with_capacity(node_kind.len());
|
||||
for c in node_kind.chars() {
|
||||
let start = node.start_position();
|
||||
let end = node.end_position();
|
||||
let mut node_text = String::new();
|
||||
for c in node.kind().chars() {
|
||||
if let Some(escaped) = escape_invisible(c) {
|
||||
node_text += escaped;
|
||||
} else {
|
||||
|
|
@ -691,9 +670,6 @@ pub fn parse_file_at_path(
|
|||
} else {
|
||||
write!(&mut stdout, "{node_text}")?;
|
||||
}
|
||||
|
||||
let start = node.start_position();
|
||||
let end = node.end_position();
|
||||
write!(
|
||||
&mut stdout,
|
||||
" [{}, {}] - [{}, {}])",
|
||||
|
|
@ -774,19 +750,15 @@ pub fn render_cst<'a, 'b: 'a>(
|
|||
cursor: &mut TreeCursor<'a>,
|
||||
opts: &ParseFileOptions,
|
||||
out: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let lossy_source_code = String::from_utf8_lossy(source_code);
|
||||
let total_width = lossy_source_code
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(row, col)| {
|
||||
row.checked_ilog10().unwrap_or(0) as usize
|
||||
+ col.len().checked_ilog10().unwrap_or(0) as usize
|
||||
+ 1
|
||||
})
|
||||
.map(|(row, col)| (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1)
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
let mut indent_level = usize::from(!opts.no_ranges);
|
||||
let mut indent_level = 1;
|
||||
let mut did_visit_children = false;
|
||||
let mut in_error = false;
|
||||
loop {
|
||||
|
|
@ -827,19 +799,19 @@ pub fn render_cst<'a, 'b: 'a>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
struct CstNodeText<'a>(&'a str);
|
||||
|
||||
impl std::fmt::Display for CstNodeText<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::fmt::Write as _;
|
||||
for c in self.0.chars() {
|
||||
match escape_invisible(c).or_else(|| escape_delimiter(c)) {
|
||||
Some(esc) => f.write_str(esc)?,
|
||||
None => f.write_char(c)?,
|
||||
fn render_node_text(source: &str) -> String {
|
||||
source
|
||||
.chars()
|
||||
.fold(String::with_capacity(source.len()), |mut acc, c| {
|
||||
if let Some(esc) = escape_invisible(c) {
|
||||
acc.push_str(esc);
|
||||
} else if let Some(esc) = escape_delimiter(c) {
|
||||
acc.push_str(esc);
|
||||
} else {
|
||||
acc.push(c);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
fn write_node_text(
|
||||
|
|
@ -850,21 +822,21 @@ fn write_node_text(
|
|||
source: &str,
|
||||
color: Option<impl Into<Color> + Copy>,
|
||||
text_info: (usize, usize),
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let (total_width, indent_level) = text_info;
|
||||
let (quote, quote_color) = if is_named {
|
||||
('`', opts.parse_theme.backtick)
|
||||
} else {
|
||||
('\"', color.map(std::convert::Into::into))
|
||||
('\"', color.map(|c| c.into()))
|
||||
};
|
||||
|
||||
if !is_named {
|
||||
write!(
|
||||
out,
|
||||
"{}{}{}",
|
||||
paint(quote_color, quote),
|
||||
paint(color, CstNodeText(source)),
|
||||
paint(quote_color, quote),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
paint(color, &render_node_text(source)),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
)?;
|
||||
} else {
|
||||
let multiline = source.contains('\n');
|
||||
|
|
@ -883,128 +855,103 @@ fn write_node_text(
|
|||
} else {
|
||||
0
|
||||
};
|
||||
if multiline {
|
||||
writeln!(out)?;
|
||||
if !opts.no_ranges {
|
||||
write!(
|
||||
out,
|
||||
"{}",
|
||||
CstNodeRange {
|
||||
opts,
|
||||
has_field_name: cursor.field_name().is_some(),
|
||||
is_named,
|
||||
is_multiline: true,
|
||||
total_width,
|
||||
range: node_range,
|
||||
}
|
||||
)?;
|
||||
}
|
||||
for _ in 0..=indent_level {
|
||||
write!(out, " ")?;
|
||||
}
|
||||
let formatted_line = render_line_feed(line, opts);
|
||||
if !opts.no_ranges {
|
||||
write!(
|
||||
out,
|
||||
"{}{}{}{}{}{}",
|
||||
if multiline { "\n" } else { "" },
|
||||
if multiline {
|
||||
render_node_range(opts, cursor, is_named, true, total_width, node_range)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if multiline {
|
||||
" ".repeat(indent_level + 1)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
paint(quote_color, &String::from(quote)),
|
||||
&paint(color, &render_node_text(&formatted_line)),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
)?;
|
||||
} else {
|
||||
write!(out, " ")?;
|
||||
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)),
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
out,
|
||||
"{}{}{}",
|
||||
paint(quote_color, quote),
|
||||
paint(color, CstLineFeed { source: line, opts }),
|
||||
paint(quote_color, quote),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct CstLineFeed<'src, 'opt> {
|
||||
source: &'src str,
|
||||
opts: &'src ParseFileOptions<'opt>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CstLineFeed<'_, '_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[cfg(windows)]
|
||||
let lf = "\r\n";
|
||||
#[cfg(not(windows))]
|
||||
let lf = "\n";
|
||||
let painted = paint(self.opts.parse_theme.line_feed, CstNodeText(lf));
|
||||
let mut parts = self.source.split(lf);
|
||||
if let Some(first) = parts.next() {
|
||||
write!(f, "{}", CstNodeText(first))?;
|
||||
}
|
||||
for part in parts {
|
||||
write!(f, "{painted}{}", CstNodeText(part))?;
|
||||
}
|
||||
Ok(())
|
||||
fn render_line_feed(source: &str, opts: &ParseFileOptions) -> String {
|
||||
if cfg!(windows) {
|
||||
source.replace("\r\n", &paint(opts.parse_theme.line_feed, "\r\n"))
|
||||
} else {
|
||||
source.replace('\n', &paint(opts.parse_theme.line_feed, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
struct CstNodeRange<'src, 'opt> {
|
||||
opts: &'src ParseFileOptions<'opt>,
|
||||
has_field_name: bool,
|
||||
fn render_node_range(
|
||||
opts: &ParseFileOptions,
|
||||
cursor: &TreeCursor,
|
||||
is_named: bool,
|
||||
is_multiline: bool,
|
||||
total_width: usize,
|
||||
range: Range,
|
||||
}
|
||||
) -> String {
|
||||
let has_field_name = cursor.field_name().is_some();
|
||||
let range_color = if is_named && !is_multiline && !has_field_name {
|
||||
opts.parse_theme.row_color_named
|
||||
} else {
|
||||
opts.parse_theme.row_color
|
||||
};
|
||||
|
||||
impl std::fmt::Display for CstNodeRange<'_, '_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let start = self.range.start_point;
|
||||
let end = self.range.end_point;
|
||||
let range_color = if self.is_named && !self.is_multiline && !self.has_field_name {
|
||||
self.opts.parse_theme.row_color_named
|
||||
} else {
|
||||
self.opts.parse_theme.row_color
|
||||
};
|
||||
let remaining_width = |row: usize, col: usize| {
|
||||
(self
|
||||
.total_width
|
||||
.saturating_sub(row.checked_ilog10().unwrap_or(0) as usize)
|
||||
.saturating_sub(col.checked_ilog10().unwrap_or(0) as usize))
|
||||
.max(1)
|
||||
};
|
||||
let remaining_width_start = remaining_width(start.row, start.column);
|
||||
let remaining_width_end = remaining_width(end.row, end.column);
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
paint(
|
||||
range_color,
|
||||
format_args!(
|
||||
"{}:{}{:remaining_width_start$}- {}:{}{:remaining_width_end$}",
|
||||
start.row, start.column, ' ', end.row, end.column, ' ',
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
let remaining_width_start = (total_width
|
||||
- (range.start_point.row as f64).log10() as usize
|
||||
- (range.start_point.column as f64).log10() as usize)
|
||||
.max(1);
|
||||
let remaining_width_end = (total_width
|
||||
- (range.end_point.row as f64).log10() as usize
|
||||
- (range.end_point.column as f64).log10() as usize)
|
||||
.max(1);
|
||||
paint(
|
||||
range_color,
|
||||
&format!(
|
||||
"{}:{}{:remaining_width_start$}- {}:{}{:remaining_width_end$}",
|
||||
range.start_point.row,
|
||||
range.start_point.column,
|
||||
' ',
|
||||
range.end_point.row,
|
||||
range.end_point.column,
|
||||
' ',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn cst_render_node(
|
||||
opts: &ParseFileOptions,
|
||||
cursor: &TreeCursor,
|
||||
cursor: &mut TreeCursor,
|
||||
source_code: &[u8],
|
||||
out: &mut impl Write,
|
||||
total_width: usize,
|
||||
indent_level: usize,
|
||||
in_error: bool,
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let node = cursor.node();
|
||||
let is_named = node.is_named();
|
||||
if !opts.no_ranges {
|
||||
write!(
|
||||
out,
|
||||
"{}",
|
||||
CstNodeRange {
|
||||
opts,
|
||||
has_field_name: cursor.field_name().is_some(),
|
||||
is_named,
|
||||
is_multiline: false,
|
||||
total_width,
|
||||
range: node.range(),
|
||||
}
|
||||
render_node_range(opts, cursor, is_named, false, total_width, node.range())
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
|
|
@ -1022,7 +969,7 @@ fn cst_render_node(
|
|||
write!(
|
||||
out,
|
||||
"{}",
|
||||
paint(opts.parse_theme.field, format_args!("{field_name}: "))
|
||||
paint(opts.parse_theme.field, &format!("{field_name}: "))
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
@ -1037,9 +984,10 @@ fn cst_render_node(
|
|||
} else {
|
||||
opts.parse_theme.node_kind
|
||||
};
|
||||
write!(out, "{}", paint(kind_color, node.kind()))?;
|
||||
write!(out, "{}", paint(kind_color, node.kind()),)?;
|
||||
|
||||
if node.child_count() == 0 {
|
||||
write!(out, " ")?;
|
||||
// Node text from a pattern or external scanner
|
||||
write_node_text(
|
||||
opts,
|
||||
|
|
@ -1093,13 +1041,10 @@ pub fn perform_edit(tree: &mut Tree, input: &mut Vec<u8>, edit: &Edit) -> Result
|
|||
|
||||
fn parse_edit_flag(source_code: &[u8], flag: &str) -> Result<Edit> {
|
||||
let error = || {
|
||||
anyhow!(
|
||||
concat!(
|
||||
"Invalid edit string '{}'. ",
|
||||
"Edit strings must match the pattern '<START_BYTE_OR_POSITION> <REMOVED_LENGTH> <NEW_TEXT>'"
|
||||
),
|
||||
flag
|
||||
)
|
||||
anyhow!(concat!(
|
||||
"Invalid edit string '{}'. ",
|
||||
"Edit strings must match the pattern '<START_BYTE_OR_POSITION> <REMOVED_LENGTH> <NEW_TEXT>'"
|
||||
), flag)
|
||||
};
|
||||
|
||||
// Three whitespace-separated parts:
|
||||
|
|
@ -1137,25 +1082,30 @@ fn parse_edit_flag(source_code: &[u8], flag: &str) -> Result<Edit> {
|
|||
|
||||
pub fn offset_for_position(input: &[u8], position: Point) -> Result<usize> {
|
||||
let mut row = 0;
|
||||
let mut line_start = 0;
|
||||
for line_end in memchr::memchr_iter(b'\n', input) {
|
||||
if row == position.row {
|
||||
if position.column > line_end - line_start {
|
||||
return Err(anyhow!("Failed to address a column: {}", position.column));
|
||||
let mut offset = 0;
|
||||
let mut iter = memchr::memchr_iter(b'\n', input);
|
||||
loop {
|
||||
if let Some(pos) = iter.next() {
|
||||
if row < position.row {
|
||||
row += 1;
|
||||
offset = pos;
|
||||
continue;
|
||||
}
|
||||
return Ok(line_start + position.column);
|
||||
}
|
||||
row += 1;
|
||||
line_start = line_end + 1;
|
||||
offset += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if row != position.row {
|
||||
if position.row - row > 0 {
|
||||
return Err(anyhow!("Failed to address a row: {}", position.row));
|
||||
}
|
||||
if position.column > input.len() - line_start {
|
||||
if let Some(pos) = iter.next() {
|
||||
if (pos - offset < position.column) || (input[offset] == b'\n' && position.column > 0) {
|
||||
return Err(anyhow!("Failed to address a column: {}", position.column));
|
||||
}
|
||||
} else if input.len() - offset < position.column {
|
||||
return Err(anyhow!("Failed to address a column over the end"));
|
||||
}
|
||||
Ok(line_start + position.column)
|
||||
Ok(offset + position.column)
|
||||
}
|
||||
|
||||
pub fn position_for_offset(input: &[u8], offset: usize) -> Result<Point> {
|
||||
|
|
@ -1175,45 +1125,3 @@ pub fn position_for_offset(input: &[u8], offset: usize) -> Result<Point> {
|
|||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{offset_for_position, parse_edit_flag};
|
||||
use tree_sitter::Point;
|
||||
|
||||
#[test]
|
||||
fn offset_for_position_uses_zero_based_line_and_column_coordinates() {
|
||||
let input = b"abc\n";
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 0 }).unwrap(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 1 }).unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 3 }).unwrap(),
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 1, column: 0 }).unwrap(),
|
||||
4
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_for_position_rejects_out_of_bounds_coordinates() {
|
||||
let input = b"abc\ndef";
|
||||
assert!(offset_for_position(input, Point { row: 0, column: 4 }).is_err());
|
||||
assert!(offset_for_position(input, Point { row: 2, column: 0 }).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_edit_flag_resolves_first_line_positions() {
|
||||
let edit = parse_edit_flag(b"abc\n", "0,0 0 X").unwrap();
|
||||
assert_eq!(edit.position, 0);
|
||||
assert_eq!(edit.deleted_length, 0);
|
||||
assert_eq!(edit.inserted_text, b"X");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@
|
|||
--light-scrollbar-track: #f1f1f1;
|
||||
--light-scrollbar-thumb: #c1c1c1;
|
||||
--light-scrollbar-thumb-hover: #a8a8a8;
|
||||
--light-tree-row-bg: #e3f2fd;
|
||||
|
||||
|
||||
--dark-bg: #1d1f21;
|
||||
--dark-border: #2d2d2d;
|
||||
--dark-text: #c5c8c6;
|
||||
|
|
@ -29,7 +28,6 @@
|
|||
--dark-scrollbar-track: #25282c;
|
||||
--dark-scrollbar-thumb: #4a4d51;
|
||||
--dark-scrollbar-thumb-hover: #5a5d61;
|
||||
--dark-tree-row-bg: #373737;
|
||||
|
||||
--primary-color: #0550ae;
|
||||
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
||||
|
|
@ -44,7 +42,6 @@
|
|||
--text-color: var(--dark-text);
|
||||
--panel-bg: var(--dark-panel-bg);
|
||||
--code-bg: var(--dark-code-bg);
|
||||
--tree-row-bg: var(--dark-tree-row-bg);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
|
|
@ -53,7 +50,6 @@
|
|||
--text-color: var(--light-text);
|
||||
--panel-bg: white;
|
||||
--code-bg: white;
|
||||
--tree-row-bg: var(--light-tree-row-bg);
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
|
|
@ -279,7 +275,7 @@
|
|||
}
|
||||
|
||||
#output-container a.highlighted {
|
||||
background-color: #cae2ff;
|
||||
background-color: #d9d9d9;
|
||||
color: red;
|
||||
border-radius: 3px;
|
||||
text-decoration: underline;
|
||||
|
|
@ -350,7 +346,7 @@
|
|||
}
|
||||
|
||||
& #output-container a.highlighted {
|
||||
background-color: #656669;
|
||||
background-color: #373b41;
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
|
@ -377,9 +373,6 @@
|
|||
color: var(--dark-text);
|
||||
}
|
||||
}
|
||||
.tree-row:has(.highlighted) {
|
||||
background-color: var(--tree-row-bg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -470,7 +463,7 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.19.0/clusterize.min.js"></script>
|
||||
|
||||
<script>LANGUAGE_BASE_URL = ".";</script>
|
||||
<script>LANGUAGE_BASE_URL = "";</script>
|
||||
<script type="module" src="playground.js"></script>
|
||||
<script type="module">
|
||||
import * as TreeSitter from './web-tree-sitter.js';
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ use std::{
|
|||
env, fs,
|
||||
net::TcpListener,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
str::{self, FromStr as _},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use log::{error, info};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use tiny_http::{Header, Response, Server};
|
||||
|
||||
use super::wasm;
|
||||
|
|
@ -47,79 +46,13 @@ fn get_main_html(tree_sitter_dir: Option<&Path>) -> Cow<'static, [u8]> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn export(grammar_path: &Path, export_path: &Path) -> Result<()> {
|
||||
let (grammar_name, language_wasm) = wasm::load_language_wasm_file(grammar_path)?;
|
||||
|
||||
fs::create_dir_all(export_path).with_context(|| {
|
||||
format!(
|
||||
"Failed to create export directory: {}",
|
||||
export_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let tree_sitter_dir = env::var("TREE_SITTER_BASE_DIR").map(PathBuf::from).ok();
|
||||
|
||||
let playground_js = get_playground_js(tree_sitter_dir.as_deref());
|
||||
let lib_js = get_lib_js(tree_sitter_dir.as_deref());
|
||||
let lib_wasm = get_lib_wasm(tree_sitter_dir.as_deref());
|
||||
|
||||
let has_local_playground_js = !playground_js.is_empty();
|
||||
let has_local_lib_js = !lib_js.is_empty();
|
||||
let has_local_lib_wasm = !lib_wasm.is_empty();
|
||||
|
||||
let mut main_html = str::from_utf8(&get_main_html(tree_sitter_dir.as_deref()))
|
||||
.unwrap()
|
||||
.replace("THE_LANGUAGE_NAME", &grammar_name);
|
||||
|
||||
if !has_local_playground_js {
|
||||
main_html = main_html.replace(
|
||||
r#"<script type="module" src="playground.js"></script>"#,
|
||||
r#"<script type="module" src="https://tree-sitter.github.io/tree-sitter/assets/js/playground.js"></script>"#
|
||||
);
|
||||
}
|
||||
if !has_local_lib_js {
|
||||
main_html = main_html.replace(
|
||||
"import * as TreeSitter from './web-tree-sitter.js';",
|
||||
"import * as TreeSitter from 'https://tree-sitter.github.io/web-tree-sitter.js';",
|
||||
);
|
||||
}
|
||||
|
||||
fs::write(export_path.join("index.html"), main_html.as_bytes())
|
||||
.with_context(|| "Failed to write index.html")?;
|
||||
|
||||
fs::write(export_path.join("tree-sitter-parser.wasm"), language_wasm)
|
||||
.with_context(|| "Failed to write parser wasm file")?;
|
||||
|
||||
if has_local_playground_js {
|
||||
fs::write(export_path.join("playground.js"), playground_js)
|
||||
.with_context(|| "Failed to write playground.js")?;
|
||||
}
|
||||
|
||||
if has_local_lib_js {
|
||||
fs::write(export_path.join("web-tree-sitter.js"), lib_js)
|
||||
.with_context(|| "Failed to write web-tree-sitter.js")?;
|
||||
}
|
||||
|
||||
if has_local_lib_wasm {
|
||||
fs::write(export_path.join("web-tree-sitter.wasm"), lib_wasm)
|
||||
.with_context(|| "Failed to write web-tree-sitter.wasm")?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Exported playground to {}",
|
||||
export_path.canonicalize()?.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serve(grammar_path: &Path, open_in_browser: bool) -> Result<()> {
|
||||
let server = get_server()?;
|
||||
let (grammar_name, language_wasm) = wasm::load_language_wasm_file(grammar_path)?;
|
||||
let url = format!("http://{}", server.server_addr());
|
||||
info!("Started playground on: {url}");
|
||||
println!("Started playground on: {url}");
|
||||
if open_in_browser && webbrowser::open(&url).is_err() {
|
||||
error!("Failed to open '{url}' in a web browser");
|
||||
eprintln!("Failed to open '{url}' in a web browser");
|
||||
}
|
||||
|
||||
let tree_sitter_dir = env::var("TREE_SITTER_BASE_DIR").map(PathBuf::from).ok();
|
||||
|
|
|
|||
|
|
@ -6,64 +6,51 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use anstyle::AnsiColor;
|
||||
use anyhow::{Context, Result};
|
||||
use log::warn;
|
||||
use streaming_iterator::StreamingIterator;
|
||||
use tree_sitter::{Language, Parser, Point, Query, QueryCursor};
|
||||
|
||||
use crate::{
|
||||
query_testing::{self, to_utf8_point},
|
||||
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||
test::paint,
|
||||
};
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn query_file_at_path(
|
||||
language: &Language,
|
||||
path: &Path,
|
||||
name: &str,
|
||||
query_path: &Path,
|
||||
opts: &QueryFileOptions,
|
||||
test_summary: Option<&mut TestSummary>,
|
||||
ordered_captures: bool,
|
||||
byte_range: Option<Range<usize>>,
|
||||
point_range: Option<Range<Point>>,
|
||||
should_test: bool,
|
||||
quiet: bool,
|
||||
print_time: bool,
|
||||
stdin: bool,
|
||||
) -> Result<()> {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = io::BufWriter::with_capacity(64 * 1024, stdout.lock());
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let query_source = fs::read_to_string(query_path)
|
||||
.with_context(|| format!("Error reading query file {}", query_path.display()))?;
|
||||
let query = Query::new(language, &query_source).with_context(|| "Query compilation failed")?;
|
||||
|
||||
let mut query_cursor = QueryCursor::new();
|
||||
if let Some(ref range) = opts.byte_range {
|
||||
query_cursor.set_byte_range(range.clone());
|
||||
if let Some(range) = byte_range {
|
||||
query_cursor.set_byte_range(range);
|
||||
}
|
||||
if let Some(ref range) = opts.point_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());
|
||||
if let Some(range) = point_range {
|
||||
query_cursor.set_point_range(range);
|
||||
}
|
||||
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language)?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
let should_test = test_summary.is_some();
|
||||
|
||||
if !should_test && !opts.stdin {
|
||||
if !should_test && !stdin {
|
||||
writeln!(&mut stdout, "{name}")?;
|
||||
}
|
||||
|
||||
|
|
@ -72,48 +59,46 @@ pub fn query_file_at_path(
|
|||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
if opts.ordered_captures {
|
||||
if ordered_captures {
|
||||
let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
|
||||
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];
|
||||
if !opts.quiet && !should_test {
|
||||
if !quiet && !should_test {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
|
||||
mat.pattern_index,
|
||||
capture.index,
|
||||
capture.node.start_position(),
|
||||
capture.node.end_position(),
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
}
|
||||
if should_test {
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||
});
|
||||
&mut stdout,
|
||||
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
|
||||
mat.pattern_index,
|
||||
capture.index,
|
||||
capture.node.start_position(),
|
||||
capture.node.end_position(),
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
}
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
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 {
|
||||
let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
|
||||
while let Some(m) = matches.next() {
|
||||
if !opts.quiet && !should_test {
|
||||
if !quiet && !should_test {
|
||||
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
||||
}
|
||||
for capture in m.captures() {
|
||||
for capture in m.captures {
|
||||
let start = capture.node.start_position();
|
||||
let end = capture.node.end_position();
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !opts.quiet && !should_test {
|
||||
if !quiet && !should_test {
|
||||
if end.row == start.row {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
|
||||
capture.index,
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
&mut stdout,
|
||||
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
|
||||
capture.index,
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
|
|
@ -121,54 +106,41 @@ pub fn query_file_at_path(
|
|||
)?;
|
||||
}
|
||||
}
|
||||
if should_test {
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
|
||||
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
|
||||
});
|
||||
}
|
||||
results.push(query_testing::CaptureInfo {
|
||||
name: (*capture_name).to_string(),
|
||||
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() {
|
||||
warn!("Query exceeded maximum number of in-progress captures!");
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
" WARNING: Query exceeded maximum number of in-progress captures!"
|
||||
)?;
|
||||
}
|
||||
if should_test {
|
||||
let path_name = if opts.stdin {
|
||||
let path_name = if stdin {
|
||||
"stdin"
|
||||
} else {
|
||||
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();
|
||||
let assertions =
|
||||
query_testing::parse_position_comments(&mut parser, language, source_code.as_slice())?;
|
||||
match query_testing::assert_expected_captures(&results, &assertions) {
|
||||
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
|
||||
Ok(assertion_count) => {
|
||||
test_summary.query_results.add_case(TestResult {
|
||||
name: path_name.to_string(),
|
||||
info: TestInfo::AssertionTest {
|
||||
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||
test_num: test_summary.test_num,
|
||||
},
|
||||
});
|
||||
println!(
|
||||
" ✓ {} ({} assertions)",
|
||||
paint(Some(AnsiColor::Green), path_name),
|
||||
assertion_count
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
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,
|
||||
},
|
||||
});
|
||||
println!(" ✗ {}", paint(Some(AnsiColor::Red), path_name));
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.print_time {
|
||||
if print_time {
|
||||
writeln!(&mut stdout, "{:?}", start.elapsed())?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::LazyLock;
|
||||
use std::{fs, path::Path, sync::LazyLock};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use bstr::{BStr, ByteSlice};
|
||||
use regex::Regex;
|
||||
use tree_sitter::{Language, Parser, Point};
|
||||
|
|
@ -106,66 +106,66 @@ pub fn parse_position_comments(
|
|||
let node = cursor.node();
|
||||
|
||||
// Find every comment node.
|
||||
if node.kind().to_lowercase().contains("comment")
|
||||
&& let Ok(text) = node.utf8_text(source)
|
||||
{
|
||||
let mut position = node.start_position();
|
||||
if position.row > 0 {
|
||||
// Find the arrow character ("^" or "<-") in the comment. A left arrow
|
||||
// refers to the column where the comment node starts. An up arrow refers
|
||||
// to its own column.
|
||||
let mut has_left_caret = false;
|
||||
let mut has_arrow = false;
|
||||
let mut negative = false;
|
||||
let mut arrow_end = 0;
|
||||
let mut arrow_count = 1;
|
||||
for (i, c) in text.char_indices() {
|
||||
arrow_end = i + 1;
|
||||
if c == '-' && has_left_caret {
|
||||
has_arrow = true;
|
||||
break;
|
||||
if node.kind().to_lowercase().contains("comment") {
|
||||
if let Ok(text) = node.utf8_text(source) {
|
||||
let mut position = node.start_position();
|
||||
if position.row > 0 {
|
||||
// Find the arrow character ("^" or "<-") in the comment. A left arrow
|
||||
// refers to the column where the comment node starts. An up arrow refers
|
||||
// to its own column.
|
||||
let mut has_left_caret = false;
|
||||
let mut has_arrow = false;
|
||||
let mut negative = false;
|
||||
let mut arrow_end = 0;
|
||||
let mut arrow_count = 1;
|
||||
for (i, c) in text.char_indices() {
|
||||
arrow_end = i + 1;
|
||||
if c == '-' && has_left_caret {
|
||||
has_arrow = true;
|
||||
break;
|
||||
}
|
||||
if c == '^' {
|
||||
has_arrow = true;
|
||||
position.column += i;
|
||||
// Continue counting remaining arrows and update their end column
|
||||
for (_, c) in text[arrow_end..].char_indices() {
|
||||
if c != '^' {
|
||||
arrow_end += arrow_count - 1;
|
||||
break;
|
||||
}
|
||||
arrow_count += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
has_left_caret = c == '<';
|
||||
}
|
||||
if c == '^' {
|
||||
has_arrow = true;
|
||||
position.column += i;
|
||||
// Continue counting remaining arrows and update their end column
|
||||
for (_, c) in text[arrow_end..].char_indices() {
|
||||
if c != '^' {
|
||||
arrow_end += arrow_count - 1;
|
||||
|
||||
// find any ! after arrows but before capture name
|
||||
if has_arrow {
|
||||
for (i, c) in text[arrow_end..].char_indices() {
|
||||
if c == '!' {
|
||||
negative = true;
|
||||
arrow_end += i + 1;
|
||||
break;
|
||||
} else if !c.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
arrow_count += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
has_left_caret = c == '<';
|
||||
}
|
||||
|
||||
// find any ! after arrows but before capture name
|
||||
if has_arrow {
|
||||
for (i, c) in text[arrow_end..].char_indices() {
|
||||
if c == '!' {
|
||||
negative = true;
|
||||
arrow_end += i + 1;
|
||||
break;
|
||||
} else if !c.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the comment node contains an arrow and a highlight name, record the
|
||||
// highlight name and the position.
|
||||
if let (true, Some(mat)) =
|
||||
(has_arrow, CAPTURE_NAME_REGEX.find(&text[arrow_end..]))
|
||||
{
|
||||
assertion_ranges.push((node.start_position(), node.end_position()));
|
||||
result.push(Assertion {
|
||||
position: to_utf8_point(position, source),
|
||||
length: arrow_count,
|
||||
negative,
|
||||
expected_capture_name: mat.as_str().to_string(),
|
||||
});
|
||||
// If the comment node contains an arrow and a highlight name, record the
|
||||
// highlight name and the position.
|
||||
if let (true, Some(mat)) =
|
||||
(has_arrow, CAPTURE_NAME_REGEX.find(&text[arrow_end..]))
|
||||
{
|
||||
assertion_ranges.push((node.start_position(), node.end_position()));
|
||||
result.push(Assertion {
|
||||
position: to_utf8_point(position, source),
|
||||
length: arrow_count,
|
||||
negative,
|
||||
expected_capture_name: mat.as_str().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,14 +219,19 @@ pub fn parse_position_comments(
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn assert_expected_captures(infos: &[CaptureInfo], assertions: &[Assertion]) -> Result<usize> {
|
||||
for assertion in assertions {
|
||||
pub fn assert_expected_captures(
|
||||
infos: &[CaptureInfo],
|
||||
path: &Path,
|
||||
parser: &mut Parser,
|
||||
language: &Language,
|
||||
) -> Result<usize> {
|
||||
let contents = fs::read_to_string(path)?;
|
||||
let pairs = parse_position_comments(parser, language, contents.as_bytes())?;
|
||||
for assertion in &pairs {
|
||||
if let Some(found) = &infos.iter().find(|p| {
|
||||
let assertion_end = Utf8Point::new(
|
||||
assertion.position.row,
|
||||
assertion.position.column + assertion.length - 1,
|
||||
);
|
||||
assertion.position >= p.start && assertion_end < p.end
|
||||
assertion.position >= p.start
|
||||
&& (assertion.position.row < p.end.row
|
||||
|| assertion.position.column + assertion.length - 1 < p.end.column)
|
||||
}) {
|
||||
if assertion.expected_capture_name != found.name && found.name != "name" {
|
||||
return Err(anyhow!(
|
||||
|
|
@ -245,24 +250,5 @@ pub fn assert_expected_captures(infos: &[CaptureInfo], assertions: &[Assertion])
|
|||
));
|
||||
}
|
||||
}
|
||||
Ok(assertions.len())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Assertion, CaptureInfo, Utf8Point, assert_expected_captures};
|
||||
|
||||
#[test]
|
||||
fn test_assertion_after_multiline_capture_does_not_match() {
|
||||
let captures = [CaptureInfo {
|
||||
name: "foo".to_string(),
|
||||
start: Utf8Point::new(0, 0),
|
||||
end: Utf8Point::new(1, 1),
|
||||
}];
|
||||
let assertions = [Assertion::new(2, 0, 1, false, "foo".to_string())];
|
||||
|
||||
let result = assert_expected_captures(&captures, &assertions);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
Ok(pairs.len())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use std::{
|
|||
fs,
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
str,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ pub fn generate_tags(
|
|||
&mut stdout,
|
||||
"{indent_str}{:<10}\t | {:<8}\t{} {} - {} `{}`",
|
||||
str::from_utf8(&source[tag.name_range]).unwrap_or(""),
|
||||
config.syntax_type_name(tag.syntax_type_id),
|
||||
&config.syntax_type_name(tag.syntax_type_id),
|
||||
if tag.is_definition { "def" } else { "ref" },
|
||||
tag.span.start,
|
||||
tag.span.end,
|
||||
|
|
@ -58,7 +59,7 @@ pub fn generate_tags(
|
|||
if docs.len() > 120 {
|
||||
write!(&mut stdout, "\t{:?}...", docs.get(0..120).unwrap_or(""))?;
|
||||
} else {
|
||||
write!(&mut stdout, "\t{docs:?}")?;
|
||||
write!(&mut stdout, "\t{:?}", &docs)?;
|
||||
}
|
||||
}
|
||||
writeln!(&mut stdout)?;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
|
||||
[*.{json,toml,yml,gyp,xml}]
|
||||
[*.{json,toml,yml,gyp}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,ts}]
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
|
@ -31,10 +31,6 @@ indent_size = 4
|
|||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.java]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
|
|
|||
|
|
@ -1,40 +1,37 @@
|
|||
"""PARSER_DESCRIPTION"""
|
||||
|
||||
from importlib.resources import files as _files
|
||||
|
||||
from ._binding import language
|
||||
|
||||
|
||||
def _get_query(name, file):
|
||||
files = globals().get("_files")
|
||||
if files is None:
|
||||
from importlib.resources import files
|
||||
globals()["_files"] = files
|
||||
try:
|
||||
query = files(f"{__package__}") / file
|
||||
globals()[name] = query.read_text()
|
||||
except FileNotFoundError:
|
||||
globals()[name] = None
|
||||
query = _files(f"{__package__}.queries") / file
|
||||
globals()[name] = query.read_text()
|
||||
return globals()[name]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "HIGHLIGHTS_QUERY":
|
||||
return _get_query("HIGHLIGHTS_QUERY", "HIGHLIGHTS_QUERY_PATH")
|
||||
if name == "INJECTIONS_QUERY":
|
||||
return _get_query("INJECTIONS_QUERY", "INJECTIONS_QUERY_PATH")
|
||||
if name == "LOCALS_QUERY":
|
||||
return _get_query("LOCALS_QUERY", "LOCALS_QUERY_PATH")
|
||||
if name == "TAGS_QUERY":
|
||||
return _get_query("TAGS_QUERY", "TAGS_QUERY_PATH")
|
||||
# NOTE: uncomment these to include any queries that this grammar contains:
|
||||
|
||||
# if name == "HIGHLIGHTS_QUERY":
|
||||
# return _get_query("HIGHLIGHTS_QUERY", "highlights.scm")
|
||||
# if name == "INJECTIONS_QUERY":
|
||||
# return _get_query("INJECTIONS_QUERY", "injections.scm")
|
||||
# if name == "LOCALS_QUERY":
|
||||
# return _get_query("LOCALS_QUERY", "locals.scm")
|
||||
# if name == "TAGS_QUERY":
|
||||
# return _get_query("TAGS_QUERY", "tags.scm")
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"language",
|
||||
"HIGHLIGHTS_QUERY",
|
||||
"INJECTIONS_QUERY",
|
||||
"LOCALS_QUERY",
|
||||
"TAGS_QUERY",
|
||||
# "HIGHLIGHTS_QUERY",
|
||||
# "INJECTIONS_QUERY",
|
||||
# "LOCALS_QUERY",
|
||||
# "TAGS_QUERY",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
from typing import Final
|
||||
from typing_extensions import CapsuleType
|
||||
|
||||
HIGHLIGHTS_QUERY: Final[str] | None
|
||||
"""The syntax highlighting query for this grammar."""
|
||||
# NOTE: uncomment these to include any queries that this grammar contains:
|
||||
|
||||
INJECTIONS_QUERY: Final[str] | None
|
||||
"""The language injection query for this grammar."""
|
||||
# HIGHLIGHTS_QUERY: Final[str]
|
||||
# INJECTIONS_QUERY: Final[str]
|
||||
# LOCALS_QUERY: Final[str]
|
||||
# TAGS_QUERY: Final[str]
|
||||
|
||||
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."""
|
||||
def language() -> CapsuleType: ...
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
[package]
|
||||
authors = [ "PARSER_AUTHOR_NAME PARSER_AUTHOR_EMAIL" ]
|
||||
autoexamples = false
|
||||
categories = [ "parser-implementations", "parsing", "text-editors" ]
|
||||
description = "PARSER_DESCRIPTION"
|
||||
edition = "2024"
|
||||
keywords = [ "incremental", "parsing", "tree-sitter", "PARSER_NAME" ]
|
||||
license = "PARSER_LICENSE"
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
readme = "README.md"
|
||||
repository = "PARSER_URL"
|
||||
description = "PARSER_DESCRIPTION"
|
||||
version = "PARSER_VERSION"
|
||||
authors = ["PARSER_AUTHOR_NAME PARSER_AUTHOR_EMAIL"]
|
||||
license = "PARSER_LICENSE"
|
||||
readme = "README.md"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "PARSER_NAME"]
|
||||
categories = ["parser-implementations", "parsing", "text-editors"]
|
||||
repository = "PARSER_URL"
|
||||
edition = "2021"
|
||||
autoexamples = false
|
||||
|
||||
build = "bindings/rust/build.rs"
|
||||
include = [ "bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json", "/LICENSE" ]
|
||||
include = [
|
||||
"bindings/rust/*",
|
||||
"grammar.js",
|
||||
"queries/*",
|
||||
"src/*",
|
||||
"tree-sitter.json",
|
||||
"/LICENSE",
|
||||
]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/lib.rs"
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,18 @@ fn main() {
|
|||
let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate");
|
||||
};
|
||||
let Ok(wasm_src) =
|
||||
std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from)
|
||||
else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate");
|
||||
};
|
||||
|
||||
c_config.include(&wasm_headers);
|
||||
c_config.files([
|
||||
wasm_src.join("stdio.c"),
|
||||
wasm_src.join("stdlib.c"),
|
||||
wasm_src.join("string.c"),
|
||||
]);
|
||||
}
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
|
|
@ -26,21 +36,4 @@ fn main() {
|
|||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,51 +4,46 @@ pub fn build(b: *std.Build) !void {
|
|||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
var threaded: std.Io.Threaded = .init(b.allocator, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
const shared = b.option(bool, "build-shared", "Build a shared library") orelse true;
|
||||
const reuse_alloc = b.option(bool, "reuse-allocator", "Reuse the library allocator") orelse false;
|
||||
|
||||
const library_name = "tree-sitter-PARSER_NAME";
|
||||
|
||||
var grammar = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
});
|
||||
const lib: *std.Build.Step.Compile = b.addLibrary(.{
|
||||
.name = library_name,
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = grammar,
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
}),
|
||||
});
|
||||
|
||||
grammar.addCSourceFile(.{
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("src/parser.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
if (fileExists(b, io, "src/scanner.c")) {
|
||||
grammar.addCSourceFile(.{
|
||||
if (fileExists(b, "src/scanner.c")) {
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("src/scanner.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
}
|
||||
|
||||
if (reuse_alloc) {
|
||||
grammar.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", "");
|
||||
lib.root_module.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", "");
|
||||
}
|
||||
if (optimize == .Debug) {
|
||||
grammar.addCMacro("TREE_SITTER_DEBUG", "");
|
||||
lib.root_module.addCMacro("TREE_SITTER_DEBUG", "");
|
||||
}
|
||||
|
||||
grammar.addIncludePath(b.path("src"));
|
||||
lib.addIncludePath(b.path("src"));
|
||||
|
||||
b.installArtifact(lib);
|
||||
b.installFile("src/node-types.json", "node-types.json");
|
||||
|
||||
if (fileExists(b, io, "queries")) {
|
||||
if (fileExists(b, "queries")) {
|
||||
b.installDirectory(.{
|
||||
.source_dir = b.path("queries"),
|
||||
.install_dir = .prefix,
|
||||
|
|
@ -74,10 +69,16 @@ pub fn build(b: *std.Build) !void {
|
|||
tests.root_module.addImport(library_name, module);
|
||||
|
||||
// HACK: fetch tree-sitter dependency only when testing this module
|
||||
if (b.option(bool, "test", "Fetch test dependencies") orelse false) {
|
||||
const ts_dep = b.lazyDependency("tree_sitter", .{});
|
||||
if (ts_dep) |dep|
|
||||
tests.root_module.addImport("tree-sitter", dep.module("tree_sitter"));
|
||||
if (b.pkg_hash.len == 0) {
|
||||
var args = try std.process.argsWithAllocator(b.allocator);
|
||||
defer args.deinit();
|
||||
while (args.next()) |a| {
|
||||
if (std.mem.eql(u8, a, "test")) {
|
||||
const ts_dep = b.lazyDependency("tree_sitter", .{}) orelse continue;
|
||||
tests.root_module.addImport("tree-sitter", ts_dep.module("tree-sitter"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const run_tests = b.addRunArtifact(tests);
|
||||
|
|
@ -85,8 +86,8 @@ pub fn build(b: *std.Build) !void {
|
|||
test_step.dependOn(&run_tests.step);
|
||||
}
|
||||
|
||||
inline fn fileExists(b: *std.Build, io: std.Io, filename: []const u8) bool {
|
||||
inline fn fileExists(b: *std.Build, filename: []const u8) bool {
|
||||
const dir = b.build_root.handle;
|
||||
dir.access(io, filename, .{}) catch return false;
|
||||
dir.access(filename, .{}) catch return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
.{
|
||||
.name = .tree_sitter_PARSER_NAME,
|
||||
.fingerprint = PARSER_FINGERPRINT,
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.version = "PARSER_VERSION",
|
||||
.dependencies = .{
|
||||
.tree_sitter = .{
|
||||
.url = "git+https://github.com/tree-sitter/zig-tree-sitter#0cf58172e61f6fdd16f681cde42b4acb531a23db",
|
||||
.hash = "tree_sitter-0.26.0-8heIf3CaAQDeVTQc0DMSBhbQAEx5aF-dTen4_LPxMgrv",
|
||||
.url = "git+https://github.com/tree-sitter/zig-tree-sitter#b4b72c903e69998fc88e27e154a5e3cc9166551b",
|
||||
.hash = "tree_sitter-0.25.0-8heIf51vAQConvVIgvm-9mVIbqh7yabZYqPXfOpS3YoG",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,22 +17,19 @@ endif()
|
|||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI" REQUIRED)
|
||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js"
|
||||
COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser
|
||||
COMMAND "${TREE_SITTER_CLI}" generate grammar.js
|
||||
--stage=json
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating grammar.json")
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
|
||||
BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
|
||||
--abi=${TREE_SITTER_ABI_VERSION}
|
||||
--stage=parser --abi=${TREE_SITTER_ABI_VERSION}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating parser.c")
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,3 @@ Package.resolved linguist-generated
|
|||
bindings/zig/* linguist-generated
|
||||
build.zig linguist-generated
|
||||
build.zig.zon linguist-generated
|
||||
|
||||
# Java bindings
|
||||
pom.xml linguist-generated
|
||||
bindings/java/** linguist-generated
|
||||
|
|
|
|||
|
|
@ -45,4 +45,3 @@ zig-out/
|
|||
*.tar.gz
|
||||
*.tgz
|
||||
*.zip
|
||||
*.jar
|
||||
|
|
|
|||
39
crates/cli/src/templates/index.d.ts
vendored
39
crates/cli/src/templates/index.d.ts
vendored
|
|
@ -18,43 +18,10 @@ type NodeInfo =
|
|||
children: ChildNode[];
|
||||
});
|
||||
|
||||
/**
|
||||
* The tree-sitter language object for this grammar.
|
||||
*
|
||||
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/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
|
||||
*/
|
||||
type Language = {
|
||||
language: unknown;
|
||||
|
||||
/**
|
||||
* The content of the `node-types.json` file for this grammar.
|
||||
*
|
||||
* @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types}
|
||||
*/
|
||||
nodeTypeInfo: NodeInfo[];
|
||||
|
||||
/** The syntax highlighting query for this grammar. */
|
||||
HIGHLIGHTS_QUERY?: string;
|
||||
|
||||
/** The language injection query for this grammar. */
|
||||
INJECTIONS_QUERY?: string;
|
||||
|
||||
/** The local variable query for this grammar. */
|
||||
LOCALS_QUERY?: string;
|
||||
|
||||
/** The symbol tagging query for this grammar. */
|
||||
TAGS_QUERY?: string;
|
||||
};
|
||||
|
||||
export default binding;
|
||||
declare const language: Language;
|
||||
export = language;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = fileURLToPath(new URL("../..", import.meta.url));
|
||||
const root = new URL("../..", import.meta.url).pathname;
|
||||
|
||||
const binding = typeof process.versions.bun === "string"
|
||||
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
|
||||
|
|
@ -9,29 +6,8 @@ const binding = typeof process.versions.bun === "string"
|
|||
: (await import("node-gyp-build")).default(root);
|
||||
|
||||
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;
|
||||
} 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];
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
export default binding;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
use tree_sitter_language::LanguageFn;
|
||||
|
||||
unsafe extern "C" {
|
||||
extern "C" {
|
||||
fn tree_sitter_PARSER_NAME() -> *const ();
|
||||
}
|
||||
|
||||
|
|
@ -32,21 +32,12 @@ 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
|
||||
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
||||
|
||||
#[cfg(with_highlights_query)]
|
||||
/// The syntax highlighting query for this grammar.
|
||||
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../HIGHLIGHTS_QUERY_PATH");
|
||||
// NOTE: uncomment these to include any queries that this grammar contains:
|
||||
|
||||
#[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");
|
||||
// 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");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
LANGUAGE_NAME := tree-sitter-KEBAB_PARSER_NAME
|
||||
HOMEPAGE_URL := PARSER_URL
|
||||
VERSION := PARSER_VERSION
|
||||
DESCRIPTION := PARSER_DESCRIPTION
|
||||
|
||||
# repository
|
||||
SRC_DIR := src
|
||||
|
|
@ -74,10 +73,10 @@ $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
|
|||
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
|
||||
|
||||
$(SRC_DIR)/grammar.json: grammar.js
|
||||
$(TS) generate --no-parser $^
|
||||
$(TS) generate --stage=json $^
|
||||
|
||||
$(PARSER): $(SRC_DIR)/grammar.json
|
||||
$(TS) generate $^
|
||||
$(TS) generate --stage=parser $^
|
||||
|
||||
install: all
|
||||
install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/KEBAB_PARSER_NAME '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"prebuildify": "^6.0.1",
|
||||
"tree-sitter": "^0.25.0",
|
||||
"tree-sitter": "^0.22.4",
|
||||
"tree-sitter-cli": "^CLI_VERSION"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.25.0"
|
||||
"tree-sitter": "^0.22.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// swift-tools-version:5.6
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import Foundation
|
||||
import PackageDescription
|
||||
|
||||
let dir = Context.packageDirectory
|
||||
var sources = ["src/parser.c"]
|
||||
if FileManager.default.fileExists(atPath: "\(dir)/src/scanner.c") {
|
||||
if FileManager.default.fileExists(atPath: "src/scanner.c") {
|
||||
sources.append("src/scanner.c")
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ let package = Package(
|
|||
.library(name: "PARSER_CLASS_NAME", targets: ["PARSER_CLASS_NAME"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.10.0"),
|
||||
.package(name: "SwiftTreeSitter", url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.9.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
|
@ -32,7 +31,7 @@ let package = Package(
|
|||
.testTarget(
|
||||
name: "PARSER_CLASS_NAMETests",
|
||||
dependencies: [
|
||||
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
|
||||
"SwiftTreeSitter",
|
||||
"PARSER_CLASS_NAME",
|
||||
],
|
||||
path: "bindings/swift/PARSER_CLASS_NAMETests"
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
<?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>
|
||||
|
|
@ -1,29 +1,29 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=62.4.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = [ "setuptools>=62.4.0", "wheel" ]
|
||||
|
||||
[project]
|
||||
authors = [ { email = "PARSER_AUTHOR_EMAIL", name = "PARSER_AUTHOR_NAME" } ]
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
description = "PARSER_DESCRIPTION"
|
||||
version = "PARSER_VERSION"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "PARSER_NAME"]
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Compilers",
|
||||
"Topic :: Text Processing :: Linguistic",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
description = "PARSER_DESCRIPTION"
|
||||
keywords = [ "incremental", "parsing", "tree-sitter", "PARSER_NAME" ]
|
||||
license.text = "PARSER_LICENSE"
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "PARSER_AUTHOR_NAME", email = "PARSER_AUTHOR_EMAIL" }]
|
||||
requires-python = ">=3.10"
|
||||
version = "PARSER_VERSION"
|
||||
license.text = "PARSER_LICENSE"
|
||||
readme = "README.md"
|
||||
|
||||
[project.urls]
|
||||
Funding = "FUNDING_URL"
|
||||
Homepage = "PARSER_URL"
|
||||
Funding = "FUNDING_URL"
|
||||
|
||||
[project.optional-dependencies]
|
||||
core = [ "tree-sitter~=0.24" ]
|
||||
core = ["tree-sitter~=0.24"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build = "cp310-*"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class BuildExt(build_ext):
|
|||
class BdistWheel(bdist_wheel):
|
||||
def get_tag(self):
|
||||
python, abi, platform = super().get_tag()
|
||||
if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"):
|
||||
if python.startswith("cp"):
|
||||
python, abi = "cp310", "abi3"
|
||||
return python, abi, platform
|
||||
|
||||
|
|
@ -42,7 +42,6 @@ class EggInfo(egg_info):
|
|||
super().find_sources()
|
||||
self.filelist.recursive_include("queries", "*.scm")
|
||||
self.filelist.include("src/tree_sitter/*.h")
|
||||
self.filelist.include("src/*.c")
|
||||
|
||||
|
||||
setup(
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ test "can load grammar" {
|
|||
const parser = Parser.create();
|
||||
defer parser.destroy();
|
||||
|
||||
const lang: *const ts.Language = Language.fromRaw(root.language());
|
||||
const lang: *const ts.Language = @ptrCast(root.language());
|
||||
defer lang.destroy();
|
||||
|
||||
try testing.expectEqual(void{}, parser.setLanguage(lang));
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,22 +1,23 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anstyle::AnsiColor;
|
||||
use anyhow::{anyhow, Result};
|
||||
use tree_sitter::Point;
|
||||
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter};
|
||||
use tree_sitter_loader::{Config, Loader};
|
||||
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments, to_utf8_point},
|
||||
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||
use super::{
|
||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||
test::paint,
|
||||
util,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Failure {
|
||||
pub(crate) row: usize,
|
||||
pub(crate) column: usize,
|
||||
pub(crate) expected_highlight: String,
|
||||
pub(crate) actual_highlights: Vec<String>,
|
||||
row: usize,
|
||||
column: usize,
|
||||
expected_highlight: String,
|
||||
actual_highlights: Vec<String>,
|
||||
}
|
||||
|
||||
impl std::error::Error for Failure {}
|
||||
|
|
@ -47,7 +48,19 @@ pub fn test_highlights(
|
|||
loader_config: &Config,
|
||||
highlighter: &mut Highlighter,
|
||||
directory: &Path,
|
||||
test_summary: &mut TestSummary,
|
||||
use_color: bool,
|
||||
) -> 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<()> {
|
||||
let mut failed = false;
|
||||
|
||||
|
|
@ -55,22 +68,25 @@ pub fn test_highlights(
|
|||
let highlight_test_file = highlight_test_file?;
|
||||
let test_file_path = highlight_test_file.path();
|
||||
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() {
|
||||
test_summary
|
||||
.highlight_results
|
||||
.add_group(test_file_name.to_string_lossy().as_ref());
|
||||
if test_highlights(
|
||||
println!("{}:", test_file_name.to_string_lossy());
|
||||
if test_highlights_indented(
|
||||
loader,
|
||||
loader_config,
|
||||
highlighter,
|
||||
&test_file_path,
|
||||
test_summary,
|
||||
use_color,
|
||||
indent_level + 1,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
test_summary.highlight_results.pop_traversal();
|
||||
} else {
|
||||
let (language, language_config) = loader
|
||||
.language_configuration_for_file_name(&test_file_path)?
|
||||
|
|
@ -82,12 +98,7 @@ pub fn test_highlights(
|
|||
})?;
|
||||
let highlight_config = language_config
|
||||
.highlight_config(language, None)?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"No highlighting config found for {}",
|
||||
test_file_path.display()
|
||||
)
|
||||
})?;
|
||||
.ok_or_else(|| anyhow!("No highlighting config found for {test_file_path:?}"))?;
|
||||
match test_highlight(
|
||||
loader,
|
||||
highlighter,
|
||||
|
|
@ -95,34 +106,39 @@ pub fn test_highlights(
|
|||
fs::read(&test_file_path)?.as_slice(),
|
||||
) {
|
||||
Ok(assertion_count) => {
|
||||
test_summary.highlight_results.add_case(TestResult {
|
||||
name: test_file_name.to_string_lossy().to_string(),
|
||||
info: TestInfo::AssertionTest {
|
||||
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||
test_num: test_summary.test_num,
|
||||
},
|
||||
});
|
||||
println!(
|
||||
"✓ {} ({assertion_count} assertions)",
|
||||
paint(
|
||||
use_color.then_some(AnsiColor::Green),
|
||||
test_file_name.to_string_lossy().as_ref()
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
test_summary.highlight_results.add_case(TestResult {
|
||||
name: test_file_name.to_string_lossy().to_string(),
|
||||
info: TestInfo::AssertionTest {
|
||||
outcome: TestOutcome::AssertionFailed {
|
||||
error: e.to_string(),
|
||||
},
|
||||
test_num: test_summary.test_num,
|
||||
},
|
||||
});
|
||||
println!(
|
||||
"✗ {}",
|
||||
paint(
|
||||
use_color.then_some(AnsiColor::Red),
|
||||
test_file_name.to_string_lossy().as_ref()
|
||||
)
|
||||
);
|
||||
println!(
|
||||
"{indent:indent_level$} {e}",
|
||||
indent = "",
|
||||
indent_level = indent_level * 2
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
test_summary.test_num += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if failed { Err(anyhow!("")) } else { Ok(()) }
|
||||
if failed {
|
||||
Err(anyhow!(""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterate_assertions(
|
||||
assertions: &[Assertion],
|
||||
highlights: &[(Utf8Point, Utf8Point, Highlight)],
|
||||
|
|
@ -139,48 +155,49 @@ pub fn iterate_assertions(
|
|||
expected_capture_name: expected_highlight,
|
||||
} in assertions
|
||||
{
|
||||
// Iterate through all of the highlights that start at or before this assertion's
|
||||
// position, looking for one that matches the assertion.
|
||||
actual_highlights.clear();
|
||||
let mut passed = false;
|
||||
let end_column = position.column + length - 1;
|
||||
for highlight in &highlights[i..] {
|
||||
// The assertions are ordered by position, so skip past all of the highlights that
|
||||
// end at or before this assertion's position.
|
||||
let mut end_column = position.column + length - 1;
|
||||
actual_highlights.clear();
|
||||
|
||||
// The assertions are ordered by position, so skip past all of the highlights that
|
||||
// end at or before this assertion's position.
|
||||
'highlight_loop: while let Some(highlight) = highlights.get(i) {
|
||||
if highlight.1 <= *position {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (highlight.0.row > position.row)
|
||||
|| (highlight.0.row == position.row && highlight.0.column > end_column)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If the highlight matches the assertion, or if the highlight doesn't
|
||||
// match the assertion but it's negative, this test passes. Otherwise,
|
||||
// add this highlight to the list of actual highlights that span the
|
||||
// assertion's position, in order to generate an error message in the event
|
||||
// of a failure.
|
||||
let highlight_name = &highlight_names[(highlight.2).0];
|
||||
if (*highlight_name == *expected_highlight) == *negative {
|
||||
actual_highlights.push(highlight_name);
|
||||
} else {
|
||||
passed = true;
|
||||
break;
|
||||
// Iterate through all of the highlights that start at or before this assertion's
|
||||
// position, looking for one that matches the assertion.
|
||||
let mut j = i;
|
||||
while let (false, Some(highlight)) = (passed, highlights.get(j)) {
|
||||
end_column = position.column + length - 1;
|
||||
if highlight.0.row >= position.row && highlight.0.column > end_column {
|
||||
break 'highlight_loop;
|
||||
}
|
||||
|
||||
// If the highlight matches the assertion, or if the highlight doesn't
|
||||
// match the assertion but it's negative, this test passes. Otherwise,
|
||||
// add this highlight to the list of actual highlights that span the
|
||||
// assertion's position, in order to generate an error message in the event
|
||||
// of a failure.
|
||||
let highlight_name = &highlight_names[(highlight.2).0];
|
||||
if (*highlight_name == *expected_highlight) == *negative {
|
||||
actual_highlights.push(highlight_name);
|
||||
} else {
|
||||
passed = true;
|
||||
break 'highlight_loop;
|
||||
}
|
||||
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !passed {
|
||||
let mut expected = String::with_capacity(expected_highlight.len() + 1);
|
||||
if *negative {
|
||||
expected.push('!');
|
||||
}
|
||||
expected.push_str(expected_highlight);
|
||||
return Err(Failure {
|
||||
row: position.row,
|
||||
column: end_column,
|
||||
expected_highlight: expected,
|
||||
expected_highlight: expected_highlight.clone(),
|
||||
actual_highlights: actual_highlights.into_iter().cloned().collect(),
|
||||
}
|
||||
.into());
|
||||
|
|
@ -219,11 +236,9 @@ pub fn get_highlight_positions(
|
|||
let mut highlight_stack = Vec::new();
|
||||
let source = String::from_utf8_lossy(source);
|
||||
let mut char_indices = source.char_indices();
|
||||
for event in
|
||||
highlighter.highlight(highlight_config, source.as_bytes(), None, None, |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})?
|
||||
{
|
||||
for event in highlighter.highlight(highlight_config, source.as_bytes(), None, |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})? {
|
||||
match event? {
|
||||
HighlightEvent::HighlightStart(h) => highlight_stack.push(h),
|
||||
HighlightEvent::HighlightEnd => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anstyle::AnsiColor;
|
||||
use anyhow::{anyhow, Result};
|
||||
use tree_sitter_loader::{Config, Loader};
|
||||
use tree_sitter_tags::{TagsConfiguration, TagsContext};
|
||||
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments, to_utf8_point},
|
||||
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||
use super::{
|
||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||
test::paint,
|
||||
util,
|
||||
};
|
||||
|
||||
|
|
@ -46,7 +47,19 @@ pub fn test_tags(
|
|||
loader_config: &Config,
|
||||
tags_context: &mut TagsContext,
|
||||
directory: &Path,
|
||||
test_summary: &mut TestSummary,
|
||||
use_color: bool,
|
||||
) -> 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<()> {
|
||||
let mut failed = false;
|
||||
|
||||
|
|
@ -54,22 +67,25 @@ pub fn test_tags(
|
|||
let tag_test_file = tag_test_file?;
|
||||
let test_file_path = tag_test_file.path();
|
||||
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() {
|
||||
test_summary
|
||||
.tag_results
|
||||
.add_group(test_file_name.to_string_lossy().as_ref());
|
||||
if test_tags(
|
||||
println!("{}:", test_file_name.to_string_lossy());
|
||||
if test_tags_indented(
|
||||
loader,
|
||||
loader_config,
|
||||
tags_context,
|
||||
&test_file_path,
|
||||
test_summary,
|
||||
use_color,
|
||||
indent_level + 1,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
test_summary.tag_results.pop_traversal();
|
||||
} else {
|
||||
let (language, language_config) = loader
|
||||
.language_configuration_for_file_name(&test_file_path)?
|
||||
|
|
@ -81,39 +97,45 @@ pub fn test_tags(
|
|||
})?;
|
||||
let tags_config = language_config
|
||||
.tags_config(language)?
|
||||
.ok_or_else(|| anyhow!("No tags config found for {}", test_file_path.display()))?;
|
||||
.ok_or_else(|| anyhow!("No tags config found for {test_file_path:?}"))?;
|
||||
match test_tag(
|
||||
tags_context,
|
||||
tags_config,
|
||||
fs::read(&test_file_path)?.as_slice(),
|
||||
) {
|
||||
Ok(assertion_count) => {
|
||||
test_summary.tag_results.add_case(TestResult {
|
||||
name: test_file_name.to_string_lossy().to_string(),
|
||||
info: TestInfo::AssertionTest {
|
||||
outcome: TestOutcome::AssertionPassed { assertion_count },
|
||||
test_num: test_summary.test_num,
|
||||
},
|
||||
});
|
||||
println!(
|
||||
"✓ {} ({assertion_count} assertions)",
|
||||
paint(
|
||||
use_color.then_some(AnsiColor::Green),
|
||||
test_file_name.to_string_lossy().as_ref()
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
test_summary.tag_results.add_case(TestResult {
|
||||
name: test_file_name.to_string_lossy().to_string(),
|
||||
info: TestInfo::AssertionTest {
|
||||
outcome: TestOutcome::AssertionFailed {
|
||||
error: e.to_string(),
|
||||
},
|
||||
test_num: test_summary.test_num,
|
||||
},
|
||||
});
|
||||
println!(
|
||||
"✗ {}",
|
||||
paint(
|
||||
use_color.then_some(AnsiColor::Red),
|
||||
test_file_name.to_string_lossy().as_ref()
|
||||
)
|
||||
);
|
||||
println!(
|
||||
"{indent:indent_level$} {e}",
|
||||
indent = "",
|
||||
indent_level = indent_level * 2
|
||||
);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
test_summary.test_num += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if failed { Err(anyhow!("")) } else { Ok(()) }
|
||||
if failed {
|
||||
Err(anyhow!(""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_tag(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
mod async_boundary_test;
|
||||
mod async_context_test;
|
||||
mod corpus_test;
|
||||
mod detect_language;
|
||||
mod helpers;
|
||||
mod highlight_test;
|
||||
mod language_test;
|
||||
mod node_test;
|
||||
mod parser_hang_test;
|
||||
mod parser_test;
|
||||
mod pathological_test;
|
||||
mod query_test;
|
||||
|
|
@ -17,23 +18,17 @@ mod tree_test;
|
|||
#[cfg(feature = "wasm")]
|
||||
mod wasm_language_test;
|
||||
|
||||
use tree_sitter_generate::{GenerateResult, OptLevel};
|
||||
use tree_sitter_generate::GenerateResult;
|
||||
|
||||
pub use crate::fuzz::{
|
||||
ITERATION_COUNT, allocations,
|
||||
allocations,
|
||||
edits::{get_random_edit, invert_edit},
|
||||
random::Rand,
|
||||
ITERATION_COUNT,
|
||||
};
|
||||
|
||||
pub use helpers::fixtures::get_language;
|
||||
|
||||
/// 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.
|
||||
fn generate_parser(grammar_json: &str) -> GenerateResult<(String, String)> {
|
||||
tree_sitter_generate::generate_parser_for_grammar(
|
||||
grammar_json,
|
||||
Some((0, 0, 0)),
|
||||
OptLevel::default(),
|
||||
&mut Vec::new(),
|
||||
)
|
||||
tree_sitter_generate::generate_parser_for_grammar(grammar_json, Some((0, 0, 0)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,150 +0,0 @@
|
|||
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) }
|
||||
}
|
||||
278
crates/cli/src/tests/async_context_test.rs
Normal file
278
crates/cli/src/tests/async_context_test.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,24 @@ use std::{collections::HashMap, env, fs};
|
|||
|
||||
use anyhow::Context;
|
||||
use tree_sitter::Parser;
|
||||
use tree_sitter_generate::OptLevel;
|
||||
use tree_sitter_proc_macro::test_with_seed;
|
||||
|
||||
use crate::{
|
||||
fuzz::{
|
||||
EDIT_COUNT, EXAMPLE_EXCLUDE, EXAMPLE_INCLUDE, ITERATION_COUNT, LANGUAGE_FILTER,
|
||||
LOG_GRAPH_ENABLED, START_SEED,
|
||||
corpus_test::{
|
||||
check_changed_ranges, check_consistent_sizes, get_parser, set_included_ranges,
|
||||
},
|
||||
edits::{get_random_edit, invert_edit},
|
||||
flatten_tests, new_seed,
|
||||
random::Rand,
|
||||
EDIT_COUNT, EXAMPLE_EXCLUDE, EXAMPLE_INCLUDE, ITERATION_COUNT, LANGUAGE_FILTER,
|
||||
LOG_GRAPH_ENABLED, START_SEED,
|
||||
},
|
||||
parse::perform_edit,
|
||||
test::{DiffKey, TestDiff, parse_tests, render_test_output},
|
||||
test::{parse_tests, print_diff, print_diff_key, strip_sexp_fields},
|
||||
tests::{
|
||||
allocations,
|
||||
helpers::fixtures::{SCRATCH_BASE_DIR, fixtures_dir, get_language, get_test_language},
|
||||
helpers::fixtures::{fixtures_dir, get_language, get_test_language, SCRATCH_BASE_DIR},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -121,10 +120,10 @@ pub fn test_language_corpus(
|
|||
skipped: Option<&[&str]>,
|
||||
language_dir: Option<&str>,
|
||||
) {
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter
|
||||
{
|
||||
return;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let language_dir = language_dir.unwrap_or_default();
|
||||
|
|
@ -186,17 +185,38 @@ pub fn test_language_corpus(
|
|||
println!();
|
||||
for (test_index, test) in tests.iter().enumerate() {
|
||||
let test_name = format!("{language_name} - {}", test.name);
|
||||
if let Some(skipped) = skipped.as_mut()
|
||||
&& let Some(counter) = skipped.get_mut(test_name.as_str())
|
||||
{
|
||||
println!(" {test_index}. {test_name} - SKIPPED");
|
||||
*counter += 1;
|
||||
continue;
|
||||
if let Some(skipped) = skipped.as_mut() {
|
||||
if let Some(counter) = skipped.get_mut(test_name.as_str()) {
|
||||
println!(" {test_index}. {test_name} - SKIPPED");
|
||||
*counter += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
println!(" {test_index}. {test_name}");
|
||||
|
||||
let passed = allocations::record(|| test.check_initial_parse(&language, &test_name, true));
|
||||
let passed = allocations::record(|| {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(&language).unwrap();
|
||||
set_included_ranges(&mut parser, &test.input, test.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect initial parse for {test_name}");
|
||||
print_diff_key();
|
||||
print_diff(&actual_output, &test.output, true);
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
if !passed {
|
||||
failure_count += 1;
|
||||
|
|
@ -254,9 +274,7 @@ pub fn test_language_corpus(
|
|||
// Check that the new tree is consistent.
|
||||
check_consistent_sizes(&tree2, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree, &tree2, &input) {
|
||||
println!(
|
||||
"\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n",
|
||||
);
|
||||
println!("\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n",);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -272,13 +290,15 @@ pub fn test_language_corpus(
|
|||
let tree3 = parser.parse(&input, Some(&tree2)).unwrap();
|
||||
|
||||
// Verify that the final tree matches the expectation from the corpus.
|
||||
let actual_output =
|
||||
render_test_output(&input, &tree3, test.cst, test.has_fields).unwrap();
|
||||
let mut actual_output = tree3.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
print_diff_key();
|
||||
print_diff(&actual_output, &test.output, true);
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -286,9 +306,7 @@ pub fn test_language_corpus(
|
|||
// Check that the edited tree is consistent.
|
||||
check_consistent_sizes(&tree3, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree2, &tree3, &input) {
|
||||
println!(
|
||||
"Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n"
|
||||
);
|
||||
println!("Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -333,10 +351,10 @@ fn test_feature_corpus_files() {
|
|||
let language_name = entry.file_name();
|
||||
let language_name = language_name.to_str().unwrap();
|
||||
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let test_path = entry.path();
|
||||
|
|
@ -353,12 +371,8 @@ fn test_feature_corpus_files() {
|
|||
)
|
||||
})
|
||||
.unwrap();
|
||||
let generate_result = tree_sitter_generate::generate_parser_for_grammar(
|
||||
&grammar_json,
|
||||
Some((0, 0, 0)),
|
||||
OptLevel::default(),
|
||||
&mut Vec::new(),
|
||||
);
|
||||
let generate_result =
|
||||
tree_sitter_generate::generate_parser_for_grammar(&grammar_json, Some((0, 0, 0)));
|
||||
|
||||
if error_message_path.exists() {
|
||||
if EXAMPLE_INCLUDE.is_some() || EXAMPLE_EXCLUDE.is_some() {
|
||||
|
|
@ -379,12 +393,12 @@ fn test_feature_corpus_files() {
|
|||
failure_count += 1;
|
||||
}
|
||||
} else {
|
||||
eprintln!("Expected error message but got none for test grammar '{language_name}'");
|
||||
eprintln!("Expected error message but got none for test grammar '{language_name}'",);
|
||||
failure_count += 1;
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = &generate_result {
|
||||
eprintln!("Unexpected error for test grammar '{language_name}':\n{e}");
|
||||
eprintln!("Unexpected error for test grammar '{language_name}':\n{e}",);
|
||||
failure_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -402,8 +416,24 @@ fn test_feature_corpus_files() {
|
|||
for test in tests {
|
||||
eprintln!(" example: {:?}", test.name);
|
||||
|
||||
let passed =
|
||||
allocations::record(|| test.check_initial_parse(&language, &test.name, true));
|
||||
let passed = allocations::record(|| {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
if actual_output == test.output {
|
||||
true
|
||||
} else {
|
||||
print_diff_key();
|
||||
print_diff(&actual_output, &test.output, true);
|
||||
println!();
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !passed {
|
||||
failure_count += 1;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ fn detect_language_by_first_line_regex() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn detect_language_by_double_barrel_file_extension() {
|
||||
fn detect_langauge_by_double_barrel_file_extension() {
|
||||
let blade_dir = tree_sitter_dir(
|
||||
r#"{
|
||||
"grammars": [
|
||||
|
|
@ -127,52 +127,6 @@ fn detect_language_by_double_barrel_file_extension() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_language_with_dots_in_filename() {
|
||||
let blade_dir = tree_sitter_dir(
|
||||
r#"{
|
||||
"grammars": [
|
||||
{
|
||||
"name": "blade_dots",
|
||||
"path": ".",
|
||||
"scope": "source.blade",
|
||||
"file-types": [
|
||||
"blade.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "php_dots",
|
||||
"path": ".",
|
||||
"scope": "source.php",
|
||||
"file-types": [
|
||||
"php"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"blade_dots",
|
||||
);
|
||||
|
||||
let mut loader = Loader::with_parser_lib_path(scratch_dir().to_path_buf());
|
||||
let config = loader
|
||||
.find_language_configurations_at_path(blade_dir.path(), false)
|
||||
.unwrap();
|
||||
|
||||
// this is just to validate that we can read the tree-sitter.json correctly
|
||||
assert_eq!(config[0].scope.as_ref().unwrap(), "source.blade");
|
||||
|
||||
let file_name = blade_dir.path().join("foo.bar.baz.blade.php");
|
||||
fs::write(&file_name, "").unwrap();
|
||||
assert_eq!(
|
||||
get_lang_scope(&loader, &file_name),
|
||||
Some("source.blade".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_language_without_filename() {
|
||||
let gitignore_dir = tree_sitter_dir(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::ops::Range;
|
||||
use std::{ops::Range, str};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReadRecorder<'a> {
|
||||
|
|
@ -20,7 +20,7 @@ impl<'a> ReadRecorder<'a> {
|
|||
if let Err(i) = self.indices_read.binary_search(&offset) {
|
||||
self.indices_read.insert(i, offset);
|
||||
}
|
||||
&self.content[offset..=offset]
|
||||
&self.content[offset..(offset + 1)]
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ impl<'a> ReadRecorder<'a> {
|
|||
let mut result = Vec::new();
|
||||
let mut last_range = Option::<Range<usize>>::None;
|
||||
for index in &self.indices_read {
|
||||
if let Some(range) = &mut last_range {
|
||||
if let Some(ref mut range) = &mut last_range {
|
||||
if range.end == *index {
|
||||
range.end += 1;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{LazyLock, Mutex},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use tree_sitter::Language;
|
||||
use tree_sitter_generate::{ALLOC_HEADER, ARRAY_HEADER, load_grammar_file};
|
||||
use tree_sitter_generate::{load_grammar_file, ALLOC_HEADER, ARRAY_HEADER};
|
||||
use tree_sitter_highlight::HighlightConfiguration;
|
||||
use tree_sitter_loader::{CompileConfig, Loader};
|
||||
use tree_sitter_tags::TagsConfiguration;
|
||||
|
|
@ -24,10 +23,6 @@ static TEST_LOADER: LazyLock<Loader> = LazyLock::new(|| {
|
|||
loader
|
||||
});
|
||||
|
||||
// Prevents parallel tests from racing on the same per-grammar
|
||||
// `src_dir/tree_sitter/` and observing a half-rewritten header.
|
||||
static WRITTEN_HEADER_DIRS: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
pub static ENGINE: LazyLock<tree_sitter::wasmtime::Engine> = LazyLock::new(Default::default);
|
||||
|
||||
|
|
@ -139,22 +134,17 @@ fn get_test_language_internal(
|
|||
};
|
||||
|
||||
let header_path = src_dir.join("tree_sitter");
|
||||
if WRITTEN_HEADER_DIRS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(header_path.clone())
|
||||
{
|
||||
fs::create_dir_all(&header_path).unwrap();
|
||||
for (file, content) in [
|
||||
("alloc.h", ALLOC_HEADER),
|
||||
("array.h", ARRAY_HEADER),
|
||||
("parser.h", tree_sitter::PARSER_HEADER),
|
||||
] {
|
||||
let path = header_path.join(file);
|
||||
fs::write(&path, content)
|
||||
.with_context(|| format!("Failed to write {}", path.display()))
|
||||
.unwrap();
|
||||
}
|
||||
fs::create_dir_all(&header_path).unwrap();
|
||||
|
||||
for (file, content) in [
|
||||
("alloc.h", ALLOC_HEADER),
|
||||
("array.h", ARRAY_HEADER),
|
||||
("parser.h", tree_sitter::PARSER_HEADER),
|
||||
] {
|
||||
let file = header_path.join(file);
|
||||
fs::write(&file, content)
|
||||
.with_context(|| format!("Failed to write {:?}", file.file_name().unwrap()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let paths_to_check = if let Some(scanner_path) = &scanner_path {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use std::{cmp::Ordering, fmt::Write, ops::Range};
|
||||
|
||||
use rand::{Rng, RngExt};
|
||||
use rand::prelude::Rng;
|
||||
use streaming_iterator::{IntoStreamingIterator, StreamingIterator};
|
||||
use tree_sitter::{
|
||||
Language, Node, Parser, Point, Query, QueryCapture, QueryCursor, QueryMatch, Tree, TreeCursor,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pattern<'a> {
|
||||
kind: Option<&'a str>,
|
||||
pub struct Pattern {
|
||||
kind: Option<&'static str>,
|
||||
named: bool,
|
||||
field: Option<&'a str>,
|
||||
field: Option<&'static str>,
|
||||
capture: Option<String>,
|
||||
children: Vec<Self>,
|
||||
children: Vec<Pattern>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -25,17 +25,17 @@ const CAPTURE_NAMES: &[&str] = &[
|
|||
"one", "two", "three", "four", "five", "six", "seven", "eight",
|
||||
];
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
pub fn random_pattern_in_tree(tree: &'a Tree, rng: &mut impl Rng) -> (Self, Range<Point>) {
|
||||
impl Pattern {
|
||||
pub fn random_pattern_in_tree(tree: &Tree, rng: &mut impl Rng) -> (Self, Range<Point>) {
|
||||
let mut cursor = tree.walk();
|
||||
|
||||
// Descend to the node at a random byte offset and depth.
|
||||
let mut max_depth = 0;
|
||||
let byte_offset = rng.random_range(0..cursor.node().end_byte());
|
||||
let byte_offset = rng.gen_range(0..cursor.node().end_byte());
|
||||
while cursor.goto_first_child_for_byte(byte_offset).is_some() {
|
||||
max_depth += 1;
|
||||
}
|
||||
let depth = rng.random_range(0..=max_depth);
|
||||
let depth = rng.gen_range(0..=max_depth);
|
||||
for _ in 0..depth {
|
||||
cursor.goto_parent();
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ impl<'a> Pattern<'a> {
|
|||
let pattern_start = cursor.node().start_position();
|
||||
let mut roots = vec![Self::random_pattern_for_node(&mut cursor, rng)];
|
||||
while roots.len() < 5 && cursor.goto_next_sibling() {
|
||||
if rng.random_bool(0.2) {
|
||||
if rng.gen_bool(0.2) {
|
||||
roots.push(Self::random_pattern_for_node(&mut cursor, rng));
|
||||
}
|
||||
}
|
||||
|
|
@ -75,26 +75,26 @@ impl<'a> Pattern<'a> {
|
|||
(pattern, pattern_start..pattern_end)
|
||||
}
|
||||
|
||||
fn random_pattern_for_node(cursor: &mut TreeCursor<'a>, rng: &mut impl Rng) -> Self {
|
||||
fn random_pattern_for_node(cursor: &mut TreeCursor, rng: &mut impl Rng) -> Self {
|
||||
let node = cursor.node();
|
||||
|
||||
// Sometimes specify the node's type, sometimes use a wildcard.
|
||||
let (kind, named) = if rng.random_bool(0.9) {
|
||||
let (kind, named) = if rng.gen_bool(0.9) {
|
||||
(Some(node.kind()), node.is_named())
|
||||
} else {
|
||||
(Some("_"), node.is_named() && rng.random_bool(0.8))
|
||||
(Some("_"), node.is_named() && rng.gen_bool(0.8))
|
||||
};
|
||||
|
||||
// Sometimes specify the node's field.
|
||||
let field = if rng.random_bool(0.75) {
|
||||
let field = if rng.gen_bool(0.75) {
|
||||
cursor.field_name()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Sometimes capture the node.
|
||||
let capture = if rng.random_bool(0.7) {
|
||||
Some(CAPTURE_NAMES[rng.random_range(0..CAPTURE_NAMES.len())].to_string())
|
||||
let capture = if rng.gen_bool(0.7) {
|
||||
Some(CAPTURE_NAMES[rng.gen_range(0..CAPTURE_NAMES.len())].to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -102,9 +102,9 @@ impl<'a> Pattern<'a> {
|
|||
// Walk the children and include child patterns for some of them.
|
||||
let mut children = Vec::new();
|
||||
if named && cursor.goto_first_child() {
|
||||
let max_children = rng.random_range(0..4);
|
||||
let max_children = rng.gen_range(0..4);
|
||||
while cursor.goto_next_sibling() {
|
||||
if rng.random_bool(0.6) {
|
||||
if rng.gen_bool(0.6) {
|
||||
let child_ast = Self::random_pattern_for_node(cursor, rng);
|
||||
children.push(child_ast);
|
||||
if children.len() >= max_children {
|
||||
|
|
@ -204,10 +204,10 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
|
||||
// If a field is specified, check that it matches the node.
|
||||
if let Some(field) = self.field
|
||||
&& cursor.field_name() != Some(field)
|
||||
{
|
||||
return Vec::new();
|
||||
if let Some(field) = self.field {
|
||||
if cursor.field_name() != Some(field) {
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a match for the current node.
|
||||
|
|
@ -225,7 +225,7 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
|
||||
// Find every matching combination of child patterns and child nodes.
|
||||
let mut finished_matches = Vec::<Match<'_, 'tree>>::new();
|
||||
let mut finished_matches = Vec::<Match>::new();
|
||||
if cursor.goto_first_child() {
|
||||
let mut match_states = vec![(0, mat)];
|
||||
loop {
|
||||
|
|
@ -268,7 +268,7 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Pattern<'_> {
|
||||
impl std::fmt::Display for Pattern {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut result = String::new();
|
||||
self.write_to_string(&mut result, 0);
|
||||
|
|
@ -336,7 +336,7 @@ pub fn collect_matches<'a>(
|
|||
while let Some(m) = matches.next() {
|
||||
result.push((
|
||||
m.pattern_index,
|
||||
format_captures(m.captures().iter().into_streaming_iter_ref(), query, source),
|
||||
format_captures(m.captures.iter().into_streaming_iter_ref(), query, source),
|
||||
));
|
||||
}
|
||||
result
|
||||
|
|
@ -347,7 +347,7 @@ pub fn collect_captures<'a>(
|
|||
query: &'a Query,
|
||||
source: &'a str,
|
||||
) -> Vec<(&'a str, &'a str)> {
|
||||
format_captures(captures.map(|(m, i)| m.captures()[*i]), query, source)
|
||||
format_captures(captures.map(|(m, i)| m.captures[*i]), query, source)
|
||||
}
|
||||
|
||||
fn format_captures<'a>(
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ use std::{
|
|||
ffi::CString,
|
||||
fs,
|
||||
os::raw::c_char,
|
||||
ptr, slice,
|
||||
ptr, slice, str,
|
||||
sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
LazyLock,
|
||||
},
|
||||
};
|
||||
|
||||
use tree_sitter_highlight::{
|
||||
Error, Highlight, HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer, c,
|
||||
c, Error, Highlight, HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer,
|
||||
};
|
||||
|
||||
use super::helpers::fixtures::{get_highlight_config, get_language, get_language_queries_path};
|
||||
|
|
@ -481,11 +481,10 @@ fn test_highlighting_cancellation() {
|
|||
|
||||
// The initial `highlight` call, which eagerly parses the outer document, should not fail.
|
||||
let mut highlighter = Highlighter::new();
|
||||
let mut events = highlighter
|
||||
let events = highlighter
|
||||
.highlight(
|
||||
&HTML_HIGHLIGHT,
|
||||
source.as_bytes(),
|
||||
None,
|
||||
Some(&cancellation_flag),
|
||||
injection_callback,
|
||||
)
|
||||
|
|
@ -493,18 +492,14 @@ fn test_highlighting_cancellation() {
|
|||
|
||||
// Iterating the scopes should not panic. It should return an error once the
|
||||
// cancellation is detected.
|
||||
let found_cancellation_error = events.any(|event| match event {
|
||||
Ok(_) => false,
|
||||
Err(Error::Cancelled) => true,
|
||||
Err(Error::InvalidLanguage(_) | Error::Unknown) => {
|
||||
unreachable!("Unexpected error type while iterating events")
|
||||
for event in events {
|
||||
if let Err(e) = event {
|
||||
assert_eq!(e, Error::Cancelled);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
assert!(
|
||||
found_cancellation_error,
|
||||
"Expected a cancellation error while iterating events"
|
||||
);
|
||||
panic!("Expected an error while iterating highlighter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -728,7 +723,6 @@ fn to_html<'a>(
|
|||
language_config,
|
||||
src,
|
||||
None,
|
||||
None,
|
||||
&test_language_for_injection_string,
|
||||
)?;
|
||||
|
||||
|
|
@ -749,10 +743,7 @@ fn to_html<'a>(
|
|||
.collect())
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::type_complexity,
|
||||
reason = "return type represents structured highlight tokens"
|
||||
)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn to_token_vector<'a>(
|
||||
src: &'a str,
|
||||
language_config: &'a HighlightConfiguration,
|
||||
|
|
@ -766,7 +757,6 @@ fn to_token_vector<'a>(
|
|||
language_config,
|
||||
src,
|
||||
None,
|
||||
None,
|
||||
&test_language_for_injection_string,
|
||||
)?;
|
||||
for event in events {
|
||||
|
|
|
|||
|
|
@ -31,17 +31,14 @@ fn test_lookahead_iterator() {
|
|||
let mut lookahead = language.lookahead_iterator(next_state).unwrap();
|
||||
assert_eq!(*lookahead.language(), language);
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
assert!(lookahead.reset_state(next_state));
|
||||
lookahead.reset_state(next_state);
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
|
||||
assert!(lookahead.reset(&language, next_state));
|
||||
assert!(
|
||||
lookahead
|
||||
.map(|s| language.node_kind_for_id(s).unwrap())
|
||||
.eq(expected_symbols)
|
||||
);
|
||||
lookahead.reset(&language, next_state);
|
||||
assert!(lookahead
|
||||
.map(|s| language.node_kind_for_id(s).unwrap())
|
||||
.eq(expected_symbols));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -67,33 +64,6 @@ fn test_lookahead_iterator_modifiable_only_by_mut() {
|
|||
let _ = names.next();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead_iterator_exhaustion() {
|
||||
let language = get_language("json");
|
||||
|
||||
for state in 0..language.parse_state_count() {
|
||||
let state = u16::try_from(state).unwrap();
|
||||
let mut lookahead = language.lookahead_iterator(state).unwrap();
|
||||
|
||||
// A fresh iterator is not positioned on a symbol.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
|
||||
let count = lookahead.by_ref().count();
|
||||
|
||||
// An exhausted iterator is not positioned on a symbol, and stays exhausted.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), 0);
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
// Resetting restores it exactly.
|
||||
assert!(lookahead.reset_state(state));
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), count);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_metadata_checks() {
|
||||
let language = get_language("rust");
|
||||
|
|
@ -140,7 +110,7 @@ fn test_supertypes() {
|
|||
supertypes
|
||||
.iter()
|
||||
.filter_map(|&s| language.node_kind_for_id(s))
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
vec![
|
||||
"_expression",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use tree_sitter::{InputEdit, Node, Parser, Point, Tree};
|
||||
use tree_sitter::{Node, Parser, Point, Tree};
|
||||
use tree_sitter_generate::load_grammar_file;
|
||||
|
||||
use super::{
|
||||
Rand, get_random_edit,
|
||||
get_random_edit,
|
||||
helpers::fixtures::{fixtures_dir, get_language, get_test_language},
|
||||
Rand,
|
||||
};
|
||||
use crate::{
|
||||
parse::perform_edit,
|
||||
|
|
@ -285,10 +286,7 @@ fn test_parent_of_zero_width_node() {
|
|||
|
||||
assert_eq!(block.to_string(), "(block)");
|
||||
assert_eq!(block_parent.kind(), "function_definition");
|
||||
assert_eq!(
|
||||
block_parent.to_string(),
|
||||
"(function_definition name: (identifier) parameters: (parameters (identifier)) body: (block))"
|
||||
);
|
||||
assert_eq!(block_parent.to_string(), "(function_definition name: (identifier) parameters: (parameters (identifier)) body: (block))");
|
||||
|
||||
assert_eq!(
|
||||
root.child_with_descendant(block).unwrap(),
|
||||
|
|
@ -454,7 +452,7 @@ fn test_node_child_by_field_name_with_extra_hidden_children() {
|
|||
// In the Python grammar, some fields are applied to `suite` nodes,
|
||||
// which consist of an invisible `indent` token followed by a block.
|
||||
// Check that when searching for a child with a field name, we don't
|
||||
// return a hidden child node.
|
||||
//
|
||||
let tree = parser.parse("while a:\n pass", None).unwrap();
|
||||
let while_node = tree.root_node().child(0).unwrap();
|
||||
assert_eq!(while_node.kind(), "while_statement");
|
||||
|
|
@ -845,92 +843,6 @@ fn test_node_is_error() {
|
|||
assert!(child.is_error());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_point() {
|
||||
let edit = InputEdit {
|
||||
start_byte: 5,
|
||||
old_end_byte: 5,
|
||||
new_end_byte: 10,
|
||||
start_position: Point::new(0, 5),
|
||||
old_end_position: Point::new(0, 5),
|
||||
new_end_position: Point::new(0, 10),
|
||||
};
|
||||
|
||||
// Point after edit
|
||||
let mut point = Point::new(0, 8);
|
||||
let mut byte = 8;
|
||||
edit.edit_point(&mut point, &mut byte);
|
||||
assert_eq!(point, Point::new(0, 13));
|
||||
assert_eq!(byte, 13);
|
||||
|
||||
// Point before edit
|
||||
let mut point = Point::new(0, 2);
|
||||
let mut byte = 2;
|
||||
edit.edit_point(&mut point, &mut byte);
|
||||
assert_eq!(point, Point::new(0, 2));
|
||||
assert_eq!(byte, 2);
|
||||
|
||||
// Point at edit start
|
||||
let mut point = Point::new(0, 5);
|
||||
let mut byte = 5;
|
||||
edit.edit_point(&mut point, &mut byte);
|
||||
assert_eq!(point, Point::new(0, 10));
|
||||
assert_eq!(byte, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_range() {
|
||||
use tree_sitter::{InputEdit, Point, Range};
|
||||
|
||||
let edit = InputEdit {
|
||||
start_byte: 10,
|
||||
old_end_byte: 15,
|
||||
new_end_byte: 20,
|
||||
start_position: Point::new(1, 0),
|
||||
old_end_position: Point::new(1, 5),
|
||||
new_end_position: Point::new(2, 0),
|
||||
};
|
||||
|
||||
// Range after edit
|
||||
let mut range = Range {
|
||||
start_byte: 20,
|
||||
end_byte: 25,
|
||||
start_point: Point::new(2, 0),
|
||||
end_point: Point::new(2, 5),
|
||||
};
|
||||
edit.edit_range(&mut range);
|
||||
assert_eq!(range.start_byte, 25);
|
||||
assert_eq!(range.end_byte, 30);
|
||||
assert_eq!(range.start_point, Point::new(3, 0));
|
||||
assert_eq!(range.end_point, Point::new(3, 5));
|
||||
|
||||
// Range before edit
|
||||
let mut range = Range {
|
||||
start_byte: 5,
|
||||
end_byte: 8,
|
||||
start_point: Point::new(0, 5),
|
||||
end_point: Point::new(0, 8),
|
||||
};
|
||||
edit.edit_range(&mut range);
|
||||
assert_eq!(range.start_byte, 5);
|
||||
assert_eq!(range.end_byte, 8);
|
||||
assert_eq!(range.start_point, Point::new(0, 5));
|
||||
assert_eq!(range.end_point, Point::new(0, 8));
|
||||
|
||||
// Range overlapping edit
|
||||
let mut range = Range {
|
||||
start_byte: 8,
|
||||
end_byte: 12,
|
||||
start_point: Point::new(0, 8),
|
||||
end_point: Point::new(1, 2),
|
||||
};
|
||||
edit.edit_range(&mut range);
|
||||
assert_eq!(range.start_byte, 8);
|
||||
assert_eq!(range.end_byte, 10);
|
||||
assert_eq!(range.start_point, Point::new(0, 8));
|
||||
assert_eq!(range.end_point, Point::new(1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_sexp() {
|
||||
let mut parser = Parser::new();
|
||||
|
|
@ -950,13 +862,6 @@ fn test_node_sexp() {
|
|||
|
||||
#[test]
|
||||
fn test_node_field_names() {
|
||||
// - "x":
|
||||
// This isn't used in the test, but prevents `_hidden_rule1` from being eliminated as a
|
||||
// unit reduction.
|
||||
// - "_hidden_rule1":
|
||||
// Fields pointing to hidden nodes with a single child resolve to the child.
|
||||
// - "_hidden_rule2":
|
||||
// Fields within hidden nodes can be referenced through the parent node.
|
||||
let (parser_name, parser_code) = generate_parser(
|
||||
r#"
|
||||
{
|
||||
|
|
@ -979,6 +884,8 @@ fn test_node_field_names() {
|
|||
{"type": "STRING", "value": "child-1"},
|
||||
{"type": "BLANK"},
|
||||
|
||||
// This isn't used in the test, but prevents `_hidden_rule1`
|
||||
// from being eliminated as a unit reduction.
|
||||
{
|
||||
"type": "ALIAS",
|
||||
"value": "x",
|
||||
|
|
@ -999,6 +906,7 @@ fn test_node_field_names() {
|
|||
]
|
||||
},
|
||||
|
||||
// Fields pointing to hidden nodes with a single child resolve to the child.
|
||||
"_hidden_rule1": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
|
|
@ -1007,6 +915,7 @@ fn test_node_field_names() {
|
|||
]
|
||||
},
|
||||
|
||||
// Fields within hidden nodes can be referenced through the parent node.
|
||||
"_hidden_rule2": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue