Website Structure

This commit is contained in:
supalerk-ar66 2026-01-13 10:46:40 +07:00
parent 62812f2090
commit 71f0676a62
22365 changed files with 4265753 additions and 791 deletions

View file

@ -0,0 +1,323 @@
import { Node, Program, IdentifierName, IdentifierReference, BindingIdentifier, LabelIdentifier, TSIndexSignatureName, ParseResult, ParserOptions, Function, ArrowFunctionExpression, VariableDeclaration, ImportDeclarationSpecifier, ImportDeclaration, CatchClause } from 'oxc-parser';
interface WalkerCallbackContext {
/**
* The key of the current node within its parent node object, if applicable.
*
* For instance, when processing a `VariableDeclarator` node, this would be the `declarations` key of the parent `VariableDeclaration` node.
* @example
* {
* type: 'VariableDeclaration',
* declarations: [[Object]],
* // ...
* },
* { // <-- when processing this, the key would be 'declarations'
* type: 'VariableDeclarator',
* // ...
* },
*/
key: string | number | symbol | null | undefined;
/**
* The zero-based index of the current node within its parent's children array, if applicable.
* For instance, when processing a `VariableDeclarator` node,
* this would be the index of the current `VariableDeclarator` node within the `declarations` array.
*
* This is `null` when the node is not part of an array.
*
* @example
* {
* type: 'VariableDeclaration',
* declarations: [[Object]],
* // ...
* },
* { // <-- when processing this, the index would be 0
* type: 'VariableDeclarator',
* // ...
* },
*/
index: number | null;
/**
* The full Abstract Syntax Tree (AST) that is being walked, starting from the root node.
*/
ast: Program | Node;
}
interface WalkerThisContextLeave {
/**
* Remove the current node from the AST.
* @remarks
* - The `ScopeTracker` currently does not support node removal
* @see ScopeTracker
*/
remove: () => void;
/**
* Replace the current node with another node.
* After replacement, the walker will continue with the next sibling of the replaced node.
*
* In case the current node was removed in the `enter` phase, this will put the new node in
* the place of the removed node - essentially undoing the removal.
* @remarks
* - The `ScopeTracker` currently does not support node replacement
* @see ScopeTracker
*/
replace: (node: Node) => void;
}
interface WalkerThisContextEnter extends WalkerThisContextLeave {
/**
* Skip traversing the child nodes of the current node.
*/
skip: () => void;
/**
* Remove the current node and all of its children from the AST.
* @remarks
* - The `ScopeTracker` currently does not support node removal
* @see ScopeTracker
*/
remove: () => void;
/**
* Replace the current node with another node.
* After replacement, the walker will continue to traverse the children of the new node.
*
* If you want to replace the current node and skip traversing its children, call `this.skip()` after calling `this.replace(newNode)`.
* @remarks
* - The `ScopeTracker` currently does not support node replacement
* @see this.skip
* @see ScopeTracker
*/
replace: (node: Node) => void;
}
type WalkerCallback<T extends WalkerThisContextLeave> = (this: T, node: Node, parent: Node | null, ctx: WalkerCallbackContext) => void;
type WalkerEnter = WalkerCallback<WalkerThisContextEnter>;
type WalkerLeave = WalkerCallback<WalkerThisContextLeave>;
interface _WalkOptions {
/**
* The instance of `ScopeTracker` to use for tracking declarations and references.
* @see ScopeTracker
* @default undefined
*/
scopeTracker: ScopeTracker;
}
interface WalkOptions extends Partial<_WalkOptions> {
/**
* The function to be called when entering a node.
*/
enter: WalkerEnter;
/**
* The function to be called when leaving a node.
*/
leave: WalkerLeave;
}
type Identifier = IdentifierName | IdentifierReference | BindingIdentifier | LabelIdentifier | TSIndexSignatureName;
/**
* 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.
*/
declare function walk(input: Program | Node, options: Partial<WalkOptions>): Node | null;
interface ParseAndWalkOptions extends WalkOptions {
/**
* The options for `oxc-parser` to use when parsing the code.
*/
parseOptions: ParserOptions;
}
/**
* Parse the code and walk the AST with the given callback, which is called when entering a node.
* @param code The string with the code to parse and walk. This can be JavaScript, TypeScript, jsx, or tsx.
* @param sourceFilename The filename of the source code. This is used to determine the language of the code, unless
* it is specified in the parse options.
* @param callback The callback to be called when entering a node.
*/
declare function parseAndWalk(code: string, sourceFilename: string, callback: WalkerEnter): ParseResult;
/**
* Parse the code and walk the AST with the given callback(s).
* @param code The string with the code to parse and walk. This can be JavaScript, TypeScript, jsx, or tsx.
* @param sourceFilename The filename of the source code. This is used to determine the language of the code, unless
* it is specified in the parse options.
* @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.
*/
declare function parseAndWalk(code: string, sourceFilename: string, options: Partial<ParseAndWalkOptions>): ParseResult;
interface ScopeTrackerProtected {
processNodeEnter: (node: Node) => void;
processNodeLeave: (node: Node) => void;
}
/**
* 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 JavaScripts 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
*/
declare class ScopeTracker {
protected scopeIndexStack: number[];
protected scopeIndexKey: string;
protected scopes: Map<string, Map<string, ScopeTrackerNode>>;
protected options: Partial<ScopeTrackerOptions>;
protected isFrozen: boolean;
constructor(options?: ScopeTrackerOptions);
protected updateScopeIndexKey(): void;
protected pushScope(): void;
protected popScope(): void;
protected declareIdentifier(name: string, data: ScopeTrackerNode): void;
protected declareFunctionParameter(param: Node, fn: Function | ArrowFunctionExpression): void;
protected declarePattern(pattern: Node, parent: VariableDeclaration | ArrowFunctionExpression | CatchClause | Function): void;
protected processNodeEnter: ScopeTrackerProtected["processNodeEnter"];
protected processNodeLeave: ScopeTrackerProtected["processNodeLeave"];
/**
* Check if an identifier is declared in the current scope or any parent scope.
* @param name the identifier name to check
*/
isDeclared(name: string): boolean;
/**
* Get the declaration node for a given identifier name.
* @param name the identifier name to look up
*/
getDeclaration(name: string): ScopeTrackerNode | null;
/**
* Get the current scope key.
*/
getCurrentScope(): string;
/**
* 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: string): boolean;
/**
* 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(): void;
}
declare function isBindingIdentifier(node: Node, parent: Node | null): boolean;
declare function getUndeclaredIdentifiersInFunction(node: Function | ArrowFunctionExpression): string[];
declare abstract class BaseNode<T extends Node = Node> {
abstract type: string;
readonly scope: string;
node: T;
constructor(node: T, scope: string);
/**
* The starting position of the entire node relevant for code transformation.
* For instance, for a reference to a variable (ScopeTrackerVariable -> Identifier), this would refer to the start of the VariableDeclaration.
*/
abstract get start(): number;
/**
* The ending position of the entire node relevant for code transformation.
* For instance, for a reference to a variable (ScopeTrackerVariable -> Identifier), this would refer to the end of the VariableDeclaration.
*/
abstract get end(): number;
/**
* Check if the node is defined under a specific scope.
* @param scope
*/
isUnderScope(scope: string): boolean;
}
declare class ScopeTrackerIdentifier extends BaseNode<Identifier> {
type: "Identifier";
get start(): number;
get end(): number;
}
declare class ScopeTrackerFunctionParam extends BaseNode {
type: "FunctionParam";
fnNode: Function | ArrowFunctionExpression;
constructor(node: Node, scope: string, fnNode: Function | ArrowFunctionExpression);
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.start` instead for now.
*/
get start(): number;
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.end` instead for now.
*/
get end(): number;
}
declare class ScopeTrackerFunction extends BaseNode<Function | ArrowFunctionExpression> {
type: "Function";
get start(): number;
get end(): number;
}
declare class ScopeTrackerVariable extends BaseNode<Identifier> {
type: "Variable";
variableNode: VariableDeclaration;
constructor(node: Identifier, scope: string, variableNode: VariableDeclaration);
get start(): number;
get end(): number;
}
declare class ScopeTrackerImport extends BaseNode<ImportDeclarationSpecifier> {
type: "Import";
importNode: ImportDeclaration;
constructor(node: ImportDeclarationSpecifier, scope: string, importNode: ImportDeclaration);
get start(): number;
get end(): number;
}
declare class ScopeTrackerCatchParam extends BaseNode {
type: "CatchParam";
catchNode: CatchClause;
constructor(node: Node, scope: string, catchNode: CatchClause);
get start(): number;
get end(): number;
}
type ScopeTrackerNode = ScopeTrackerFunctionParam | ScopeTrackerFunction | ScopeTrackerVariable | ScopeTrackerIdentifier | ScopeTrackerImport | ScopeTrackerCatchParam;
interface ScopeTrackerOptions {
/**
* If true, the scope tracker will preserve exited scopes in memory.
* This is necessary when you want to do a pre-pass to collect all identifiers before walking, for example.
* @default false
*/
preserveExitedScopes?: boolean;
}
export { ScopeTracker, getUndeclaredIdentifiersInFunction, isBindingIdentifier, parseAndWalk, walk };
export type { Identifier, ScopeTrackerNode };

