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

23
Frontend-Learner/node_modules/oxc-walker/LICENCE generated vendored Normal file
View file

@ -0,0 +1,23 @@
MIT License
Copyright (c) 2024 Daniel Roe
Copyright (c) 2024 Matej Černý
Copyright (c) 2015-2020 [estree-walker contributors] (https://github.com/Rich-Harris/estree-walker/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

225
Frontend-Learner/node_modules/oxc-walker/README.md generated vendored Normal file
View file

@ -0,0 +1,225 @@
# oxc-walker
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![Github Actions][github-actions-src]][github-actions-href]
[![Codecov][codecov-src]][codecov-href]
A strongly-typed ESTree AST walker built on top of [oxc-parser](https://github.com/oxc-project/oxc).
## Usage
Install package:
```sh
# npm
npm install oxc-walker
# pnpm
pnpm install oxc-walker
```
### Walk a parsed AST
```ts
import { parseSync } from 'oxc-parser'
import { walk } from 'oxc-walker'
const ast = parseSync('example.js', 'const x = 1')
walk(ast.program, {
enter(node, parent, ctx) {
// ...
},
})
```
### Parse and walk directly
```js
import { parseAndWalk } from 'oxc-walker'
parseAndWalk('const x = 1', 'example.js', (node, parent, ctx) => {
// ...
})
```
## ⚙️ API
### `walk(ast, options)`
Walk an AST.
```ts
// options
interface WalkOptions {
/**
* The function to be called when entering a node.
*/
enter?: (node: Node, parent: Node | null, ctx: CallbackContext) => void
/**
* The function to be called when leaving a node.
*/
leave?: (node: Node, parent: Node | null, ctx: CallbackContext) => void
/**
* The instance of `ScopeTracker` to use for tracking declarations and references.
*/
scopeTracker?: ScopeTracker
}
interface CallbackContext {
/**
* The key of the current node within its parent node object, if applicable.
*/
key: string | number | symbol | null | undefined
/**
* The zero-based index of the current node within its parent's children array, if applicable.
*/
index: number | null
/**
* The full Abstract Syntax Tree (AST) that is being walked, starting from the root node.
*/
ast: Program | Node
}
```
#### `this.skip()`
When called inside an `enter` callback, prevents the node's children from being walked.
It is not available in `leave`.
#### `this.replace(newNode)`
Replaces the current node with `newNode`. When called inside `enter`, the **new node's children** will be walked.
The leave callback will still be called with the original node.
> ⚠️ When a `ScopeTracker` is provided, calling `this.replace()` will not update its declarations.
#### `this.remove()`
Removes the current node from its parent. When called inside `enter`, the removed node's children
will not be walked.
_This has a higher precedence than `this.replace()`, so if both are called, the node will be removed._
> ⚠️ When a `ScopeTracker` is provided, calling `this.remove()` will not update its declarations.
### `parseAndWalk(source, filename, callback, options?)`
Parse the source code using `oxc-parser`, walk the resulting AST and return the `ParseResult`.
Overloads:
- `parseAndWalk(code, filename, enter)`
- `parseAndWalk(code, filename, options)`
```ts
interface ParseAndWalkOptions {
/**
* The function to be called when entering a node.
*/
enter?: (node: Node, parent: Node | null, ctx: CallbackContext) => void
/**
* The function to be called when leaving a node.
*/
leave?: (node: Node, parent: Node | null, ctx: CallbackContext) => void
/**
* The instance of `ScopeTracker` to use for tracking declarations and references.
*/
scopeTracker?: ScopeTracker
/**
* The options for `oxc-parser` to use when parsing the code.
*/
parseOptions?: ParserOptions
}
```
### `ScopeTracker`
A utility to track scopes and declarations while walking an AST. It is designed to be used with the `walk`
function from this library.
```ts
interface ScopeTrackerOptions {
/**
* If true, the scope tracker will preserve exited scopes in memory.
* @default false
*/
preserveExitedScopes?: boolean
}
```
#### Example usage:
```ts
import { parseAndWalk, ScopeTracker } from 'oxc-walker'
const scopeTracker = new ScopeTracker()
parseAndWalk('const x = 1; function foo() { console.log(x) }', 'example.js', {
scopeTracker,
enter(node, parent) {
if (node.type === 'Identifier' && node.name === 'x' && parent?.type === 'CallExpression') {
const declaration = scopeTracker.getDeclaration(node.name)
console.log(declaration) // ScopeTrackerVariable
}
},
})
```
```ts
import { parseAndWalk, ScopeTracker, walk } from 'oxc-walker'
const code = `
function foo() {
console.log(a)
}
const a = 1
`
const scopeTracker = new ScopeTracker({
preserveExitedScopes: true,
})
// pre-pass to collect hoisted declarations
const { program } = parseAndWalk(code, 'example.js', {
scopeTracker,
})
// freeze the scope tracker to prevent further modifications
// and prepare it for second pass
scopeTracker.freeze()
// main pass to analyze references
walk(program, {
scopeTracker,
enter(node) {
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression' /* ... */) {
const declaration = scopeTracker.getDeclaration('a')
console.log(declaration) // ScopeTrackerVariable; would be `null` without the pre-pass
}
}
})
```
#### Helpers:
- `scopeTracker.isDeclared(name: string): boolean` - check if an identifier is declared in reference to the current scope
- `scopeTracker.getDeclaration(name: string): ScopeTrackerNode | null` - get the scope tracker node with metadata for a given identifier name in reference to the current scope
- `scopeTracker.freeze()` - freeze the scope tracker to prevent further modifications and prepare for second pass (useful for multi-pass analysis)
- `scopeTracker.getCurrentScope(): string` - get the key of the current scope (a unique identifier for the scope, do not rely on its format)
- `scopeTracker.isCurrentScopeUnder(scopeKey: string): boolean` - check if the current scope is a child of the given scope key
## 💻 Development
- Clone this repository
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`
## License
Made with ❤️
Published under [MIT License](./LICENCE).
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/oxc-walker?style=flat-square
[npm-version-href]: https://npmjs.com/package/oxc-walker
[npm-downloads-src]: https://img.shields.io/npm/dm/oxc-walker?style=flat-square
[npm-downloads-href]: https://npm.chart.dev/oxc-walker
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/danielroe/oxc-walker/ci.yml?branch=main&style=flat-square
[github-actions-href]: https://github.com/danielroe/oxc-walker/actions?query=workflow%3Aci
[codecov-src]: https://img.shields.io/codecov/c/gh/danielroe/oxc-walker/main?style=flat-square
[codecov-href]: https://codecov.io/gh/danielroe/oxc-walker

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 };

60
Frontend-Learner/node_modules/oxc-walker/package.json generated vendored Normal file
View file

@ -0,0 +1,60 @@
{
"name": "oxc-walker",
"type": "module",
"version": "0.6.0",
"description": "",
"license": "MIT",
"repository": "oxc-project/oxc-walker",
"sideEffects": false,
"exports": {
".": "./dist/index.mjs"
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"peerDependencies": {
"oxc-parser": ">=0.98.0"
},
"dependencies": {
"magic-regexp": "^0.10.0"
},
"devDependencies": {
"@types/node": "24.10.1",
"@vitest/coverage-v8": "4.0.10",
"knip": "^5.69.1",
"lint-staged": "16.2.6",
"oxc-parser": "0.98.0",
"oxfmt": "^0.14.0",
"oxlint": "^1.29.0",
"simple-git-hooks": "2.13.1",
"typescript": "5.9.3",
"unbuild": "3.6.1",
"vitest": "4.0.10"
},
"resolutions": {
"oxc-walker": "link:."
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*.{js,ts,mjs,cjs}": [
"pnpm run fmt"
],
"*.{js,ts,mjs,cjs,json,.*rc}": [
"pnpm run lint --fix"
]
},
"scripts": {
"build": "unbuild",
"dev": "vitest dev",
"lint": "oxlint",
"fmt": "oxfmt",
"test": "pnpm test:unit && pnpm test:types",
"test:unit": "vitest",
"test:types": "tsc --noEmit"
}
}