ast-walker-scope
Advanced tools
| 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[]; | ||
| } | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/babel.d.ts | ||
| 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; | ||
| //#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; | ||
| //#endregion | ||
| export { ParseOptions, Scope, ScopeContext, WalkerContext, WalkerHooks, babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
+169
| import { isFunctionType, walkAST as walkAST$1 } from "ast-kit"; | ||
| import { parse } from "@babel/parser"; | ||
| //#region src/utils/babel.ts | ||
| const NEW_SCOPE = new Set([ | ||
| "CatchClause", | ||
| "ForInStatement", | ||
| "ForOfStatement" | ||
| ]); | ||
| const isNewScope = (node) => node && NEW_SCOPE.has(node.type) || 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"); | ||
| } | ||
| return parse(code, { | ||
| sourceType: "module", | ||
| plugins | ||
| }); | ||
| } | ||
| 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 = Object.create(null); | ||
| const scopeStack = [currentScope]; | ||
| walkAST$1(Array.isArray(node) ? { | ||
| type: "Program", | ||
| body: node | ||
| } : node, { | ||
| enter(node, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node, [parent, ...args]); | ||
| enter?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node); | ||
| node = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| enterNode(node, parent); | ||
| enterAfter?.call(scopeCtx(), node); | ||
| } | ||
| }, | ||
| leave(node, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node, [parent, ...args]); | ||
| leave?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node); | ||
| node = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| leaveNode(node, parent); | ||
| leaveAfter?.call(scopeCtx(), node); | ||
| } | ||
| } | ||
| }); | ||
| function getHookContext(ctx, node, [parent, key, index]) { | ||
| const scopeCtx = () => ({ | ||
| parent, | ||
| key, | ||
| index, | ||
| scope: scopeStack.reduce((prev, curr) => Object.assign(prev, curr), Object.create(null)), | ||
| scopes: scopeStack, | ||
| level: scopeStack.length | ||
| }); | ||
| let isSkip = false; | ||
| let isRemoved = false; | ||
| let newNode = node; | ||
| return { | ||
| scopeCtx, | ||
| walkerCtx: { | ||
| skip() { | ||
| isSkip = true; | ||
| ctx.skip(); | ||
| }, | ||
| replace(node) { | ||
| newNode = node; | ||
| }, | ||
| remove() { | ||
| isRemoved = true; | ||
| } | ||
| }, | ||
| isSkip: () => isSkip, | ||
| isRemoved: () => isRemoved, | ||
| getNode: () => newNode | ||
| }; | ||
| } | ||
| function enterNode(node, parent) { | ||
| if (isNewScope(node) || node.type === "BlockStatement" && !isNewScope(parent)) scopeStack.push(currentScope = Object.create(null)); | ||
| if (isFunctionType(node)) walkFunctionParams(node, registerBinding); | ||
| else if (node.type === "CatchClause" && node.param && node.param.type === "Identifier") registerBinding(node.param); | ||
| if (node.type === "BlockStatement" || node.type === "Program") { | ||
| for (const stmt of node.body) if (stmt.type === "VariableDeclaration" && stmt.kind === "var") walkVariableDeclaration(stmt, registerBinding); | ||
| else if (stmt.type === "FunctionDeclaration" && stmt.id) registerBinding(stmt.id); | ||
| } | ||
| } | ||
| function leaveNode(node, parent) { | ||
| if (isNewScope(node) || node.type === "BlockStatement" && !isNewScope(parent)) { | ||
| scopeStack.pop(); | ||
| currentScope = scopeStack.at(-1); | ||
| } | ||
| walkNewIdentifier(node, 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) { | ||
| const e = new Error(msg); | ||
| e.node = node; | ||
| throw e; | ||
| } | ||
| } | ||
| function getRootScope(nodes) { | ||
| const scope = Object.create(null); | ||
| for (const node of nodes) walkNewIdentifier(node, (id) => { | ||
| scope[id.name] = id; | ||
| }); | ||
| return scope; | ||
| } | ||
| //#endregion | ||
| export { babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
+18
-18
| { | ||
| "name": "ast-walker-scope", | ||
| "type": "module", | ||
| "version": "0.9.0", | ||
| "version": "0.10.0", | ||
| "description": "Traverse Babel AST with scope information.", | ||
@@ -18,3 +18,3 @@ "author": "Kevin Deng <sxzz@sxzz.moe>", | ||
| "exports": { | ||
| ".": "./dist/index.mjs", | ||
| ".": "./dist/index.js", | ||
| "./package.json": "./package.json" | ||
@@ -29,24 +29,24 @@ }, | ||
| "engines": { | ||
| "node": ">=20.19.0" | ||
| "node": "^22.18.0 || >=24.11.0" | ||
| }, | ||
| "dependencies": { | ||
| "@babel/parser": "^7.29.2", | ||
| "@babel/types": "^7.29.0", | ||
| "ast-kit": "^2.2.0" | ||
| "@babel/parser": "^8.0.0", | ||
| "@babel/types": "^8.0.0", | ||
| "ast-kit": "^3.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@sxzz/eslint-config": "^7.8.4", | ||
| "@sxzz/eslint-config": "^8.2.0", | ||
| "@sxzz/prettier-config": "^2.3.1", | ||
| "@types/node": "^25.6.0", | ||
| "@typescript/native-preview": "7.0.0-dev.20260412.1", | ||
| "bumpp": "^11.0.1", | ||
| "eslint": "^10.2.0", | ||
| "@types/node": "^25.9.3", | ||
| "@typescript/native-preview": "7.0.0-dev.20260616.1", | ||
| "bumpp": "^11.1.0", | ||
| "eslint": "^10.5.0", | ||
| "magic-string": "^0.30.21", | ||
| "prettier": "^3.8.2", | ||
| "tsdown": "^0.21.7", | ||
| "tsdown-preset-sxzz": "^0.5.0", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^6.0.2", | ||
| "vite": "^8.0.8", | ||
| "vitest": "^4.1.4" | ||
| "prettier": "^3.8.4", | ||
| "tsdown": "^0.22.2", | ||
| "tsdown-preset-sxzz": "^0.6.0", | ||
| "tsx": "^4.22.4", | ||
| "typescript": "^6.0.3", | ||
| "vite": "^8.0.16", | ||
| "vitest": "^4.1.9" | ||
| }, | ||
@@ -53,0 +53,0 @@ "prettier": "@sxzz/prettier-config", |
| 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[]; | ||
| } | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/babel.d.ts | ||
| 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; | ||
| //#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; | ||
| //#endregion | ||
| export { ParseOptions, Scope, ScopeContext, WalkerContext, WalkerHooks, babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
-169
| import { isFunctionType, walkAST as walkAST$1 } from "ast-kit"; | ||
| import { parse } from "@babel/parser"; | ||
| //#region src/utils/babel.ts | ||
| const NEW_SCOPE = new Set([ | ||
| "CatchClause", | ||
| "ForInStatement", | ||
| "ForOfStatement" | ||
| ]); | ||
| const isNewScope = (node) => node && NEW_SCOPE.has(node.type) || 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"); | ||
| } | ||
| return parse(code, { | ||
| sourceType: "module", | ||
| plugins | ||
| }); | ||
| } | ||
| 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 = Object.create(null); | ||
| const scopeStack = [currentScope]; | ||
| walkAST$1(Array.isArray(node) ? { | ||
| type: "Program", | ||
| body: node | ||
| } : node, { | ||
| enter(node, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node, [parent, ...args]); | ||
| enter?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node); | ||
| node = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| enterNode(node, parent); | ||
| enterAfter?.call(scopeCtx(), node); | ||
| } | ||
| }, | ||
| leave(node, parent, ...args) { | ||
| const { scopeCtx, walkerCtx, isSkip, isRemoved, getNode } = getHookContext(this, node, [parent, ...args]); | ||
| leave?.call({ | ||
| ...scopeCtx(), | ||
| ...walkerCtx | ||
| }, node); | ||
| node = getNode(); | ||
| if (!isSkip() && !isRemoved()) { | ||
| leaveNode(node, parent); | ||
| leaveAfter?.call(scopeCtx(), node); | ||
| } | ||
| } | ||
| }); | ||
| function getHookContext(ctx, node, [parent, key, index]) { | ||
| const scopeCtx = () => ({ | ||
| parent, | ||
| key, | ||
| index, | ||
| scope: scopeStack.reduce((prev, curr) => Object.assign(prev, curr), Object.create(null)), | ||
| scopes: scopeStack, | ||
| level: scopeStack.length | ||
| }); | ||
| let isSkip = false; | ||
| let isRemoved = false; | ||
| let newNode = node; | ||
| return { | ||
| scopeCtx, | ||
| walkerCtx: { | ||
| skip() { | ||
| isSkip = true; | ||
| ctx.skip(); | ||
| }, | ||
| replace(node) { | ||
| newNode = node; | ||
| }, | ||
| remove() { | ||
| isRemoved = true; | ||
| } | ||
| }, | ||
| isSkip: () => isSkip, | ||
| isRemoved: () => isRemoved, | ||
| getNode: () => newNode | ||
| }; | ||
| } | ||
| function enterNode(node, parent) { | ||
| if (isNewScope(node) || node.type === "BlockStatement" && !isNewScope(parent)) scopeStack.push(currentScope = Object.create(null)); | ||
| if (isFunctionType(node)) walkFunctionParams(node, registerBinding); | ||
| else if (node.type === "CatchClause" && node.param && node.param.type === "Identifier") registerBinding(node.param); | ||
| if (node.type === "BlockStatement" || node.type === "Program") { | ||
| for (const stmt of node.body) if (stmt.type === "VariableDeclaration" && stmt.kind === "var") walkVariableDeclaration(stmt, registerBinding); | ||
| else if (stmt.type === "FunctionDeclaration" && stmt.id) registerBinding(stmt.id); | ||
| } | ||
| } | ||
| function leaveNode(node, parent) { | ||
| if (isNewScope(node) || node.type === "BlockStatement" && !isNewScope(parent)) { | ||
| scopeStack.pop(); | ||
| currentScope = scopeStack.at(-1); | ||
| } | ||
| walkNewIdentifier(node, 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) { | ||
| const e = new Error(msg); | ||
| e.node = node; | ||
| throw e; | ||
| } | ||
| } | ||
| function getRootScope(nodes) { | ||
| const scope = Object.create(null); | ||
| for (const node of nodes) walkNewIdentifier(node, (id) => { | ||
| scope[id.name] = id; | ||
| }); | ||
| return scope; | ||
| } | ||
| //#endregion | ||
| export { babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
12791
0.09%218
28.99%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated