API Overview
z(source)
Parse source code and return a Collection.
import { z } from "zmod";
const root = z(source);
Collection
.find(type, filter?)
Find all nodes of a given type with an optional filter.
root.find(z.CallExpression, { callee: { name: "useState" } });
.replaceWith(node | fn)
Replace each matched node.
root.find(z.Identifier, { name: "foo" }).replaceWith(z.identifier("bar"));
.remove()
Remove each matched node.
root.find(z.ImportDeclaration).remove();
.forEach(fn)
Iterate over matched nodes.
root.find(z.Identifier).forEach((path) => {
console.log(path.node.name);
});
.filter(fn)
Filter matched nodes.
root.find(z.Identifier).filter((path) => path.node.name.startsWith("use"));
.closest(type)
Traverse up to find the closest ancestor of a given type.
root.find(z.Identifier).closest(z.FunctionDeclaration);
.toSource()
Apply all patches and return the modified source.
z.withParser(parser)
Return a new z function that uses a custom parser instead of the default oxc.
import { z } from "zmod";
import { parse } from "@babel/parser";
const j = z.withParser({
parse(source) {
return parse(source, { plugins: ["typescript"], sourceType: "module" }).program;
},
});
const root = j(source);
The original z is unaffected — withParser always returns a new, isolated instance.
z.print(node)
Serialize an AST node to source code using the active printer.
const code = z.print(z.identifier("foo")); // "foo"
const code = z.print(z.callExpression(z.identifier("fn"), [])); // "fn()"
Falls back to zmod's internal printer when no custom print is provided via withParser.
Parser interface
Any object with a parse method that returns an ESTree-compatible Program node. Optionally provides a print method to enable AST node serialization.
import type { Parser } from "zmod";
const myParser: Parser = {
parse(source: string) {
// Must return an ESTree Program where every node has
// numeric `start` and `end` byte-offset properties.
return myAstLibrary.parse(source);
},
};
Parser.print — pluggable printer
Adding print to your parser enables:
replaceWith(astNode) — replace with a builder-created node instead of a string
z.print(node) — manually serialize any AST node
import { parse as babelParse } from "@babel/parser";
import generate from "@babel/generator";
import type { Parser } from "zmod";
const babelCodec: Parser = {
parse(source, options) {
return babelParse(source, {
plugins: ["typescript"],
sourceType: "module",
...options,
}).program;
},
print(node) {
return generate(node).code;
},
};
const j = z.withParser(babelCodec);
// Now builder nodes work in replaceWith:
root
.find(z.CallExpression, { callee: { name: "legacy" } })
.replaceWith((path) =>
z.callExpression(
z.memberExpression(z.identifier("api"), z.identifier("call")),
path.node.arguments,
),
);
Without print, zmod falls back to its internal printer which handles common ESTree node types. The internal printer is sufficient for simple identifier/string replacements.
Requirements for custom parsers:
- Returns an ESTree-compatible AST
- Every node must have
start and end as numeric byte offsets (used for span-based patching)
Program.body must be an array
Compatible parsers: @babel/parser, acorn, SWC (ESTree mode).
export const parser in transforms
A transform file can export a parser to override the parser used by run() — identical to jscodeshift's pattern:
import { parse } from "@babel/parser";
import type { Parser, Transform } from "zmod";
// run() picks this up automatically
export const parser: Parser = {
parse(source) {
return parse(source, {
plugins: [["decorators", { version: "legacy" }], "typescript"],
sourceType: "module",
}).program;
},
};
const transform: Transform = ({ source }, { z }) => {
return z(source).find(z.Identifier, { name: "injectable" }).replaceWith("singleton").toSource();
};
export default transform;
oxcParser
The default parser used by z. Can be imported directly if needed:
import { oxcParser } from "zmod";
run(transform, options)
Batch-run a transform over files matching a glob pattern.
import { run } from "zmod";
await run(transform, { include: "src/**/*.tsx" });
If the transform exports a parser, run() automatically uses it for all files.