diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 578b151c..ead9b5ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,10 +77,9 @@ jobs: # `dune runtest` — not an optional extra that merely widens # coverage. Dropping it turns the walker suite red, which is # the intended behaviour: see that file's header comment. - # Pin exact version + --ignore-scripts: Sonar githubactions:S8543 - # (unlocked versions) and S6505 (lifecycle scripts). Same binary - # the grammar's package.json asks for (^0.25.0 floor). - run: npm install -g --ignore-scripts tree-sitter-cli@0.25.0 + # GitHub release binary (no npm postinstall). --ignore-scripts + # on tree-sitter-cli@0.25.0 left the `tree-sitter` binary missing. + run: ./scripts/install-tree-sitter-cli.sh - name: Build pinned tree-sitter-rescript grammar run: ./editors/tree-sitter-rescript/scripts/install.sh - name: Build @@ -241,10 +240,9 @@ jobs: # so this job needs the same grammar prerequisites as `build`. # Before the skip was removed, this job was green while running # zero walker tests. - # Pin exact version + --ignore-scripts: Sonar githubactions:S8543 - # (unlocked versions) and S6505 (lifecycle scripts). Same binary - # the grammar's package.json asks for (^0.25.0 floor). - run: npm install -g --ignore-scripts tree-sitter-cli@0.25.0 + # GitHub release binary (no npm postinstall). --ignore-scripts + # on tree-sitter-cli@0.25.0 left the `tree-sitter` binary missing. + run: ./scripts/install-tree-sitter-cli.sh - name: Build pinned tree-sitter-rescript grammar run: ./editors/tree-sitter-rescript/scripts/install.sh - name: Run tests with bisect_ppx instrumentation @@ -347,17 +345,9 @@ jobs: 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. - # Pin exact version + --ignore-scripts: Sonar githubactions:S8543 - # (unlocked versions) and S6505 (lifecycle scripts). Same binary - # the grammar's package.json asks for (^0.25.0 floor). - run: npm install -g --ignore-scripts tree-sitter-cli@0.25.0 + # GitHub release binary (no npm postinstall). --ignore-scripts + # on tree-sitter-cli@0.25.0 left the `tree-sitter` binary missing. + run: ./scripts/install-tree-sitter-cli.sh - name: Build pinned tree-sitter-rescript grammar # Direct script invocation rather than `just install-grammar` — # GitHub Actions runners do not ship `just` preinstalled, and diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index d06b225a..1422f8a9 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -28,7 +28,9 @@ jobs: errors=0 for f in .github/workflows/*.yml .github/workflows/*.yaml; do [ -f "$f" ] || continue - if ! head -1 "$f" | grep -q "SPDX-License-Identifier"; then + # actions-lock prepends a managed-by comment, so SPDX is + # often line 2. Accept it anywhere in the first 5 lines. + if ! head -5 "$f" | grep -q "SPDX-License-Identifier"; then echo "ERROR: $f missing SPDX header" errors=$((errors + 1)) fi diff --git a/affinescript-vite/README.adoc b/affinescript-vite/README.adoc new file mode 100644 index 00000000..13a63706 --- /dev/null +++ b/affinescript-vite/README.adoc @@ -0,0 +1,23 @@ += affinescript-vite +:toc: macro + +toc::[] + +Vite plugin **scaffold** for AffineScript (issue #56 remainder). +Not a production bundler integration. + +== Use + +[source,javascript] +---- +import affinescript from "affinescript-vite"; + +export default { + plugins: [affinescript()], +}; +---- + +`*.affine` modules are compiled with `affinescript compile --bun-esm`. +The compiler must be on `PATH` (or set `AFFINESCRIPT` / `compiler` option). + +Deno-ESM is retired; the emit target is Bun-ESM. diff --git a/affinescript-vite/index.js b/affinescript-vite/index.js new file mode 100644 index 00000000..72e4c2a1 --- /dev/null +++ b/affinescript-vite/index.js @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 +/** + * Vite plugin scaffold for AffineScript (#56). + * + * Transforms `*.affine` sources by shelling out to `affinescript compile + * --bun-esm`. This is a compile-pass wiring, not a production bundler + * integration: the compiler must be on PATH, and the plugin does not + * ship a JS-hosted AffineScript frontend. + */ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +export default function affinescriptVite(options = {}) { + const compiler = options.compiler || process.env.AFFINESCRIPT || "affinescript"; + return { + name: "affinescript", + enforce: "pre", + async transform(_code, id) { + const filename = id.split("?")[0]; + if (!filename.endsWith(".affine")) return null; + const dir = mkdtempSync(join(tmpdir(), "affinescript-vite-")); + const src = join(dir, "input.affine"); + const out = join(dir, "out.bun.js"); + try { + writeFileSync(src, _code); + const r = spawnSync(compiler, ["compile", src, "-o", out, "--bun-esm"], { + encoding: "utf8", + }); + if (r.status !== 0) { + const msg = (r.stderr || r.stdout || "affinescript compile failed").trim(); + this.error(msg); + return null; + } + return { code: readFileSync(out, "utf8"), map: null }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }, + }; +} diff --git a/affinescript-vite/package.json b/affinescript-vite/package.json new file mode 100644 index 00000000..097db0e5 --- /dev/null +++ b/affinescript-vite/package.json @@ -0,0 +1,14 @@ +{ + "name": "affinescript-vite", + "version": "0.0.1", + "description": "Vite plugin scaffold: compile .affine files with --bun-esm", + "license": "MPL-2.0", + "type": "module", + "main": "index.js", + "exports": { + ".": "./index.js" + }, + "peerDependencies": { + "vite": ">=5" + } +} diff --git a/docs/ECOSYSTEM.adoc b/docs/ECOSYSTEM.adoc index 2be9f4b4..df08e0b0 100644 --- a/docs/ECOSYSTEM.adoc +++ b/docs/ECOSYSTEM.adoc @@ -166,7 +166,10 @@ against a real Int-handle host DOM, mutation log asserted. `stdlib/Dom.affine` + `stdlib/Console.affine`. Remaining: full idaptik surface. -|`affinescript-vite` |scaffold |Build-tool integration shell. +|`affinescript-vite` |scaffold |In-tree Vite plugin shell +(`affinescript-vite/`): transforms `*.affine` via +`affinescript compile --bun-esm`. Compiler must be on PATH. Not a +production bundler. |`affinescript-deno-test` |historical |Smoke-test harness from the retired Deno-ESM target. JS-host ESM is Bun-ESM (`--bun-esm`). diff --git a/lib/codegen_deno.ml b/lib/codegen_deno.ml index 84caf529..a7a36c43 100644 --- a/lib/codegen_deno.ml +++ b/lib/codegen_deno.ml @@ -1407,7 +1407,14 @@ let rec gen_expr ctx (expr : expr) : string = | ExprReturn (Some e) -> iife ctx ("return " ^ gen_expr ctx e ^ ";") | ExprReturn None -> iife ctx "return Unit;" (* #459: break/continue lower to the corresponding JS keywords. The - wrapping Iram) -> mangle p.p_name.name) elam_params in + wrapping IIFE pattern used for `return` doesn't work here — JS's + `break`/`continue` only target the nearest enclosing loop and an + IIFE wraps the keyword in a new function frame. Emit a bare + statement and rely on the parent block-flatten machinery. *) + | ExprBreak _ -> iife ctx "break;" + | ExprContinue _ -> iife ctx "continue;" + | ExprLambda { elam_params; elam_body; elam_ret_ty = _ } -> + let ps = List.map (fun (p : param) -> mangle p.p_name.name) elam_params in "((" ^ String.concat ", " ps ^ ") => " ^ gen_expr ctx elam_body ^ ")" | ExprTry { et_body; et_catch; et_finally } -> gen_try ctx et_body et_catch et_finally @@ -1800,16 +1807,6 @@ let rec type_expr_name : type_expr -> string option = function | TyOwn t | TyRef (_, t) | TyMut (_, t) -> type_expr_name t | _ -> None -(* The struct (if any, among [known]) that [fd]'s first parameter is typed - as — i.e. [fd] is a receiver-first method of that struct. *) -let receiver_struct ~(known : (string, 'a) Hashtbl.t) (fd : fn_decl) - : (string * string) option = - match fd.fd_params withtion = function - | TyCon id | TyVar id -> Some id.name - | TyApp (id, _) -> Some id.name - | TyOwn t | TyRef (_, t) | TyMut (_, t) -> type_expr_name t - | _ -> None - (* The struct (if any, among [known]) that [fd]'s first parameter is typed as — i.e. [fd] is a receiver-first method of that struct. *) let receiver_struct ~(known : (string, 'a) Hashtbl.t) (fd : fn_decl) diff --git a/scripts/install-tree-sitter-cli.sh b/scripts/install-tree-sitter-cli.sh new file mode 100755 index 00000000..998d74dc --- /dev/null +++ b/scripts/install-tree-sitter-cli.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Install a pinned tree-sitter CLI binary from GitHub Releases. +# +# Used by CI instead of `npm install -g --ignore-scripts tree-sitter-cli`: +# that package's binary is fetched in a postinstall script, so +# --ignore-scripts (Sonar S6505) leaves `tree-sitter` missing (ENOENT). +# A release tarball has no lifecycle scripts. +set -euo pipefail + +VER="${TREE_SITTER_CLI_VERSION:-0.25.0}" +DEST="${TREE_SITTER_CLI_DEST:-/usr/local/bin/tree-sitter}" + +arch="$(uname -m)" +case "$arch" in + x86_64|amd64) ts_arch=x64 ;; + aarch64|arm64) ts_arch=arm64 ;; + *) + echo "error: unsupported arch $arch" >&2 + exit 1 + ;; +esac + +url="https://github.com/tree-sitter/tree-sitter/releases/download/v${VER}/tree-sitter-linux-${ts_arch}.gz" +tmp="$(mktemp)" +trap 'rm -f "$tmp"' EXIT +curl -fsSL "$url" | gunzip > "$tmp" +chmod +x "$tmp" + +if [ -w "$(dirname "$DEST")" ]; then + mv "$tmp" "$DEST" + trap - EXIT +else + sudo mv "$tmp" "$DEST" + trap - EXIT +fi + +"$DEST" --version