View file

@ -0,0 +1,323 @@
import { Node, Program, IdentifierName, IdentifierReference, BindingIdentifier, LabelIdentifier, TSIndexSignatureName, ParseResult, ParserOptions, Function, ArrowFunctionExpression, VariableDeclaration, ImportDeclarationSpecifier, ImportDeclaration, CatchClause } from 'oxc-parser';
interface WalkerCallbackContext {
/**
* The key of the current node within its parent node object, if applicable.
*
* For instance, when processing a `VariableDeclarator` node, this would be the `declarations` key of the parent `VariableDeclaration` node.
* @example
* {
* type: 'VariableDeclaration',
* declarations: [[Object]],
* // ...
* },
* { // <-- when processing this, the key would be 'declarations'
* type: 'VariableDeclarator',
* // ...
* },
*/
key: string | number | symbol | null | undefined;
/**
* The zero-based index of the current node within its parent's children array, if applicable.
* For instance, when processing a `VariableDeclarator` node,
* this would be the index of the current `VariableDeclarator` node within the `declarations` array.
*
* This is `null` when the node is not part of an array.
*
* @example
* {
* type: 'VariableDeclaration',
* declarations: [[Object]],
* // ...
* },
* { // <-- when processing this, the index would be 0
* type: 'VariableDeclarator',
* // ...
* },
*/
index: number | null;
/**
* The full Abstract Syntax Tree (AST) that is being walked, starting from the root node.
*/
ast: Program | Node;
}
interface WalkerThisContextLeave {
/**
* Remove the current node from the AST.
* @remarks
* - The `ScopeTracker` currently does not support node removal
* @see ScopeTracker
*/
remove: () => void;
/**
* Replace the current node with another node.
* After replacement, the walker will continue with the next sibling of the replaced node.
*
* In case the current node was removed in the `enter` phase, this will put the new node in
* the place of the removed node - essentially undoing the removal.
* @remarks
* - The `ScopeTracker` currently does not support node replacement
* @see ScopeTracker
*/
replace: (node: Node) => void;
}
interface WalkerThisContextEnter extends WalkerThisContextLeave {
/**
* Skip traversing the child nodes of the current node.
*/
skip: () => void;
/**
* Remove the current node and all of its children from the AST.
* @remarks
* - The `ScopeTracker` currently does not support node removal
* @see ScopeTracker
*/
remove: () => void;
/**
* Replace the current node with another node.
* After replacement, the walker will continue to traverse the children of the new node.
*
* If you want to replace the current node and skip traversing its children, call `this.skip()` after calling `this.replace(newNode)`.
* @remarks
* - The `ScopeTracker` currently does not support node replacement
* @see this.skip
* @see ScopeTracker
*/
replace: (node: Node) => void;
}
type WalkerCallback<T extends WalkerThisContextLeave> = (this: T, node: Node, parent: Node | null, ctx: WalkerCallbackContext) => void;
type WalkerEnter = WalkerCallback<WalkerThisContextEnter>;
type WalkerLeave = WalkerCallback<WalkerThisContextLeave>;
interface _WalkOptions {
/**
* The instance of `ScopeTracker` to use for tracking declarations and references.
* @see ScopeTracker
* @default undefined
*/
scopeTracker: ScopeTracker;
}
interface WalkOptions extends Partial<_WalkOptions> {
/**
* The function to be called when entering a node.
*/
enter: WalkerEnter;
/**
* The function to be called when leaving a node.
*/
leave: WalkerLeave;
}
type Identifier = IdentifierName | IdentifierReference | BindingIdentifier | LabelIdentifier | TSIndexSignatureName;
/**
* 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.
*/
declare function walk(input: Program | Node, options: Partial<WalkOptions>): Node | null;
interface ParseAndWalkOptions extends WalkOptions {
/**
* The options for `oxc-parser` to use when parsing the code.
*/
parseOptions: ParserOptions;
}
/**
* Parse the code and walk the AST with the given callback, which is called when entering a node.
* @param code The string with the code to parse and walk. This can be JavaScript, TypeScript, jsx, or tsx.
* @param sourceFilename The filename of the source code. This is used to determine the language of the code, unless
* it is specified in the parse options.
* @param callback The callback to be called when entering a node.
*/
declare function parseAndWalk(code: string, sourceFilename: string, callback: WalkerEnter): ParseResult;
/**
* Parse the code and walk the AST with the given callback(s).
* @param code The string with the code to parse and walk. This can be JavaScript, TypeScript, jsx, or tsx.
* @param sourceFilename The filename of the source code. This is used to determine the language of the code, unless
* it is specified in the parse options.
* @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.
*/
declare function parseAndWalk(code: string, sourceFilename: string, options: Partial<ParseAndWalkOptions>): ParseResult;
interface ScopeTrackerProtected {
processNodeEnter: (node: Node) => void;
processNodeLeave: (node: Node) => void;
}
/**
* 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 JavaScripts 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
*/
declare class ScopeTracker {
protected scopeIndexStack: number[];
protected scopeIndexKey: string;
protected scopes: Map<string, Map<string, ScopeTrackerNode>>;
protected options: Partial<ScopeTrackerOptions>;
protected isFrozen: boolean;
constructor(options?: ScopeTrackerOptions);
protected updateScopeIndexKey(): void;
protected pushScope(): void;
protected popScope(): void;
protected declareIdentifier(name: string, data: ScopeTrackerNode): void;
protected declareFunctionParameter(param: Node, fn: Function | ArrowFunctionExpression): void;
protected declarePattern(pattern: Node, parent: VariableDeclaration | ArrowFunctionExpression | CatchClause | Function): void;
protected processNodeEnter: ScopeTrackerProtected["processNodeEnter"];
protected processNodeLeave: ScopeTrackerProtected["processNodeLeave"];
/**
* Check if an identifier is declared in the current scope or any parent scope.
* @param name the identifier name to check
*/
isDeclared(name: string): boolean;
/**
* Get the declaration node for a given identifier name.
* @param name the identifier name to look up
*/
getDeclaration(name: string): ScopeTrackerNode | null;
/**
* Get the current scope key.
*/
getCurrentScope(): string;
/**
* 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: string): boolean;
/**
* 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(): void;
}
declare function isBindingIdentifier(node: Node, parent: Node | null): boolean;
declare function getUndeclaredIdentifiersInFunction(node: Function | ArrowFunctionExpression): string[];
declare abstract class BaseNode<T extends Node = Node> {
abstract type: string;
readonly scope: string;
node: T;
constructor(node: T, scope: string);
/**
* The starting position of the entire node relevant for code transformation.
* For instance, for a reference to a variable (ScopeTrackerVariable -> Identifier), this would refer to the start of the VariableDeclaration.
*/
abstract get start(): number;
/**
* The ending position of the entire node relevant for code transformation.
* For instance, for a reference to a variable (ScopeTrackerVariable -> Identifier), this would refer to the end of the VariableDeclaration.
*/
abstract get end(): number;
/**
* Check if the node is defined under a specific scope.
* @param scope
*/
isUnderScope(scope: string): boolean;
}
declare class ScopeTrackerIdentifier extends BaseNode<Identifier> {
type: "Identifier";
get start(): number;
get end(): number;
}
declare class ScopeTrackerFunctionParam extends BaseNode {
type: "FunctionParam";
fnNode: Function | ArrowFunctionExpression;
constructor(node: Node, scope: string, fnNode: Function | ArrowFunctionExpression);
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.start` instead for now.
*/
get start(): number;
/**
* @deprecated The representation of this position may change in the future. Use `.fnNode.end` instead for now.
*/
get end(): number;
}
declare class ScopeTrackerFunction extends BaseNode<Function | ArrowFunctionExpression> {
type: "Function";
get start(): number;
get end(): number;
}
declare class ScopeTrackerVariable extends BaseNode<Identifier> {
type: "Variable";
variableNode: VariableDeclaration;
constructor(node: Identifier, scope: string, variableNode: VariableDeclaration);
get start(): number;
get end(): number;
}
declare class ScopeTrackerImport extends BaseNode<ImportDeclarationSpecifier> {
type: "Import";
importNode: ImportDeclaration;
constructor(node: ImportDeclarationSpecifier, scope: string, importNode: ImportDeclaration);
get start(): number;
get end(): number;
}
declare class ScopeTrackerCatchParam extends BaseNode {
type: "CatchParam";
catchNode: CatchClause;
constructor(node: Node, scope: string, catchNode: CatchClause);
get start(): number;
get end(): number;
}
type ScopeTrackerNode = ScopeTrackerFunctionParam | ScopeTrackerFunction | ScopeTrackerVariable | ScopeTrackerIdentifier | ScopeTrackerImport | ScopeTrackerCatchParam;
interface ScopeTrackerOptions {
/**
* If true, the scope tracker will preserve exited scopes in memory.
* This is necessary when you want to do a pre-pass to collect all identifiers before walking, for example.
* @default false
*/
preserveExitedScopes?: boolean;
}
export { ScopeTracker, getUndeclaredIdentifiersInFunction, isBindingIdentifier, parseAndWalk, walk };
export type { Identifier, ScopeTrackerNode };

627
Frontend-Learner/node_modules/oxc-walker/dist/index.mjs generated vendored Normal file
View file

@ -0,0 +1,627 @@
import { createRegExp, exactly, anyOf } from 'magic-regexp/further-magic';
import { parseSync } from 'oxc-parser';
function isNode(v) {
return v !== null && typeof v === "object" && v.type != null && typeof v.type === "string";
}
class WalkerBase {
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];
}
}
}
class WalkerSync extends WalkerBase {
constructor(handler, options) {
super(handler, options);
}
traverse(input, key, index, parent) {
const ast = input;
const ctx = { key: null, index: index ?? null, ast };
const hasScopeTracker = !!this.scopeTracker;
const _walk = (input2, parent2, key2, index2, skip) => {
if (!isNode(input2)) {
return null;
}
this.scopeTracker?.processNodeEnter(input2);
let currentNode = input2;
let removedInEnter = false;
let skipChildren = skip;
if (this.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 = key2;
ctx.index = index2;
this.enter.call(this.contextEnter, input2, parent2, ctx);
if (this._replacement && !this._remove) {
currentNode = this._replacement;
this.replace(parent2, key2, index2, this._replacement);
}
if (this._remove) {
removedInEnter = true;
currentNode = null;
this.remove(parent2, key2, index2);
}
if (this._skip) {
skipChildren = true;
}
this._skip = _skip;
this._remove = _remove;
this._replacement = _replacement;
}
if ((!skipChildren || hasScopeTracker) && currentNode) {
for (const k in currentNode) {
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 (isNode(node)) {
_walk(node, currentNode, k, null, skipChildren);
}
}
}
this.scopeTracker?.processNodeLeave(input2);
if (this.leave && !skip) {
const _replacement = this._replacement;
const _remove = this._remove;
this._replacement = null;
this._remove = false;
ctx.key = key2;
ctx.index = index2;
this.leave.call(this.contextLeave, input2, parent2, ctx);
if (this._replacement && !this._remove) {
currentNode = this._replacement;
if (removedInEnter) {
this.insert(parent2, key2, index2, this._replacement);
} else {
this.replace(parent2, key2, index2, this._replacement);
}
}
if (this._remove) {
currentNode = null;
this.remove(parent2, key2, index2);
}
this._replacement = _replacement;
this._remove = _remove;
}
return currentNode;
};
return _walk(input, parent ?? null, key ?? null, index ?? null, false);
}
}
function walk(input, options) {
return new WalkerSync(
{
enter: options.enter,
leave: options.leave
},
{
scopeTracker: options.scopeTracker
}
).traverse(input);
}
const LANG_RE = createRegExp(
exactly("jsx").or("tsx").or("js").or("ts").groupedAs("lang").after(exactly(".").and(anyOf("c", "m").optionally())).at.lineEnd()
);
function parseAndWalk(code, sourceFilename, arg3) {
const lang = sourceFilename?.match(LANG_RE)?.groups?.lang;
const { parseOptions: _parseOptions = {}, ...options } = typeof arg3 === "function" ? { enter: arg3 } : arg3;
const parseOptions = {
sourceType: "module",
lang,
..._parseOptions
};
const ast = parseSync(sourceFilename, code, parseOptions);
walk(ast.program, options);
return ast;
}
class ScopeTracker {
scopeIndexStack = [];
scopeIndexKey = "";
scopes = /* @__PURE__ */ new Map();
options;
isFrozen = false;
constructor(options = {}) {
this.options = options;
}
updateScopeIndexKey() {
this.scopeIndexKey = this.scopeIndexStack.slice(0, -1).join("-");
}
pushScope() {
this.scopeIndexStack.push(0);
this.updateScopeIndexKey();
}
popScope() {
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.updateScopeIndexKey();
}
declareIdentifier(name, data) {
if (this.isFrozen) {
return;
}
let scope = this.scopes.get(this.scopeIndexKey);
if (!scope) {
scope = /* @__PURE__ */ new Map();
this.scopes.set(this.scopeIndexKey, scope);
}
scope.set(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 identifiers = getPatternIdentifiers(pattern);
for (const identifier of identifiers) {
this.declareIdentifier(
identifier.name,
parent.type === "VariableDeclaration" ? new ScopeTrackerVariable(identifier, this.scopeIndexKey, parent) : parent.type === "CatchClause" ? new ScopeTrackerCatchParam(identifier, this.scopeIndexKey, parent) : new ScopeTrackerFunctionParam(
identifier,
this.scopeIndexKey,
parent
)
);
}
}
processNodeEnter = (node) => {
switch (node.type) {
case "Program":
case "BlockStatement":
case "StaticBlock":
this.pushScope();
break;
case "FunctionDeclaration":
if (node.id?.name) {
this.declareIdentifier(
node.id.name,
new ScopeTrackerFunction(node, this.scopeIndexKey)
);
}
this.pushScope();
for (const param of node.params) {
this.declareFunctionParameter(param, node);
}
break;
case "FunctionExpression":
this.pushScope();
if (node.id?.name) {
this.declareIdentifier(
node.id.name,
new ScopeTrackerFunction(node, this.scopeIndexKey)
);
}
this.pushScope();
for (const param of node.params) {
this.declareFunctionParameter(param, node);
}
break;
case "ArrowFunctionExpression":
this.pushScope();
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)
);
}
break;
case "ClassExpression":
this.pushScope();
if (node.id?.name) {
this.declareIdentifier(
node.id.name,
new ScopeTrackerIdentifier(node.id, this.scopeIndexKey)
);
}
break;
case "ImportDeclaration":
for (const specifier of node.specifiers) {
this.declareIdentifier(
specifier.local.name,
new ScopeTrackerImport(specifier, this.scopeIndexKey, node)
);
}
break;
case "CatchClause":
this.pushScope();
if (node.param) {
this.declarePattern(node.param, node);
}
break;
case "ForStatement":
case "ForOfStatement":
case "ForInStatement":
this.pushScope();
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) => {
switch (node.type) {
case "Program":
case "BlockStatement":
case "CatchClause":
case "FunctionDeclaration":
case "ArrowFunctionExpression":
case "StaticBlock":
case "ClassExpression":
case "ForStatement":
case "ForOfStatement":
case "ForInStatement":
this.popScope();
break;
case "FunctionExpression":
this.popScope();
this.popScope();
break;
}
};
/**
* Check if an identifier is declared in the current scope or any parent scope.
* @param name the identifier name to check
*/
isDeclared(name) {
if (!this.scopeIndexKey) {
return this.scopes.get("")?.has(name) || false;
}
const indices = this.scopeIndexKey.split("-").map(Number);
for (let i = indices.length; i >= 0; i--) {
if (this.scopes.get(indices.slice(0, i).join("-"))?.has(name)) {
return true;
}
}
return false;
}
/**
* Get the declaration node for a given identifier name.
* @param name the identifier name to look up
*/
getDeclaration(name) {
if (!this.scopeIndexKey) {
return this.scopes.get("")?.get(name) ?? null;
}
const indices = this.scopeIndexKey.split("-").map(Number);
for (let i = indices.length; i >= 0; i--) {
const node = this.scopes.get(indices.slice(0, i).join("-"))?.get(name);
if (node) {
return node;
}
}
return null;
}
/**
* 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.updateScopeIndexKey();
}
}
function getPatternIdentifiers(pattern) {
const identifiers = [];
function collectIdentifiers(pattern2) {
switch (pattern2.type) {
case "Identifier":
identifiers.push(pattern2);
break;
case "AssignmentPattern":
collectIdentifiers(pattern2.left);
break;
case "RestElement":
collectIdentifiers(pattern2.argument);
break;
case "ArrayPattern":
for (const element of pattern2.elements) {
if (element) {
collectIdentifiers(
element.type === "RestElement" ? element.argument : element
);
}
}
break;
case "ObjectPattern":
for (const property of pattern2.properties) {
collectIdentifiers(
property.type === "RestElement" ? property.argument : property.value
);
}
break;
}
}
collectIdentifiers(pattern);
return identifiers;
}
function isBindingIdentifier(node, parent) {
if (!parent || node.type !== "Identifier") {
return false;
}
switch (parent.type) {
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
if (parent.type !== "ArrowFunctionExpression" && parent.id === node) {
return true;
}
if (parent.params.length) {
for (const param of parent.params) {
const identifiers = getPatternIdentifiers(param);
if (identifiers.includes(node)) {
return true;
}
}
}
return false;
case "ClassDeclaration":
case "ClassExpression":
return parent.id === node;
case "MethodDefinition":
return parent.key === node;
case "PropertyDefinition":
return parent.key === node;
case "VariableDeclarator":
return getPatternIdentifiers(parent.id).includes(node);
case "CatchClause":
if (!parent.param) {
return false;
}
return getPatternIdentifiers(parent.param).includes(node);
case "Property":
return parent.key === node && parent.value !== node;
case "MemberExpression":
return parent.property === node;
}
return false;
}
function getUndeclaredIdentifiersInFunction(node) {
const scopeTracker = new ScopeTracker({
preserveExitedScopes: true
});
const undeclaredIdentifiers = /* @__PURE__ */ new Set();
function isIdentifierUndeclared(node2, parent) {
return !isBindingIdentifier(node2, parent) && !scopeTracker.isDeclared(node2.name);
}
walk(node, {
scopeTracker
});
scopeTracker.freeze();
walk(node, {
scopeTracker,
enter(node2, parent) {
if (node2.type === "Identifier" && isIdentifierUndeclared(node2, parent)) {
undeclaredIdentifiers.add(node2.name);
}
}
});
return Array.from(undeclaredIdentifiers);
}
function isChildScope(a, b) {
return a.startsWith(b) && a.length > b.length;
}
class BaseNode {
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);
}
}
class ScopeTrackerIdentifier extends BaseNode {
type = "Identifier";
get start() {
return this.node.start;
}
get end() {
return this.node.end;
}
}
class ScopeTrackerFunctionParam 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;
}
}
class ScopeTrackerFunction extends BaseNode {
type = "Function";
get start() {
return this.node.start;
}
get end() {
return this.node.end;
}
}
class ScopeTrackerVariable 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;
}
}
class ScopeTrackerImport 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;
}
}
class ScopeTrackerCatchParam 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;
}
}
export { ScopeTracker, getUndeclaredIdentifiersInFunction, isBindingIdentifier, parseAndWalk, walk };