From 5d234efb51174cd69e7d70d289631c10826ef4af Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 21 May 2026 10:31:23 +0100 Subject: [PATCH 1/2] feat(res-to-affine): land tree-sitter grammar build pipeline (Phase 2a, Refs #57) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 of the `.res → .affine` migration assistant (#57) replaces the Phase-1 line-regex scanner with a tree-sitter AST walker. The grammar itself is manifest-vendored in `editors/tree-sitter-rescript/` (since #314), but the actual build pipeline that turns the manifest into a loadable parser had not been wired up. This commit is that wiring. - `justfile` gains an `install-grammar` recipe that wraps the existing `editors/tree-sitter-rescript/scripts/install.sh` so the bootstrap step is a discoverable `just` recipe alongside `build`/`test`/etc. - `editors/tree-sitter-rescript/scripts/install.sh` updates its error message when the `tree-sitter` CLI is missing to point at both the Rust-native install (`cargo install tree-sitter-cli`, the repo's preferred path for CLI tooling per CLAUDE.md) and the existing Node-based one (`npm install -g tree-sitter-cli`). The script behaviour is unchanged when the CLI is present. - `.github/workflows/ci.yml` gains a `migration-assistant` job that installs the tree-sitter CLI via npm (the fast CI path — a pre-built binary, ~5 s vs. ~5 min for `cargo install` from source), runs `just install-grammar`, verifies `tools/vendor/tree-sitter-rescript/ src/parser.c` was produced, and smoke-parses the existing `tools/res-to-affine/test/fixtures/sample.res` fixture. The job sits alongside `vscode-smoke` as a Node-using carve-out under the same reasoning (manifest dep on an npm-distributed tool whose binary output is what we actually consume; no new TS source in this repo). - `editors/tree-sitter-rescript/README.md` documents the dual install path, the new justfile recipe, and the CI gate. Phase 2b (the actual walker — `walker.ml`, AST-based detection of `side-effect-import`, `--engine` CLI flag, snapshot tests vs. the regex scanner) stacks on top of this branch. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 60 +++++++++++++++++++ editors/tree-sitter-rescript/README.md | 29 ++++++++- .../tree-sitter-rescript/scripts/install.sh | 4 +- justfile | 10 ++++ 4 files changed, 100 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82935278..fabe9032 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,3 +119,63 @@ jobs: # Headless display required because @vscode/test-electron launches # the real Electron-based VS Code binary. run: xvfb-run -a npm test + + migration-assistant: + # Build pinned tree-sitter-rescript grammar consumed by the + # `.res → .affine` migration assistant (#57 Phase 2). The grammar + # is manifest-vendored (`editors/tree-sitter-rescript/package.json`) + # so this job exists to (a) verify the install script and pinned + # commit still build cleanly and (b) gate `tools/res-to-affine/` + # walker work that depends on the generated parser. + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v4 + with: + node-version: "20" + + - name: Install tree-sitter CLI + # npm install of tree-sitter-cli is the fast CI path (~5 s vs. + # ~5 min for `cargo install tree-sitter-cli`). The repo's + # preferred local path is cargo (see editors/tree-sitter-rescript/ + # README.md) — both produce the same `tree-sitter` binary that + # the install script invokes via `command -v`. The version + # tracks `tree-sitter-rescript`'s package.json devDependency + # range. + run: npm install -g tree-sitter-cli@^0.25.0 + + - name: Build pinned tree-sitter-rescript grammar + run: just install-grammar + + - name: Verify generated parser + # `tree-sitter generate` is supposed to drop src/parser.c into + # the cloned grammar. If it didn't, the install path is broken + # and Phase-2 walker work cannot proceed; fail loudly here + # rather than at the OCaml link step in a downstream PR. + run: | + test -f tools/vendor/tree-sitter-rescript/src/parser.c \ + || { echo "error: parser.c not produced by tree-sitter generate" >&2; exit 1; } + echo "parser.c size: $(wc -c < tools/vendor/tree-sitter-rescript/src/parser.c) bytes" + + - name: Smoke-parse a sample .res file + # Sanity-check that the grammar actually parses a non-trivial + # ReScript source. Picks the existing res-to-affine test fixture + # so any drift in the pinned commit's syntactic surface area + # surfaces here rather than at walker-rule writing time. + run: | + shopt -s nullglob + fixtures=(tools/res-to-affine/test/fixtures/*.res) + if [ ${#fixtures[@]} -eq 0 ]; then + echo "no .res fixtures to smoke-parse; skipping" + exit 0 + fi + tree-sitter parse \ + --quiet \ + "${fixtures[0]}" \ + --paths tools/vendor/tree-sitter-rescript \ + > /dev/null + echo "smoke-parsed: ${fixtures[0]}" diff --git a/editors/tree-sitter-rescript/README.md b/editors/tree-sitter-rescript/README.md index f5148b0f..69b554ee 100644 --- a/editors/tree-sitter-rescript/README.md +++ b/editors/tree-sitter-rescript/README.md @@ -25,13 +25,38 @@ snapshots, since AST shapes may shift. ## Install +From the repo root: + ```sh -./scripts/install.sh +just install-grammar # justfile recipe +# or directly: +./editors/tree-sitter-rescript/scripts/install.sh ``` This writes a `tree-sitter-rescript` directory under `tools/vendor/` (gitignored — same convention as the WASI adapter pinning), containing -the generated parser. Requires `git` and `tree-sitter` CLI on PATH. +the generated parser. Requires `git` and the `tree-sitter` CLI on PATH. + +The `tree-sitter` CLI can be installed either way: + +```sh +cargo install tree-sitter-cli # Rust-native, repo-preferred +npm install -g tree-sitter-cli # Node-based, also fine +``` + +CI installs via `npm` for speed (`tree-sitter-cli` from npm is a pre-built +binary, ~5 s install). The `cargo` path builds from source (~5 min on a +cold cache) and is the recommended local install because it keeps the +contributor toolchain centred on Rust rather than Node. The +`package.json` in this directory pins the version range; bump it in +sync when the upstream grammar pin moves. + +## Continuous integration + +The `migration-assistant` job in `.github/workflows/ci.yml` runs `just +install-grammar` on every PR, then smoke-parses +`tools/res-to-affine/test/fixtures/sample.res`. If the pinned commit +stops building cleanly, this job is the first signal. ## Why manifest, not copy diff --git a/editors/tree-sitter-rescript/scripts/install.sh b/editors/tree-sitter-rescript/scripts/install.sh index e4fa37dc..9b171f50 100755 --- a/editors/tree-sitter-rescript/scripts/install.sh +++ b/editors/tree-sitter-rescript/scripts/install.sh @@ -15,7 +15,9 @@ BUILD_DIR="${REPO_ROOT}/tools/vendor/tree-sitter-rescript" if ! command -v tree-sitter >/dev/null 2>&1; then echo "error: tree-sitter CLI not found on PATH" >&2 - echo " install via: npm install -g tree-sitter-cli" >&2 + echo " install via either:" >&2 + echo " cargo install tree-sitter-cli (Rust-native, repo-preferred)" >&2 + echo " npm install -g tree-sitter-cli (Node-based, also fine)" >&2 exit 2 fi diff --git a/justfile b/justfile index 527902ab..ac0d7687 100644 --- a/justfile +++ b/justfile @@ -123,6 +123,16 @@ regen-idaptik-wasm: dune exec affinescript -- tea-bridge -o ../../idaptik/public/assets/wasm/titlescreen.wasm @echo "[AffineTEA] titlescreen.wasm regenerated" +# ── Tooling (manifest-driven dev deps) ──────────────────────────────────────── + +# Fetch + build the pinned tree-sitter-rescript grammar used by the +# `.res → .affine` migration assistant (#57 Phase 2). Output is written +# to `tools/vendor/tree-sitter-rescript/` (gitignored). Requires the +# `tree-sitter` CLI on PATH — install via `cargo install tree-sitter-cli` +# (Rust-native, repo-preferred) or `npm install -g tree-sitter-cli`. +install-grammar: + ./editors/tree-sitter-rescript/scripts/install.sh + # ── Validation ──────────────────────────────────────────────────────────────── # Verify golden path end-to-end From 220f0ef2add21d6d9b6b2f1168ce0f03114bb61e Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 21 May 2026 10:41:37 +0100 Subject: [PATCH 2/2] fix(ci): drop `just` from migration-assistant job (not preinstalled) The 2a `migration-assistant` job ran `just install-grammar`, but GitHub Actions ubuntu-latest runners do not ship the `just` task runner preinstalled. Direct script invocation `./editors/tree-sitter-rescript/scripts/install.sh` is equivalent and removes the implicit dependency on a tool nothing else in this workflow uses. The justfile recipe stays for local developer ergonomics. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fabe9032..b83edfb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,7 +149,12 @@ jobs: run: npm install -g tree-sitter-cli@^0.25.0 - name: Build pinned tree-sitter-rescript grammar - run: just install-grammar + # Direct script invocation rather than `just install-grammar` — + # GitHub Actions runners do not ship `just` preinstalled, and + # there is no other recipe used in this workflow that justifies + # adding a setup step for it. The justfile recipe still exists + # for local developer ergonomics; both call the same script. + run: ./editors/tree-sitter-rescript/scripts/install.sh - name: Verify generated parser # `tree-sitter generate` is supposed to drop src/parser.c into