ast-walker-scope
Advanced tools
+34
-20
@@ -1,29 +0,32 @@ | ||
| import { ParserPlugin, ParseResult } from '@babel/parser'; | ||
| import { Node, Function, Identifier, File, VariableDeclaration } from '@babel/types'; | ||
| import { ParseResult, ParserPlugin } from "@babel/parser"; | ||
| import { File, Function, Identifier, Node, VariableDeclaration } from "@babel/types"; | ||
| //#region src/types.d.ts | ||
| interface ParseOptions { | ||
| filename?: string; | ||
| parserPlugins?: ParserPlugin[]; | ||
| filename?: string; | ||
| parserPlugins?: ParserPlugin[]; | ||
| } | ||
| type Scope = Record<string, Node>; | ||
| interface WalkerContext { | ||
| skip: () => void; | ||
| remove: () => void; | ||
| replace: (node: Node) => void; | ||
| skip: () => void; | ||
| remove: () => void; | ||
| replace: (node: Node) => void; | ||
| } | ||
| interface ScopeContext { | ||
| parent: Node | undefined | null; | ||
| key: string | undefined | null; | ||
| index: number | undefined | null; | ||
| scope: Scope; | ||
| scopes: Scope[]; | ||
| level: number; | ||
| parent: Node | undefined | null; | ||
| key: string | undefined | null; | ||
| index: number | undefined | null; | ||
| scope: Scope; | ||
| scopes: Scope[]; | ||
| level: number; | ||
| } | ||
| interface WalkerHooks { | ||
| enter?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| enterAfter?: (this: ScopeContext, node: Node) => void; | ||
| leave?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| leaveAfter?: (this: ScopeContext, node: Node) => void; | ||
| enter?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| enterAfter?: (this: ScopeContext, node: Node) => void; | ||
| leave?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| leaveAfter?: (this: ScopeContext, node: Node) => void; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/babel.d.ts | ||
| declare const isNewScope: (node: Node | undefined | null) => boolean; | ||
@@ -36,6 +39,17 @@ declare function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void): void; | ||
| declare function walk(code: string, walkHooks: WalkerHooks, { filename, parserPlugins }?: ParseOptions): ParseResult<File>; | ||
| declare function walkAST(node: Node | Node[], { enter, leave, enterAfter, leaveAfter }: WalkerHooks): void; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare function walk(code: string, walkHooks: WalkerHooks, { | ||
| filename, | ||
| parserPlugins | ||
| }?: ParseOptions): ParseResult<File>; | ||
| declare function walkAST(node: Node | Node[], { | ||
| enter, | ||
| leave, | ||
| enterAfter, | ||
| leaveAfter | ||
| }: WalkerHooks): void; | ||
| declare function getRootScope(nodes: Node[]): Scope; | ||
| export { type ParseOptions, type Scope, type ScopeContext, type WalkerContext, type WalkerHooks, babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; | ||
| //#endregion | ||
| export { ParseOptions, Scope, ScopeContext, WalkerContext, WalkerHooks, babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
+16
-20
| { | ||
| "name": "ast-walker-scope", | ||
| "version": "0.7.1", | ||
| "version": "0.8.0", | ||
| "description": "Traverse Babel AST with scope information.", | ||
@@ -24,6 +24,3 @@ "type": "module", | ||
| "exports": { | ||
| ".": { | ||
| "require": "./dist/index.cjs", | ||
| "import": "./dist/index.js" | ||
| }, | ||
| ".": "./dist/index.js", | ||
| "./*": "./*" | ||
@@ -35,23 +32,22 @@ }, | ||
| "dependencies": { | ||
| "@babel/parser": "^7.26.9", | ||
| "ast-kit": "^1.4.0" | ||
| "@babel/parser": "^7.27.2", | ||
| "ast-kit": "^1.4.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@babel/types": "^7.26.9", | ||
| "@sxzz/eslint-config": "^5.2.0", | ||
| "@sxzz/prettier-config": "^2.2.0", | ||
| "@types/node": "^22.13.9", | ||
| "bumpp": "^10.0.3", | ||
| "eslint": "^9.21.0", | ||
| "@babel/types": "^7.27.1", | ||
| "@sxzz/eslint-config": "^7.0.0", | ||
| "@sxzz/prettier-config": "^2.2.1", | ||
| "@types/node": "^22.15.17", | ||
| "bumpp": "^10.1.0", | ||
| "eslint": "^9.26.0", | ||
| "magic-string": "^0.30.17", | ||
| "oxc-transform": "^0.55.0", | ||
| "prettier": "^3.5.3", | ||
| "tsdown": "^0.6.3", | ||
| "tsx": "^4.19.3", | ||
| "typescript": "^5.8.2", | ||
| "vite": "^6.2.0", | ||
| "vitest": "^3.0.7" | ||
| "tsdown": "^0.11.1", | ||
| "tsx": "^4.19.4", | ||
| "typescript": "^5.8.3", | ||
| "vite": "^6.3.5", | ||
| "vitest": "^3.1.3" | ||
| }, | ||
| "engines": { | ||
| "node": ">=18.12.0" | ||
| "node": ">=20.18.0" | ||
| }, | ||
@@ -58,0 +54,0 @@ "prettier": "@sxzz/prettier-config", |
-210
| "use strict"; | ||
| //#region rolldown:runtime | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| const ast_kit = __toESM(require("ast-kit")); | ||
| const __babel_parser = __toESM(require("@babel/parser")); | ||
| //#region src/utils/babel.ts | ||
| const NEW_SCOPE = [ | ||
| "CatchClause", | ||
| "ForInStatement", | ||
| "ForOfStatement" | ||
| ]; | ||
| const isNewScope = (node) => node && NEW_SCOPE.includes(node.type) || (0, ast_kit.isFunctionType)(node); | ||
| function walkFunctionParams(node, onIdent) { | ||
| for (const p of node.params) for (const id of extractIdentifiers(p)) onIdent(id); | ||
| } | ||
| function extractIdentifiers(param, nodes = []) { | ||
| switch (param.type) { | ||
| case "Identifier": | ||
| nodes.push(param); | ||
| break; | ||
| case "MemberExpression": { | ||
| let object = param; | ||
| while (object.type === "MemberExpression") object = object.object; | ||
| nodes.push(object); | ||
| break; | ||
| } | ||
| case "ObjectPattern": | ||
| for (const prop of param.properties) if (prop.type === "RestElement") extractIdentifiers(prop.argument, nodes); | ||
| else extractIdentifiers(prop.value, nodes); | ||
| break; | ||
| case "ArrayPattern": | ||
| param.elements.forEach((element) => { | ||
| if (element) extractIdentifiers(element, nodes); | ||
| }); | ||
| break; | ||
| case "RestElement": | ||
| extractIdentifiers(param.argument, nodes); | ||
| break; | ||
| case "AssignmentPattern": | ||
| extractIdentifiers(param.left, nodes); | ||
| break; | ||
| } | ||
| return nodes; | ||
| } | ||
| function babelParse(code, filename, parserPlugins = []) { | ||
| const plugins = parserPlugins || []; | ||
| if (filename) { | ||
| if (/\.tsx?$/.test(filename)) plugins.push("typescript"); | ||
| if (filename.endsWith("x")) plugins.push("jsx"); | ||
| } | ||
| const ast = (0, __babel_parser.parse)(code, { | ||
| sourceType: "module", | ||
| plugins | ||
| }); | ||
| return ast; | ||
| } | ||
| function walkVariableDeclaration(stmt, register) { | ||
| if (stmt.declare) return; | ||
| for (const decl of stmt.declarations) for (const id of extractIdentifiers(decl.id)) register(id); | ||
| } | ||
| function walkNewIdentifier(node, register) { | ||
| if (node.type === "ExportNamedDeclaration" && node.declaration) node = node.declaration; | ||
| if (node.type === "VariableDeclaration") walkVariableDeclaration(node, register); | ||
| else if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") { | ||
| if (node.declare || !node.id) return; | ||
| register(node.id); | ||
| } else if (node.type === "ExportNamedDeclaration" && node.declaration && node.declaration.type === "VariableDeclaration") walkVariableDeclaration(node.declaration, register); | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function walk(code, walkHooks, { filename, parserPlugins } = {}) { | ||
| const ast = babelParse(code, filename, parserPlugins); | ||
| walkAST(ast.program, walkHooks); | ||
| return ast; | ||
| } | ||
| function walkAST(node, { enter, leave, enterAfter, leaveAfter }) { | ||
| let currentScope = {}; | ||
| const scopeStack = [currentScope]; | ||
| const ast = Array.isArray(node) ? { | ||
| type: "Program", | ||
| body: node | ||
| } : node; | ||
| (0, ast_kit.walkAST)(ast, { | ||
| enter(node$1, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node$1, [parent, ...args]); | ||
| enter?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node$1); | ||
| node$1 = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| enterNode(node$1, parent); | ||
| enterAfter?.call(scopeCtx(), node$1); | ||
| } | ||
| }, | ||
| leave(node$1, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node$1, [parent, ...args]); | ||
| leave?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node$1); | ||
| node$1 = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| leaveNode(node$1, parent); | ||
| leaveAfter?.call(scopeCtx(), node$1); | ||
| } | ||
| } | ||
| }); | ||
| function getHookContext(ctx, node$1, [parent, key, index]) { | ||
| const scopeCtx = () => ({ | ||
| parent, | ||
| key, | ||
| index, | ||
| scope: scopeStack.reduce((prev, curr) => ({ | ||
| ...prev, | ||
| ...curr | ||
| }), {}), | ||
| scopes: scopeStack, | ||
| level: scopeStack.length | ||
| }); | ||
| let isSkip = false; | ||
| let isRemoved = false; | ||
| let newNode = node$1; | ||
| const walkerCtx = { | ||
| skip() { | ||
| isSkip = true; | ||
| ctx.skip(); | ||
| }, | ||
| replace(node$2) { | ||
| newNode = node$2; | ||
| }, | ||
| remove() { | ||
| isRemoved = true; | ||
| } | ||
| }; | ||
| return { | ||
| scopeCtx, | ||
| walkerCtx, | ||
| isSkip: () => isSkip, | ||
| isRemoved: () => isRemoved, | ||
| getNode: () => newNode | ||
| }; | ||
| } | ||
| function enterNode(node$1, parent) { | ||
| if (isNewScope(node$1) || node$1.type === "BlockStatement" && !isNewScope(parent)) scopeStack.push(currentScope = {}); | ||
| if ((0, ast_kit.isFunctionType)(node$1)) walkFunctionParams(node$1, registerBinding); | ||
| else if (node$1.type === "CatchClause" && node$1.param && node$1.param.type === "Identifier") registerBinding(node$1.param); | ||
| if (node$1.type === "BlockStatement" || node$1.type === "Program") { | ||
| for (const stmt of node$1.body) if (stmt.type === "VariableDeclaration" && stmt.kind === "var") walkVariableDeclaration(stmt, registerBinding); | ||
| else if (stmt.type === "FunctionDeclaration" && stmt.id) registerBinding(stmt.id); | ||
| } | ||
| } | ||
| function leaveNode(node$1, parent) { | ||
| if (isNewScope(node$1) || node$1.type === "BlockStatement" && !isNewScope(parent)) { | ||
| scopeStack.pop(); | ||
| currentScope = scopeStack.at(-1); | ||
| } | ||
| walkNewIdentifier(node$1, registerBinding); | ||
| } | ||
| function registerBinding(id) { | ||
| if (currentScope) currentScope[id.name] = id; | ||
| else error("registerBinding called without active scope, something is wrong.", id); | ||
| } | ||
| function error(msg, node$1) { | ||
| const e = new Error(msg); | ||
| e.node = node$1; | ||
| throw e; | ||
| } | ||
| } | ||
| function getRootScope(nodes) { | ||
| const scope = {}; | ||
| for (const node of nodes) walkNewIdentifier(node, (id) => { | ||
| scope[id.name] = id; | ||
| }); | ||
| return scope; | ||
| } | ||
| //#endregion | ||
| exports.babelParse = babelParse | ||
| exports.extractIdentifiers = extractIdentifiers | ||
| exports.getRootScope = getRootScope | ||
| exports.isNewScope = isNewScope | ||
| exports.walk = walk | ||
| exports.walkAST = walkAST | ||
| exports.walkFunctionParams = walkFunctionParams | ||
| exports.walkNewIdentifier = walkNewIdentifier | ||
| exports.walkVariableDeclaration = walkVariableDeclaration |
| import { ParserPlugin, ParseResult } from '@babel/parser'; | ||
| import { Node, Function, Identifier, File, VariableDeclaration } from '@babel/types'; | ||
| interface ParseOptions { | ||
| filename?: string; | ||
| parserPlugins?: ParserPlugin[]; | ||
| } | ||
| type Scope = Record<string, Node>; | ||
| interface WalkerContext { | ||
| skip: () => void; | ||
| remove: () => void; | ||
| replace: (node: Node) => void; | ||
| } | ||
| interface ScopeContext { | ||
| parent: Node | undefined | null; | ||
| key: string | undefined | null; | ||
| index: number | undefined | null; | ||
| scope: Scope; | ||
| scopes: Scope[]; | ||
| level: number; | ||
| } | ||
| interface WalkerHooks { | ||
| enter?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| enterAfter?: (this: ScopeContext, node: Node) => void; | ||
| leave?: (this: WalkerContext & ScopeContext, node: Node) => void; | ||
| leaveAfter?: (this: ScopeContext, node: Node) => void; | ||
| } | ||
| declare const isNewScope: (node: Node | undefined | null) => boolean; | ||
| declare function walkFunctionParams(node: Function, onIdent: (id: Identifier) => void): void; | ||
| declare function extractIdentifiers(param: Node, nodes?: Identifier[]): Identifier[]; | ||
| declare function babelParse(code: string, filename?: string, parserPlugins?: ParserPlugin[]): ParseResult<File>; | ||
| declare function walkVariableDeclaration(stmt: VariableDeclaration, register: (id: Identifier) => void): void; | ||
| declare function walkNewIdentifier(node: Node, register: (id: Identifier) => void): void; | ||
| declare function walk(code: string, walkHooks: WalkerHooks, { filename, parserPlugins }?: ParseOptions): ParseResult<File>; | ||
| declare function walkAST(node: Node | Node[], { enter, leave, enterAfter, leaveAfter }: WalkerHooks): void; | ||
| declare function getRootScope(nodes: Node[]): Scope; | ||
| export { type ParseOptions, type Scope, type ScopeContext, type WalkerContext, type WalkerHooks, babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
13
-7.14%12468
-40.92%5
-28.57%223
-46.27%Updated
Updated