WebAssembly
@utoo/lint-wasm is the browser-ready, ESM-only WebAssembly distribution of
utoo-lint. It is designed for lint playgrounds, editor integrations, Web
Workers, and Node ESM programs that lint source already held in memory.
The WebAssembly module is freestanding: it imports neither WASI nor a host filesystem API.
Installation
pnpm add @utoo/lint-wasm
The package includes its JavaScript loader, TypeScript declarations, and the
utoo-lint.wasm binary. A bundler or asset server must preserve the emitted
Wasm asset when deploying a browser application.
Browser ESM
Create one linter and reuse it across edits:
import { createUtooLint } from "@utoo/lint-wasm";const linter = await createUtooLint();const report = linter.lint("const answer = 42; debugger;", {filePath: "playground.ts",rules: {"no-debugger": "error","no-unused-vars": "warn"}});for (const diagnostic of report.diagnostics) {console.log(diagnostic.ruleId, diagnostic.message, diagnostic.range);}
createUtooLint() loads and instantiates the Wasm module asynchronously. The
returned instance's lint() and lintAndFix() methods are synchronous, which
makes reuse inexpensive but also means an interactive UI should call them from
a Web Worker.
Node ESM
The async convenience functions lazily create and reuse a default instance:
import { lint, lintAndFix } from "@utoo/lint-wasm";const report = await lint("debugger;", {filePath: "snippet.js",rules: { "no-debugger": "error" }});const fixed = await lintAndFix("const value = 1;;;", {filePath: "snippet.js",rules: { "no-extra-semi": "error" }});console.log(report.diagnostics);console.log(fixed.output);
Use createUtooLint() when an application needs an explicit instance or wants
to supply its own Wasm URL, response, bytes, compiled module, or instance:
const linter = await createUtooLint({wasm: new URL("./utoo-lint.wasm", import.meta.url)});
In-Memory Execution Model
Each call accepts one source string plus options. filePath is a virtual path:
its extension selects JavaScript, TypeScript, JSX, or TSX parsing, but the file
is never read or written. The Wasm build does not discover project config,
expand globs, traverse directories, resolve modules, or inspect dependencies.
lintAndFix() returns the fixed source in output; it does not modify a file.
Diagnostics from that call describe the final output and set
diagnosticsSource to "output". Plain lint() diagnostics describe the
input. Diagnostic and fix range values are UTF-16 offsets, so they can be
used directly with JavaScript strings and browser editor APIs.
Suppressed findings are returned separately in suppressedDiagnostics.
Rule Selection
Omitting rules uses utoo-lint's built-in default rule set:
linter.lint(source, { filePath: "input.js" });
Providing a rules object starts from all lint rules disabled and enables only
the entries in that object. In particular, an empty object disables every
configurable lint rule:
linter.lint(source, { filePath: "input.js", rules: {} });
Parser diagnostics can still be returned when rules is empty because parsing
is required to process the source.
Rules that need real project I/O cannot execute in the freestanding module. If
one of these rules is enabled, the call succeeds and its rule ID is included in
skippedRules rather than silently pretending that the project check ran. The
current filesystem-backed set is:
@alipay/ant/no-phantom-dependencies@alipay/ant/prefer-import-from-stdlibimport/defaultimport/exportimport/namedimport/namespaceimport/no-cycleimport/no-named-as-defaultimport/no-named-as-default-memberimport/no-unresolved
Check skippedRules on every result before presenting a Playground run as
equivalent to project-wide CLI linting.
Playground Architecture
Keep the initialized linter inside a long-lived Web Worker. Send only the latest source and options to it, and discard stale responses in the UI when a newer edit has already been queued.
// lint.worker.jsimport { createUtooLint } from "@utoo/lint-wasm";const linterPromise = createUtooLint();self.onmessage = async ({ data }) => {const { id, source, options, fix = false } = data;try {const linter = await linterPromise;const result = fix? linter.lintAndFix(source, options): linter.lint(source, options);self.postMessage({ id, result });} catch (error) {self.postMessage({id,error: {name: error.name,code: error.code,message: error.message,ruleId: error.ruleId}});}};
Debounce editor events on the main thread and attach a monotonically
increasing id to each request. This keeps lint execution off the rendering
thread without repeatedly compiling the Wasm module.
Build and Test
Initialize the Yuku submodule and use the Zig version documented in
CONTRIBUTING.md, then install workspace dependencies:
git submodule update --init --recursivepnpm install
Build the raw freestanding module:
pnpm build:wasm
The output is zig-out/bin/utoo-lint.wasm. To build it and copy it into the npm
package directory, run:
pnpm package:wasm
Run the raw ABI and JavaScript wrapper tests, followed by the staged package and declaration tests:
pnpm test:wasmpnpm --dir npm/@utoo/lint-wasm testpnpm --dir npm/@utoo/lint-wasm test:types
The ABI test also verifies that the module has no imports and exposes the expected memory, allocator, ABI version, and lint entry points.