README · Get started · Migration · Security · Performance
| Surface | v2 beta contract |
|---|---|
| Guest source | ES5.1 script with no imports; return a result as the final completion value |
| Modern syntax | Downlevel with Babel or SWC before compile(); no automatic polyfills |
| Generated output | CommonJS artifact; bundle with esbuild or another tool for browsers and other module formats |
| Execution modes | sandbox by default; explicit trusted pass-through mode for fully trusted programs |
| Dynamic code | Literal static eval/Function inputs can be AOT-compiled; runtime-generated source is unavailable |
| Debugging | Optional inline or external Source Map v3 plus generated-code, HIR, and MIR inspection |
| Resource control | Dedicated Worker timeout and host-enforced size limits; no portable browser hard-memory quota |
| Release-tested hosts | Node 24, Deno 2, Bun, Chromium, Firefox, and WebKit |
| Language evidence | Release-gated, pinned Test262 tests carrying the ES5-era es5id marker |
Newer built-ins such as typed arrays, Map, Set, Promise, Symbol,
BigInt, Reflect, Proxy, Atomics, WeakRef, and Intl follow host
availability. They do not expand the ES5.1 syntax accepted by the compiler.
The pinned Test262 gate is the executable form of sablejs's ES5.1 contract: every run compiles each eligible test, executes it in a trusted-mode instance, and A/B-compares failures against the same source on native V8. This document is the policy record of that runner — what is in the corpus, what is excluded, what is adjusted to ES5.1 expectations, and how results are attributed.
Runner: test/conformance/test262.js. Invocation and archiving:
npm run upstream:fetch -- test262 # pinned revision checkout
npm run test262 # gate; exits non-zero on sablejs failures
npm run test262:archive # full report -> archives/test262/
Only files under test/language/** and test/built-ins/** whose frontmatter
carries an es5id are eligible. That key is Test262's own marker for tests
whose assertions describe an ES5-era specification clause, which is the
population sablejs claims. Everything else in the upstream suite — ES2015+
feature tests without ES5 ids, annex B material, and harness-only files —
is outside the corpus by definition, not by policy exclusion.
Each eligible file runs in the variants its flags declare: onlyStrict and
noStrict/raw narrow to one mode, otherwise both strict and sloppy.
sablejs deliberately removes the runtime dynamic-code surface: global eval
and Function constructed with runtime-generated source are rejected
(literal eval/Function inputs are AOT-compiled at build time). Tests that
would require that surface cannot pass by design and are excluded:
- all of
built-ins/eval/*, and built-ins/Object/getOwnPropertyNames/15.2.3.4-4-1.js(asserts theevalglobal exists, which sablejs intentionally removes).
The remaining exclusion is token-based, not text-based: the source is run
through acorn's tokenizer, and a file is excluded only when it actually
touches the surface — a name token eval, or Function followed by ( or
by .call/.apply/.bind(. Descriptions, comments, string literals, and
regular expressions that merely contain the words are not enough, and a file
whose tested early error is the tokenization itself can never execute
dynamic code, so it stays eligible.
Test262 is a living suite: its tests evolve to describe current engines even
when their es5id is unchanged. Where a current copy asserts behavior newer
than ES5.1, the runner pins the ES5.1 expectation instead:
language/statements/function/13.2-15-1.js— the upstream copy asserts the function-length descriptor is configurable (configurable: true, the ES2015+ form); ES5.1 13.2 step 15 requires[[Configurable]]: false. The runner patches the expected value back tofalse.language/expressions/object/prop-dup-*— ES5.1 makes duplicate properties in object literals an early error for the strict mode and for the non-(data,data) forms (getter/setter collisions) even in sloppy mode. The runner attaches the ES5.1SyntaxErrorexpectation to exactly those variants where the current upstream copy allows the program.language/expressions/object/11.1.5-2gs.jsandlanguage/statements/break/S12.8_A3.js,S12.8_A4_*— strict-mode object-literal andbreakearly errors that ES5.1 requires but the current copies dropped; the runner restores theSyntaxErrorexpectations.
These paths are enumerated in es5VariantMetadata and
hasES5SourceAdjustment and are counted in the report as es5Adjusted.
Positive-syntax tests that no longer parse as ES5 (current Test262 is written
for modern engines even when the clause is old) are transpiled with
Babel preset-env targeting IE 11 before running — syntax downlevel only,
no polyfills. Negative tests whose expected failure phase is parse/early
are never transpiled: the early error is exactly what is being tested.
Every sablejs failure is re-run on native V8 inside the same harness with the
same source. A native failure is diagnostic evidence, not an automatic
waiver: the gate still fails unless the exact path, strict/sloppy mode,
sablejs reason, and native reason match a reviewed entry in
test/conformance/host-failures.json. A full run also fails when an entry in
that file is no longer observed, so the list cannot silently become stale.
The current policy is empty. The report distinguishes total hostFailures,
allowedHostFailures, and hostFailurePolicyDrift; its failures array
carries both reasons and whether the entry was allowlisted.
The runner prints one JSON report on stdout:
{ revision, optimization, elapsedMs, files, variants, passed, negativePassed,
es5Adjusted, policyExcluded, hostFailures, allowedHostFailures,
hostFailurePolicyDrift, failed, codegen, failures }
The gate defaults to a 30-entry failure detail cap; npm run test262:archive
reruns with the cap raised to the full list, stamps the report with the
environment (node version, platform, arch, OS release, CPU count, time), and
writes archives/test262/<revision>-<timestamp>.json plus a latest.json
pointer. A red gate still archives the report and exits non-zero. The release
workflow archives its already-completed gate (without rerunning it) and
attaches the full report plus latest.json to the GitHub release.