Contributing #
This guide gets you from a fresh clone to a passing test run, and shows where things live. Read docs/glossary.md before you write code or docs. It lists the words this repo uses for each concept.
Setup #
You need:
- Node 24 or later.
- The Zena compiler,
zena-cli. Build it from elematic/zena at commita7c1d2cd, following that repo'sDEVELOPMENT.md. Then putzena-clion yourPATH, or setZENAto its path.ZENA=$(./scripts/zena.sh)does all of this for you, and needs a Rust toolchain. A different Zena commit may produce a different binary.
pnpm install
./scripts/vendor.sh # ts-blank-space (the erase-mode oracle), TypeScript 6's test suites, corpus sources
pnpm test # build both engines, then run the default suite
pnpm install brings in binaryen (pinned, for wasm-opt and wasm-merge) and TypeScript. scripts/vendor.sh clones its sources into vendor/, which git ignores, and it is safe to re-run.
For the OSS sweeps and the benchmarks, also fetch the OSS corpus. It is several GB.
./scripts/oss.sh
Build #
pnpm build
scripts/build.sh compiles each engine with Zena, links in the generated data tables, and optimizes with wasm-opt. It writes:
| File | What it is |
|---|---|
build/erase.wasm |
The erase engine (shipped) |
build/downlevel.wasm |
The downlevel engine (shipped) |
build/downlevel-debug.wasm |
The downlevel engine with a dbg() probe, for scripts/debug/ |
The build prints each engine's raw and gzip size. Check them against the size table in the README when you change the engine. docs/architecture.md explains each build step and its flags.
Some engine sources are generated. Edit the generator, run it, and commit both the generator and its output:
| Generator | Writes |
|---|---|
node scripts/gen/keywords.mjs |
engine/keywords.zena, engine/keywords.wat |
node scripts/gen/chartable.mjs |
engine/chartable.zena, engine/chartable.wat |
node scripts/gen/downlevel-text.mjs |
The generated region of engine/downlevel.zena, and engine/downlevel-text.wat |
scripts/gen/layout.mjs holds the memory addresses those tables share.
Test #
pnpm test builds, then runs the default suite. Each part also runs on its own:
pnpm test:erase # erase mode against ts-blank-space
pnpm test:downlevel # downlevel mode against ts.transpileModule
pnpm test:wrapper # streaming splits, byte input, lazy loading, the browser entry
pnpm test:types # the TypeScript declarations
The wider suites need the fetched inputs. Times are from an Apple M1:
pnpm test:erase:typescript # 15 s TypeScript 6's own sources and test suites
pnpm test:downlevel:typescript # 1 min
pnpm test:erase:oss # 2 min needs ./scripts/oss.sh
pnpm test:downlevel:oss # 16 min needs ./scripts/oss.sh
pnpm test:erase:bytediff # 2 min needs ./scripts/oss.sh
pnpm test:erase:all and pnpm test:downlevel:all run every suite for one mode, the default one included.
docs/correctness.md describes what each suite checks, the corpus files, the known-failure baselines, and the open bugs. docs/downlevel-oracle.md describes how the downlevel judge works.
Run the wider suite for a mode whenever you change how that mode parses or emits. Before you send a change, run at least pnpm test and pnpm test:erase:typescript.
Debug #
The tools in scripts/debug/ work on one input at a time. Each loads build/erase.wasm unless you set TYPESTRIP_ERASE_WASM. bisect.mjs and classify.mjs print engine state when TYPESTRIP_ERASE_WASM=build/downlevel-debug.wasm.
| Tool | Use it to |
|---|---|
try.mjs '<snippet>' or try.mjs -f file.ts [chunkSize] |
Compare typestrip's output with ts-blank-space's for one input |
minimize.mjs file [threw|chunked|ast|errors] |
Shrink a failing input while it still fails the same way |
bisect.mjs file [maxChunk] |
Find the chunk sizes where streaming diverges, and where |
judge.mjs <dir|file.jsonl|substring> [--list=path] |
Run the erase judge without stopping at the first throw, and tally failures by kind |
classify.mjs fails.tsv [kind] [max] |
Group judge.mjs --list output by engine state and source context |
semidiff.mjs file |
Find which ; changes the AST when output differs from the oracle only in ; and spaces |
sweep.mjs [dir] |
Run whole and chunked transforms over a tree and list inputs that throw |
stream-sweep.mjs <dir> [maxSmall] [shard/shards] |
Check several fixed chunk sizes against the whole-input result over a tree |
Where things live #
| Path | Contents |
|---|---|
engine/ |
The engine, in Zena. strip.zena is the parser and emitter both modes share. |
src/ |
The wrapper: core.js, the Node entry index.js, the browser entry browser.js, and index.d.ts |
scripts/ |
Build, vendoring, code generation (gen/), OSS sweeps, and debug tools (debug/) |
test/ |
The judges, wrapper tests, fuzzers, and known-failure baselines (baselines/) |
corpus/ |
Checked-in test inputs, regressions, and findings |
bench/ |
Benchmarks against other tools, with their own package.json. See docs/benchmarks.md. |
examples/ |
Usage examples |
docs/ |
Reference docs |
build/, vendor/ |
Build output and fetched sources, both ignored by git |
Conventions #
- Use the glossary. If you need a new term, add it to docs/glossary.md in the same change.
- Every fixed bug gets a regression. Add a minimized input to
corpus/regressions.jsonlorcorpus/downlevel.jsonl. The steps are in docs/correctness.md. - Baselines only shrink. Don't add a new failure to a baseline. Record it as a finding.
- Keep erase mode lean. Code that only downlevel mode needs goes in
engine/downlevel.zena, behind a hook, sobuild/erase.wasmnever contains it. - Measure size and speed changes. Report the raw and gzip size of both engines before and after, and benchmark changes that could affect throughput.
- Comments explain why. Explain a constraint the code can't show. Don't narrate what the code does, and don't record history; that belongs in the commit message.
Release #
Add a changeset (pnpm changeset) with any change users will notice. .changeset/README.md explains how a version gets from a changeset to npm.
Zena compiler issues #
docs/zena-compiler-issues.md lists Zena compiler bugs and limitations this project has worked around. Each is waiting to be filed upstream.