# oxc-walker [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![Github Actions][github-actions-src]][github-actions-href] [![Codecov][codecov-src]][codecov-href] A strongly-typed ESTree AST walker built on top of [oxc-parser](https://github.com/oxc-project/oxc). ## Usage Install package: ```sh # npm npm install oxc-walker # pnpm pnpm install oxc-walker ``` ### Walk a parsed AST ```ts import { parseSync } from "oxc-parser"; import { walk } from "oxc-walker"; const ast = parseSync("example.js", "const x = 1"); walk(ast.program, { enter(node, parent, ctx) { // ... }, }); ``` ### Parse and walk directly ```js import { parseAndWalk } from "oxc-walker"; parseAndWalk("const x = 1", "example.js", (node, parent, ctx) => { // ... }); ``` ## ⚙️ API ### `walk(ast, options)` Walk an AST. ```ts // options interface WalkOptions { /** * The function to be called when entering a node. */ enter?: (node: Node, parent: Node | null, ctx: CallbackContext) => void; /** * The function to be called when leaving a node. */ leave?: (node: Node, parent: Node | null, ctx: CallbackContext) => void; /** * The instance of `ScopeTracker` to use for tracking declarations and references. */ scopeTracker?: ScopeTracker; } interface CallbackContext { /** * The key of the current node within its parent node object, if applicable. */ key: string | number | symbol | null | undefined; /** * The zero-based index of the current node within its parent's children array, if applicable. */ index: number | null; /** * The full Abstract Syntax Tree (AST) that is being walked, starting from the root node. */ ast: Program | Node; } ``` #### `this.skip()` When called inside an `enter` callback, prevents the node's children from being walked. It is not available in `leave`. #### `this.replace(newNode)` Replaces the current node with `newNode`. When called inside `enter`, the **new node's children** will be walked. The leave callback will still be called with the original node. > ⚠️ When a `ScopeTracker` is provided, calling `this.replace()` will not update its declarations. #### `this.remove()` Removes the current node from its parent. When called inside `enter`, the removed node's children will not be walked. _This has a higher precedence than `this.replace()`, so if both are called, the node will be removed._ > ⚠️ When a `ScopeTracker` is provided, calling `this.remove()` will not update its declarations. ### `parseAndWalk(source, filename, callback, options?)` Parse the source code using `oxc-parser`, walk the resulting AST and return the `ParseResult`. Overloads: - `parseAndWalk(code, filename, enter)` - `parseAndWalk(code, filename, options)` ```ts interface ParseAndWalkOptions { /** * The function to be called when entering a node. */ enter?: (node: Node, parent: Node | null, ctx: CallbackContext) => void; /** * The function to be called when leaving a node. */ leave?: (node: Node, parent: Node | null, ctx: CallbackContext) => void; /** * The instance of `ScopeTracker` to use for tracking declarations and references. */ scopeTracker?: ScopeTracker; /** * The options for `oxc-parser` to use when parsing the code. */ parseOptions?: ParserOptions; } ``` ### `ScopeTracker` A utility to track scopes and declarations while walking an AST. It is designed to be used with the `walk` function from this library. ```ts interface ScopeTrackerOptions { /** * If true, the scope tracker will preserve exited scopes in memory. * @default false */ preserveExitedScopes?: boolean; } ``` #### Example usage: ```ts import { parseAndWalk, ScopeTracker } from "oxc-walker"; const scopeTracker = new ScopeTracker(); parseAndWalk("const x = 1; function foo() { console.log(x) }", "example.js", { scopeTracker, enter(node, parent) { if (node.type === "Identifier" && node.name === "x" && parent?.type === "CallExpression") { const declaration = scopeTracker.getDeclaration(node.name); console.log(declaration); // ScopeTrackerVariable } }, }); ``` ```ts import { parseAndWalk, ScopeTracker, walk } from "oxc-walker"; const code = ` function foo() { console.log(a) } const a = 1 `; const scopeTracker = new ScopeTracker({ preserveExitedScopes: true, }); // pre-pass to collect hoisted declarations const { program } = parseAndWalk(code, "example.js", { scopeTracker, }); // freeze the scope tracker to prevent further modifications // and prepare it for second pass scopeTracker.freeze(); // main pass to analyze references walk(program, { scopeTracker, enter(node) { if (node.type === "CallExpression" && node.callee.type === "MemberExpression" /* ... */) { const declaration = scopeTracker.getDeclaration("a"); console.log(declaration); // ScopeTrackerVariable; would be `null` without the pre-pass } }, }); ``` #### Helpers: - `scopeTracker.isDeclared(name: string): boolean` - check if an identifier is declared in reference to the current scope - `scopeTracker.getDeclaration(name: string): ScopeTrackerNode | null` - get the scope tracker node with metadata for a given identifier name in reference to the current scope - `scopeTracker.freeze()` - freeze the scope tracker to prevent further modifications and prepare for second pass (useful for multi-pass analysis) - `scopeTracker.getCurrentScope(): string` - get the key of the current scope (a unique identifier for the scope, do not rely on its format) - `scopeTracker.isCurrentScopeUnder(scopeKey: string): boolean` - check if the current scope is a child of the given scope key ## 💻 Development - Clone this repository - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` - Install dependencies using `pnpm install` - Run interactive tests using `pnpm dev` ## License Made with ❤️ Published under [MIT License](./LICENCE). [npm-version-src]: https://img.shields.io/npm/v/oxc-walker?style=flat-square [npm-version-href]: https://npmjs.com/package/oxc-walker [npm-downloads-src]: https://img.shields.io/npm/dm/oxc-walker?style=flat-square [npm-downloads-href]: https://npm.chart.dev/oxc-walker [github-actions-src]: https://img.shields.io/github/actions/workflow/status/danielroe/oxc-walker/ci.yml?branch=main&style=flat-square [github-actions-href]: https://github.com/danielroe/oxc-walker/actions?query=workflow%3Aci [codecov-src]: https://img.shields.io/codecov/c/gh/danielroe/oxc-walker/main?style=flat-square [codecov-href]: https://codecov.io/gh/danielroe/oxc-walker