site-panzai/node_modules/oxc-walker/dist/index.mjs

874 lines
30 KiB
JavaScript
Raw Normal View History

2026-11-13 13:57:04 +01:00
import { createRequire } from "node:module";
//#region src/utils.ts
function isNode(v) {
return v !== null && typeof v === "object" && typeof v.type === "string";
}
//#endregion
//#region src/walker/base.ts
var WalkerBase = class {
scopeTracker;
enter;
leave;
contextEnter = {
skip: () => {
this._skip = true;
},
remove: () => {
this._remove = true;
},
replace: (node) => {
this._replacement = node;
}
};
contextLeave = {
remove: this.contextEnter.remove,
replace: this.contextEnter.replace
};
_skip = false;
_remove = false;
_replacement = null;
constructor(handler, options) {
this.enter = handler.enter;
this.leave = handler.leave;
this.scopeTracker = options?.scopeTracker;
}
replace(parent, key, index, node) {
if (!parent || key === null) return;
if (index !== null) parent[key][index] = node;
else parent[key] = node;
}
insert(parent, key, index, node) {
if (!parent || key === null) return;
if (index !== null) parent[key].splice(index, 0, node);
else parent[key] = node;
}
remove(parent, key, index) {
if (!parent || key === null) return;
if (index !== null) parent[key].splice(index, 1);
else delete parent[key];
}
};
//#endregion
//#region src/walker/sync.ts
var WalkerSync = class extends WalkerBase {
constructor(handler, options) {
super(handler, options);
}
traverse(input, key, index, parent) {
const ctx = {
key: null,
index: index ?? null,
ast: input
};
const scopeTracker = this.scopeTracker;
const enter = this.enter;
const leave = this.leave;
const contextEnter = this.contextEnter;
const contextLeave = this.contextLeave;
const _walk = (input, parent, key, index, skip) => {
if (scopeTracker) scopeTracker.processNodeEnter(input);
let currentNode = input;
let removedInEnter = false;
let replacedInEnter = false;
let skipChildren = skip;
if (enter && !skip) {
const _skip = this._skip;
const _remove = this._remove;
const _replacement = this._replacement;
this._skip = false;
this._remove = false;
this._replacement = null;
ctx.key = key;
ctx.index = index;
enter.call(contextEnter, input, parent, ctx);
if (this._replacement && !this._remove) {
currentNode = this._replacement;
replacedInEnter = true;
this.replace(parent, key, index, this._replacement);
if (scopeTracker) scopeTracker.processNodeEnter(currentNode);
}
if (this._remove) {
removedInEnter = true;
currentNode = null;
this.remove(parent, key, index);
}
if (this._skip) skipChildren = true;
this._skip = _skip;
this._remove = _remove;
this._replacement = _replacement;
}
if ((!skipChildren || scopeTracker) && currentNode) for (const k in currentNode) {
if (k === "type" || k === "start" || k === "end") continue;
const node = currentNode[k];
if (!node || typeof node !== "object") continue;
if (Array.isArray(node)) for (let i = 0; i < node.length; i++) {
const child = node[i];
if (isNode(child)) {
if (_walk(child, currentNode, k, i, skipChildren) === null) i--;
}
}
else if (typeof node.type === "string") _walk(node, currentNode, k, null, skipChildren);
}
if (scopeTracker) {
if (replacedInEnter) scopeTracker.processNodeLeave(currentNode);
scopeTracker.processNodeLeave(input);
}
if (leave && !skip) {
const _replacement = this._replacement;
const _remove = this._remove;
this._replacement = null;
this._remove = false;
ctx.key = key;
ctx.index = index;
leave.call(contextLeave, input, parent, ctx);
if (this._replacement && !this._remove) {
currentNode = this._replacement;
if (removedInEnter) this.insert(parent, key, index, this._replacement);
else this.replace(parent, key, index, this._replacement);
}
if (this._remove) {
currentNode = null;
this.remove(parent, key, index);
}
this._replacement = _replacement;
this._remove = _remove;
}
return currentNode;
};
if (!isNode(input)) return null;
if (scopeTracker) scopeTracker.onWalkStart(input);
if (scopeTracker && !enter && !leave) {
const _walkScopesOnly = (input) => {
scopeTracker.processNodeEnter(input);
for (const k in input) {
if (k === "type" || k === "start" || k === "end") continue;
const node = input[k];
if (!node || typeof node !== "object") continue;
if (Array.isArray(node)) for (let i = 0; i < node.length; i++) {
const child = node[i];
if (isNode(child)) _walkScopesOnly(child);
}
else if (typeof node.type === "string") _walkScopesOnly(node);
}
scopeTracker.processNodeLeave(input);
};
_walkScopesOnly(input);
return input;
}
return _walk(input, parent ?? null, key ?? null, index ?? null, false);
}
};
//#endregion
//#region src/walk.ts
let cachedParseSync;
function resolveParseSync() {
if (cachedParseSync) return cachedParseSync;
const require = createRequire(import.meta.url);
for (const id of ["oxc-parser", "rolldown/utils"]) try {
const mod = require(id);
if (typeof mod.parseSync === "function") {
cachedParseSync = mod.parseSync;
return cachedParseSync;
}
} catch {}
throw new Error("oxc-walker: could not resolve a `parseSync` implementation. Install `oxc-parser` or `rolldown` (and use `rolldown/utils`), or pass a `parseSync` function via the `parseAndWalk` options.");
}
/**
* Walk the AST with the given options.
* @param input The AST to walk.
* @param options The options to be used when walking the AST. Here you can specify the callbacks for entering and leaving nodes, as well as other options.
*/
function walk(input, options) {
return new WalkerSync({
enter: options.enter,
leave: options.leave
}, { scopeTracker: options.scopeTracker }).traverse(input);
}
const LANG_RE = /\.(?:c|m)?(?<lang>jsx?|tsx?)$/;
function parseAndWalk(code, sourceFilename, arg3) {
const lang = sourceFilename?.match(LANG_RE)?.groups?.lang;
const { parseOptions: _parseOptions = {}, parseSync: _parseSync, ...options } = typeof arg3 === "function" ? { enter: arg3 } : arg3;
const parseOptions = {
sourceType: "module",
lang,
..._parseOptions
};
const ast = (_parseSync ?? resolveParseSync())(sourceFilename, code, parseOptions);
walk(ast.program, options);
return ast;
}
//#endregion
//#region src/scope-tracker.ts
/**
* The node types handled by {@link ScopeTracker.processNodeEnter}.
* Must list every `case` of its switch statement; a missing entry silently disables
* scope tracking for that node type (see the drift test in `test/scope-tracker.test.ts`).
*/
const SCOPE_ENTER_TYPES = /* @__PURE__ */ new Set([
"Program",
"BlockStatement",
"StaticBlock",
"TSModuleBlock",
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"VariableDeclaration",
"ClassDeclaration",
"ClassExpression",
"ImportDeclaration",
"TSEnumDeclaration",
"TSModuleDeclaration",
"TSImportEqualsDeclaration",
"TSDeclareFunction",
"TSInterfaceDeclaration",
"TSTypeAliasDeclaration",
"TSTypeParameter",
"TSMappedType",
"TSEnumBody",
"TSMethodSignature",
"TSCallSignatureDeclaration",
"TSConstructSignatureDeclaration",
"TSFunctionType",
"TSConstructorType",
"TSConditionalType",
"CatchClause",
"ForStatement",
"ForOfStatement",
"ForInStatement",
"SwitchStatement"
]);
/**
* Tracks variable scopes and identifier declarations within a JavaScript AST.
*
* Maintains a stack of scopes, each represented as a map from identifier names to their declaration nodes,
* enabling efficient lookup of the declaration.
*
* The ScopeTracker is designed to integrate with the `walk` function,
* it automatically manages scope creation and identifier tracking,
* so only query and inspection methods are exposed for external use.
*
* ### Scope tracking
* A new scope is created when entering blocks, function parameters, loop variables, etc.
* Note that this representation may split a single JavaScript lexical scope into multiple internal scopes,
* meaning it doesn't mirror JavaScript’s scoping 1:1.
*
* Scopes are represented using a string-based index like `"0-1-2"`, which tracks depth and ancestry.
*
* #### Root scope
* The root scope is represented by an empty string `""`.
*
* #### Scope key format
* Scope keys are hierarchical strings that uniquely identify each scope and its position in the tree.
* They are constructed using a depth-based indexing scheme, where:
*
* - the root scope is represented by an empty string `""`.
* - the first child scope is `"0"`.
* - a parallel sibling of `"0"` becomes `"1"`, `"2"`, etc.
* - a nested scope under `"0"` is `"0-0"`, then its sibling is `"0-1"`, and so on.
*
* Each segment in the key corresponds to the zero-based index of the scope at that depth level in
* the order of AST traversal.
*
* ### Additional features
* - supports freezing the tracker to allow for second passes through the AST without modifying the scope data
* (useful for doing a pre-pass to collect all identifiers before walking).
*
* @example
* ```ts
* const scopeTracker = new ScopeTracker()
* walk(code, {
* scopeTracker,
* enter(node) {
* // ...
* },
* })
* ```
*
* @see parseAndWalk
* @see walk
*/
var ScopeTracker = class {
scopeIndexStack = [];
/**
* The scope keys of the ancestors of the current scope, one entry per active scope.
* `pushScope` pushes the current `scopeIndexKey` before deriving the new one,
* and `popScope` restores it from here, so the stack always mirrors the path
* from the root scope down to the parent of the current scope.
*/
scopeKeyStack = [];
/**
* The nodes that created the currently active scopes, one entry per scope.
* A node that creates multiple scopes (e.g. `FunctionExpression`) appears once per scope.
* This lets `processNodeLeave` detect scope ends with a pointer comparison
* instead of checking the node type.
*/
scopeOwnerStack = [];
scopeIndexKey = "";
scopes = /* @__PURE__ */ new Map();
typeScopes = /* @__PURE__ */ new Map();
/**
* The implicit `arguments` keyed by the function that introduces them,
* so that repeated queries return the same instance.
*/
implicitArguments = /* @__PURE__ */ new WeakMap();
options;
isFrozen = false;
constructor(options = {}) {
this.options = options;
}
onWalkStart = (root) => {
if (this.scopeIndexStack.length === 0 && root.type !== "Program") this.scopeIndexStack.push(0);
};
pushScope(owner) {
const depthIndex = this.scopeIndexStack[this.scopeIndexStack.length - 1];
this.scopeKeyStack.push(this.scopeIndexKey);
this.scopeOwnerStack.push(owner);
this.scopeIndexStack.push(0);
if (depthIndex !== void 0) this.scopeIndexKey = this.scopeIndexKey ? `${this.scopeIndexKey}-${depthIndex}` : `${depthIndex}`;
}
popScope() {
this.scopeOwnerStack.pop();
this.scopeIndexStack.pop();
if (this.scopeIndexStack[this.scopeIndexStack.length - 1] !== void 0) this.scopeIndexStack[this.scopeIndexStack.length - 1]++;
if (!this.options.preserveExitedScopes) {
this.scopes.delete(this.scopeIndexKey);
this.typeScopes.delete(this.scopeIndexKey);
}
this.scopeIndexKey = this.scopeKeyStack.pop() ?? "";
}
/**
* Get the key of the active scope at the given index of the scope stacks.
*/
getScopeKeyAt(index) {
return index === this.scopeKeyStack.length - 1 ? this.scopeIndexKey : this.scopeKeyStack[index + 1];
}
/**
* Get the key of the closest function-like scope (function bodies, static blocks,
* module blocks and the root scope), which is where `var` declarations are hoisted to.
*/
getVarScopeKey() {
const owners = this.scopeOwnerStack;
for (let i = owners.length - 1; i >= 0; i--) switch (owners[i].type) {
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
case "TSDeclareFunction":
case "Program":
case "StaticBlock":
case "TSModuleBlock": return this.getScopeKeyAt(i);
}
return "";
}
declareInNamespace(scopes, name, node) {
let scope = scopes.get(node.scope);
if (!scope) {
scope = /* @__PURE__ */ new Map();
scopes.set(node.scope, scope);
}
scope.set(name, node);
}
declareIdentifier(name, data, namespaces = { value: true }) {
if (this.isFrozen) return;
if (namespaces.value) this.declareInNamespace(this.scopes, name, data);
if (namespaces.type) this.declareInNamespace(this.typeScopes, name, data);
}
declareFunctionParameter(param, fn) {
if (this.isFrozen) return;
const identifiers = getPatternIdentifiers(param);
for (const identifier of identifiers) this.declareIdentifier(identifier.name, new ScopeTrackerFunctionParam(identifier, this.scopeIndexKey, fn));
}
declarePattern(pattern, parent) {
if (this.isFrozen) return;
const scopeKey = parent.type === "VariableDeclaration" && parent.kind === "var" ? this.getVarScopeKey() : this.scopeIndexKey;
const identifiers = getPatternIdentifiers(pattern);
for (const identifier of identifiers) this.declareIdentifier(identifier.name, parent.type === "VariableDeclaration" ? new ScopeTrackerVariable(identifier, scopeKey, parent) : parent.type === "CatchClause" ? new ScopeTrackerCatchParam(identifier, scopeKey, parent) : new ScopeTrackerFunctionParam(identifier, scopeKey, parent));
}
processNodeEnter = (node) => {
if (!SCOPE_ENTER_TYPES.has(node.type)) return;
switch (node.type) {
case "Program":
case "BlockStatement":
case "StaticBlock":
case "SwitchStatement":
case "TSModuleBlock":
case "TSMethodSignature":
case "TSCallSignatureDeclaration":
case "TSConstructSignatureDeclaration":
case "TSFunctionType":
case "TSConstructorType":
case "TSConditionalType":
this.pushScope(node);
break;
case "FunctionDeclaration":
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerFunction(node, this.scopeIndexKey));
this.pushScope(node);
for (const param of node.params) this.declareFunctionParameter(param, node);
break;
case "FunctionExpression":
this.pushScope(node);
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerFunction(node, this.scopeIndexKey));
this.pushScope(node);
for (const param of node.params) this.declareFunctionParameter(param, node);
break;
case "ArrowFunctionExpression":
this.pushScope(node);
for (const param of node.params) this.declareFunctionParameter(param, node);
break;
case "VariableDeclaration":
for (const decl of node.declarations) this.declarePattern(decl.id, node);
break;
case "ClassDeclaration":
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerIdentifier(node.id, this.scopeIndexKey), {
value: true,
type: true
});
if (node.typeParameters) this.pushScope(node);
break;
case "ClassExpression":
this.pushScope(node);
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerIdentifier(node.id, this.scopeIndexKey), {
value: true,
type: true
});
break;
case "ImportDeclaration":
for (const specifier of node.specifiers) {
const isTypeOnly = node.importKind === "type" || specifier.type === "ImportSpecifier" && specifier.importKind === "type";
this.declareIdentifier(specifier.local.name, new ScopeTrackerImport(specifier, this.scopeIndexKey, node), {
value: !isTypeOnly,
type: true
});
}
break;
case "TSEnumDeclaration":
case "TSModuleDeclaration":
case "TSImportEqualsDeclaration":
if (node.type === "TSModuleDeclaration" && node.kind === "global") break;
if (node.id?.type === "Identifier" && node.id.name) this.declareIdentifier(node.id.name, new ScopeTrackerIdentifier(node.id, this.scopeIndexKey), {
value: true,
type: true
});
break;
case "TSDeclareFunction":
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerIdentifier(node.id, this.scopeIndexKey));
this.pushScope(node);
break;
case "TSInterfaceDeclaration":
case "TSTypeAliasDeclaration":
if (node.id?.name) this.declareIdentifier(node.id.name, new ScopeTrackerIdentifier(node.id, this.scopeIndexKey), { type: true });
this.pushScope(node);
break;
case "TSTypeParameter":
if (node.name?.name) this.declareIdentifier(node.name.name, new ScopeTrackerIdentifier(node.name, this.scopeIndexKey), { type: true });
break;
case "TSMappedType":
this.pushScope(node);
this.declareIdentifier(node.key.name, new ScopeTrackerIdentifier(node.key, this.scopeIndexKey), { type: true });
break;
case "TSEnumBody":
this.pushScope(node);
for (const member of node.members) if (member.id.type === "Identifier") this.declareIdentifier(member.id.name, new ScopeTrackerIdentifier(member.id, this.scopeIndexKey));
break;
case "CatchClause":
this.pushScope(node);
if (node.param) this.declarePattern(node.param, node);
break;
case "ForStatement":
case "ForOfStatement":
case "ForInStatement":
this.pushScope(node);
if (node.type === "ForStatement" && node.init?.type === "VariableDeclaration") for (const decl of node.init.declarations) this.declarePattern(decl.id, node.init);
else if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && node.left.type === "VariableDeclaration") for (const decl of node.left.declarations) this.declarePattern(decl.id, node.left);
break;
}
};
processNodeLeave = (node) => {
const owners = this.scopeOwnerStack;
while (owners.length > 0 && owners[owners.length - 1] === node) this.popScope();
};
getDeclarationIn(scopes, name) {
let key = this.scopeIndexKey;
while (true) {
const node = scopes.get(key)?.get(name);
if (node) return node;
if (!key) return null;
const separatorIndex = key.lastIndexOf("-");
key = separatorIndex === -1 ? "" : key.slice(0, separatorIndex);
}
}
/**
* Check if an identifier is declared in the current scope or any parent scope.
* @param name the identifier name to check
* @param options which namespace to check — the value namespace (default), the type namespace, or both
*/
isDeclared(name, options) {
return this.getDeclaration(name, options) !== null;
}
/**
* Get the declaration node for a given identifier name.
* @param name the identifier name to look up
* @param options which namespace to check — the value namespace (default), the type namespace, or both
*/
getDeclaration(name, options) {
const mode = options?.mode ?? "value";
const declaration = (mode !== "type" ? this.getDeclarationIn(this.scopes, name) : null) ?? (mode !== "value" ? this.getDeclarationIn(this.typeScopes, name) : null);
if (name === "arguments" && mode !== "type") {
const owners = this.scopeOwnerStack;
for (let i = owners.length - 1; i >= 0; i--) {
const owner = owners[i];
if (owner.type === "FunctionDeclaration" || owner.type === "FunctionExpression") {
const fnScope = this.getScopeKeyAt(i);
if (!declaration || isChildScope(fnScope, declaration.scope)) {
let implicit = this.implicitArguments.get(owner);
if (!implicit) {
implicit = new ScopeTrackerFunctionArguments(owner, fnScope);
this.implicitArguments.set(owner, implicit);
}
return implicit;
}
break;
}
}
}
return declaration;
}
/**
* Get the current scope key.
*/
getCurrentScope() {
return this.scopeIndexKey;
}
/**
* Check if the current scope is a child of a specific scope.
* @example
* ```ts
* // current scope is 0-1
* isCurrentScopeUnder('0') // true
* isCurrentScopeUnder('0-1') // false
* ```
*
* @param scope the parent scope key to check against
* @returns `true` if the current scope is a child of the specified scope, `false` otherwise (also when they are the same)
*/
isCurrentScopeUnder(scope) {
return isChildScope(this.scopeIndexKey, scope);
}
/**
* Freezes the ScopeTracker, preventing further modifications to its state.
* It also resets the scope index stack to its initial state so that the tracker can be reused.
*
* This is useful for second passes through the AST.
*/
freeze() {
this.isFrozen = true;
this.scopeIndexStack = [];
this.scopeKeyStack = [];
this.scopeOwnerStack = [];
this.scopeIndexKey = "";
}
};
function getPatternIdentifiers(pattern) {
const identifiers = [];
function collectIdentifiers(pattern) {
switch (pattern.type) {
case "Identifier":
identifiers.push(pattern);
break;
case "AssignmentPattern":
collectIdentifiers(pattern.left);
break;
case "RestElement":
collectIdentifiers(pattern.argument);
break;
case "TSParameterProperty":
collectIdentifiers(pattern.parameter);
break;
case "ArrayPattern":
for (const element of pattern.elements) if (element) collectIdentifiers(element.type === "RestElement" ? element.argument : element);
break;
case "ObjectPattern":
for (const property of pattern.properties) collectIdentifiers(property.type === "RestElement" ? property.argument : property.value);
break;
}
}
collectIdentifiers(pattern);
return identifiers;
}
/**
* An allocation-free alternative to `getPatternIdentifiers(pattern).includes(node)`
*/
function isIdentifierInPattern(pattern, node) {
switch (pattern.type) {
case "Identifier": return pattern === node;
case "AssignmentPattern": return isIdentifierInPattern(pattern.left, node);
case "RestElement": return isIdentifierInPattern(pattern.argument, node);
case "TSParameterProperty": return isIdentifierInPattern(pattern.parameter, node);
case "ArrayPattern":
for (const element of pattern.elements) if (element && isIdentifierInPattern(element.type === "RestElement" ? element.argument : element, node)) return true;
return false;
case "ObjectPattern":
for (const property of pattern.properties) if (isIdentifierInPattern(property.type === "RestElement" ? property.argument : property.value, node)) return true;
return false;
}
return false;
}
/**
* Check if an identifier is in a binding position, where it declares a new variable.
*
* Note that identifiers nested in destructuring patterns (`const { a, b = c } = obj`)
* cannot be resolved from the direct parent alone; this function returns `false` for them
* and {@link isReferenceIdentifier} reports them as references.
* Pair these functions with a {@link ScopeTracker} to resolve such identifiers correctly.
*/
function isOnlyBindingIdentifier(node, parent) {
if (!parent || node.type !== "Identifier") return false;
switch (parent.type) {
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
case "TSDeclareFunction":
case "TSEmptyBodyFunctionExpression":
if (parent.type !== "ArrowFunctionExpression" && parent.id === node) return true;
for (const param of parent.params) if (isIdentifierInPattern(param, node)) return true;
return false;
case "ClassDeclaration":
case "ClassExpression": return parent.id === node;
case "VariableDeclarator": return isIdentifierInPattern(parent.id, node);
case "CatchClause":
if (!parent.param) return false;
return isIdentifierInPattern(parent.param, node);
case "ImportSpecifier": return parent.local === node;
case "ImportDefaultSpecifier":
case "ImportNamespaceSpecifier": return true;
case "TSEnumDeclaration":
case "TSModuleDeclaration": return parent.id === node;
case "TSImportEqualsDeclaration": return parent.id === node;
case "TSParameterProperty": return isIdentifierInPattern(parent.parameter, node);
}
return false;
}
/**
* @deprecated
* Despite its name, this function does not check for binding positions only.
* It will adopt the strict binding-position behavior of {@link isOnlyBindingIdentifier}
* in the next major version. Migrate based on what you need:
* - `!isReferenceIdentifier(node, parent)` if you were filtering out variable references
* (the common case)
* - `isOnlyBindingIdentifier(node, parent)` if you need actual binding positions
*/
function isBindingIdentifier(node, parent) {
if (!parent || node.type !== "Identifier") return false;
if (isOnlyBindingIdentifier(node, parent)) return true;
switch (parent.type) {
case "MethodDefinition":
case "PropertyDefinition": return parent.key === node;
case "Property": return parent.key === node && parent.value !== node;
case "MemberExpression": return parent.property === node;
}
return false;
}
/**
* Check if an identifier is a reference.
*
* Note that this function returns `true` for the local name of a plain export
* (`export { foo }`), where it is a genuine reference to a local variable,
* but also for the local name of a re-export (`export { foo } from '...'`),
* where it refers to the other module's exports instead.
*
* The two are indistinguishable from the specifier alone, as the `source` lives
* on the parent `ExportNamedDeclaration`.
* Skip re-export declarations during the walk to avoid the false positives.
*
* Identifiers nested in destructuring patterns (`const { a, b = c } = obj`) are also
* indistinguishable from references based on the direct parent alone and are reported
* as references. Pair this function with a {@link ScopeTracker} to resolve them correctly.
*/
function isReferenceIdentifier(node, parent, options) {
if (!parent) return false;
const mode = options?.mode ?? "value";
if (node.type === "JSXIdentifier") {
if (mode === "type") return false;
switch (parent.type) {
case "JSXOpeningElement":
case "JSXClosingElement": return parent.name === node && !/^[a-z]/.test(node.name);
case "JSXMemberExpression": return parent.object === node;
case "JSXAttribute":
case "JSXNamespacedName": return false;
}
return false;
}
if (node.type !== "Identifier" || isOnlyBindingIdentifier(node, parent)) return false;
switch (parent.type) {
case "MemberExpression": return mode !== "type" && (parent.object === node || parent.computed);
case "Property": return mode !== "type" && (parent.value === node || parent.computed);
case "MethodDefinition":
case "PropertyDefinition":
case "AccessorProperty":
case "TSAbstractMethodDefinition":
case "TSAbstractPropertyDefinition":
case "TSAbstractAccessorProperty": return mode !== "type" && (parent.value === node || parent.computed);
case "ExportSpecifier": return mode !== "type" && parent.local === node;
case "ExportAllDeclaration": return false;
case "ImportSpecifier": return false;
case "LabeledStatement":
case "BreakStatement":
case "ContinueStatement": return false;
case "MetaProperty": return false;
case "ImportAttribute": return false;
case "TSEnumMember": return mode !== "type" && parent.id !== node;
case "TSMappedType": return false;
case "TSTypeReference": return mode !== "value";
case "TSQualifiedName": return mode !== "value" && parent.left === node;
case "TSClassImplements":
case "TSInterfaceHeritage": return mode !== "value" && parent.expression === node;
case "TSTypeParameter":
case "TSInterfaceDeclaration":
case "TSTypeAliasDeclaration":
case "TSPropertySignature":
case "TSMethodSignature":
case "TSIndexSignature": return false;
case "TSFunctionType":
case "TSConstructorType":
case "TSCallSignatureDeclaration":
case "TSConstructSignatureDeclaration": return false;
}
return mode !== "type";
}
function getUndeclaredIdentifiersInFunction(node) {
const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
const undeclaredIdentifiers = /* @__PURE__ */ new Set();
function isIdentifierUndeclared(node, parent) {
return isReferenceIdentifier(node, parent) && !scopeTracker.isDeclared(node.name);
}
walk(node, { scopeTracker });
scopeTracker.freeze();
walk(node, {
scopeTracker,
enter(node, parent) {
if ((node.type === "Identifier" || node.type === "JSXIdentifier") && isIdentifierUndeclared(node, parent)) undeclaredIdentifiers.add(node.name);
}
});
return Array.from(undeclaredIdentifiers);
}
/**
* A function to check whether scope A is a child of scope B.
* @example
* ```ts
* isChildScope('0-1-2', '0-1') // true
* isChildScope('0-1', '0-1') // false
* ```
*
* @param a the child scope
* @param b the parent scope
* @returns true if scope A is a child of scope B, false otherwise (also when they are the same)
*/
function isChildScope(a, b) {
return a.length > b.length && a.startsWith(b) && (b === "" || a[b.length] === "-");
}
var BaseNode = class {
scope;
node;
constructor(node, scope) {
this.node = node;
this.scope = scope;
}
/**
* Check if the node is defined under a specific scope.
* @param scope
*/
isUnderScope(scope) {
return isChildScope(this.scope, scope);
}
};
var ScopeTrackerIdentifier = class extends BaseNode {
type = "Identifier";
get start() {
return this.node.start;
}
get end() {
return this.node.end;
}
};
var ScopeTrackerFunctionParam = class extends BaseNode {
type = "FunctionParam";
fnNode;
constructor(node, scope, fnNode) {
super(node, scope);
this.fnNode = fnNode;
}
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.start` instead for now.
*/
get start() {
return this.fnNode.start;
}
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.end` instead for now.
*/
get end() {
return this.fnNode.end;
}
};
var ScopeTrackerFunction = class extends BaseNode {
type = "Function";
get start() {
return this.node.start;
}
get end() {
return this.node.end;
}
};
var ScopeTrackerFunctionArguments = class extends BaseNode {
type = "FunctionArguments";
get start() {
return this.node.start;
}
get end() {
return this.node.end;
}
};
var ScopeTrackerVariable = class extends BaseNode {
type = "Variable";
variableNode;
constructor(node, scope, variableNode) {
super(node, scope);
this.variableNode = variableNode;
}
get start() {
return this.variableNode.start;
}
get end() {
return this.variableNode.end;
}
};
var ScopeTrackerImport = class extends BaseNode {
type = "Import";
importNode;
constructor(node, scope, importNode) {
super(node, scope);
this.importNode = importNode;
}
get start() {
return this.importNode.start;
}
get end() {
return this.importNode.end;
}
};
var ScopeTrackerCatchParam = class extends BaseNode {
type = "CatchParam";
catchNode;
constructor(node, scope, catchNode) {
super(node, scope);
this.catchNode = catchNode;
}
get start() {
return this.catchNode.start;
}
get end() {
return this.catchNode.end;
}
};
//#endregion
export { ScopeTracker, ScopeTrackerCatchParam, ScopeTrackerFunction, ScopeTrackerFunctionArguments, ScopeTrackerFunctionParam, ScopeTrackerIdentifier, ScopeTrackerImport, ScopeTrackerVariable, getUndeclaredIdentifiersInFunction, isBindingIdentifier, isOnlyBindingIdentifier, isReferenceIdentifier, parseAndWalk, walk };