Yuku
Parser
Yuku’s parser turns JavaScript and TypeScript source code into an Abstract Syntax Tree.
Node.js
npm install yuku-parser
import { parse } from "yuku-parser";
const { program, comments, diagnostics } = parse("const x = 1 + 2;");
Outputs an ESTree / TS-ESTree-compatible AST matching Oxc. Runs 3-10x faster than alternatives on npm. See yuku-parser on npm for the full API.
Zig
zig fetch --save git+https://github.com/yuku-toolchain/yuku.git
In your build.zig.
const yuku_dep = b.dependency("yuku", .{
.target = target,
.optimize = optimize,
});
my_module.addImport("parser", yuku_dep.module("parser"));
Yuku requires Zig 0.16.0 or later.
Quick Start
const std = @import("std");
const parser = @import("parser");
pub fn main() !void {
// the smp allocator is used as the backing allocator for the tree's internal arena
var tree = try parser.parse(std.heap.smp_allocator, "const x = 5;", .{});
defer tree.deinit();
for (tree.diagnostics.items) |d| {
std.debug.print("{s}\n", .{d.message});
}
}
Options
parse takes an Options struct to configure the parsing mode.
const tree = try parser.parse(allocator, source, .{
.source_type = .module,
.lang = .jsx,
});
| Field | Values | Default | Description |
|---|---|---|---|
source_type | .script, .module | .module | Script mode or ES module mode (strict mode) |
lang | .js, .ts, .jsx, .tsx, .dts | .js | Language variant and syntax features to enable |
preserve_parens | true, false | true | Keep ParenthesizedExpression nodes in the AST |
allow_return_outside_function | true, false | false | Allow return statements at the top level |
comments | .none, .flat, .attached, .both | .flat | Collect comments as a flat list (tree.comments), attached to host nodes (tree.commentsOf), or both. See Comments |
Both fields can be inferred from a file path.
const tree = try parser.parse(allocator, source, .{
.source_type = .fromPath("app.cjs"), // .script
.lang = .fromPath("app.tsx"), // .tsx
});
The Tree
parse returns a Tree containing the full AST, diagnostics, and source metadata. The allocator passed to parse is used as the backing allocator for the tree’s internal arena, so tree.deinit() frees everything at once.
The AST is a flat array of nodes referenced by integer index. tree.root is always a program node, so unpack it directly. Everything below it is a tagged union you switch on.
var tree = try parser.parse(allocator, source, .{});
defer tree.deinit();
const program = tree.data(tree.root).program;
for (tree.extra(program.body)) |child_idx| {
switch (tree.data(child_idx)) {
.variable_declaration => |decl| {
for (tree.extra(decl.declarators)) |d| {
_ = d;
}
},
.function => |func| {
_ = func;
},
else => {},
}
}
The four read primitives are tree.data(idx) for a node’s typed payload, tree.span(idx) for its source range, tree.extra(range) for a variadic child list, and tree.string(handle) for string content. See the AST reference for the full node catalog, the field conventions, and the eight categorical predicates on NodeData.
Diagnostics
The parser recovers from errors and continues, so a single parse produces the full AST alongside all diagnostics.
for (tree.diagnostics.items) |d| {
std.debug.print("[{s}] {s} at {d}..{d}\n", .{
d.severity.toString(), d.message, d.span.start, d.span.end,
});
for (d.labels) |label| {
std.debug.print(" {d}..{d}: {s}\n", .{ label.span.start, label.span.end, label.message });
}
if (d.help) |help| {
std.debug.print(" help: {s}\n", .{help});
}
}
Each diagnostic has a severity (.error, .warning, .hint, .info), a message, a source span, optional labels pointing to related code regions, and optional help text.
Going Further
- Semantic Analysis documents the semantic model of a tree, with scopes, symbols, resolved references, early errors, and module records.
- Traverse walks the AST with typed visitor hooks in four modes (basic, scoped, semantic, transform).
- Codegen prints a tree back to JavaScript/TypeScript source.