🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

ast-walker-scope

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ast-walker-scope - npm Package Compare versions

Comparing version
0.8.3
to
0.9.0
+51
dist/index.d.mts
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 };
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 };
+32
-36
{
"name": "ast-walker-scope",
"version": "0.8.3",
"type": "module",
"version": "0.9.0",
"description": "Traverse Babel AST with scope information.",
"type": "module",
"author": "Kevin Deng <sxzz@sxzz.moe>",
"license": "MIT",
"funding": "https://github.com/sponsors/sxzz",
"homepage": "https://github.com/sxzz/ast-walker-scope#readme",
"bugs": {
"url": "https://github.com/sxzz/ast-walker-scope/issues"
},
"repository": {

@@ -15,43 +14,40 @@ "type": "git",

},
"author": "Kevin Deng <sxzz@sxzz.moe>",
"funding": "https://github.com/sponsors/sxzz",
"bugs": {
"url": "https://github.com/sxzz/ast-walker-scope/issues"
},
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
},
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=20.19.0"
},
"dependencies": {
"@babel/parser": "^7.28.4",
"ast-kit": "^2.1.3"
"@babel/parser": "^7.29.2",
"@babel/types": "^7.29.0",
"ast-kit": "^2.2.0"
},
"devDependencies": {
"@babel/types": "^7.28.4",
"@sxzz/eslint-config": "^7.2.7",
"@sxzz/prettier-config": "^2.2.4",
"@types/node": "^24.7.0",
"bumpp": "^10.3.1",
"eslint": "^9.37.0",
"magic-string": "^0.30.19",
"prettier": "^3.6.2",
"tsdown": "^0.15.6",
"tsx": "^4.20.6",
"typescript": "^5.9.3",
"vite": "^7.1.9",
"vitest": "^3.2.4"
"@sxzz/eslint-config": "^7.8.4",
"@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",
"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"
},
"engines": {
"node": ">=20.19.0"
},
"prettier": "@sxzz/prettier-config",
"tsdown": {
"exports": true
},
"scripts": {

@@ -62,5 +58,5 @@ "lint": "eslint .",

"test": "vitest",
"typecheck": "tsc --noEmit",
"typecheck": "tsgo --noEmit",
"release": "bumpp"
}
}
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 };
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 = {};
const scopeStack = [currentScope];
walkAST$1(Array.isArray(node) ? {
type: "Program",
body: node
} : node, {
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;
return {
scopeCtx,
walkerCtx: {
skip() {
isSkip = true;
ctx.skip();
},
replace(node$2) {
newNode = node$2;
},
remove() {
isRemoved = true;
}
},
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 (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
export { babelParse, extractIdentifiers, getRootScope, isNewScope, walk, walkAST, walkFunctionParams, walkNewIdentifier, walkVariableDeclaration };