-
Notifications
You must be signed in to change notification settings - Fork 333
refactor(plugins): share small AST helper utilities #1754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| export type AstRecord = { | ||
| type: string; | ||
| start?: number; | ||
| end?: number; | ||
| [key: string]: unknown; | ||
| }; | ||
|
|
||
| export type AstRange = AstRecord & { | ||
| start: number; | ||
| end: number; | ||
| }; | ||
|
|
||
| const SKIP_CHILD_KEYS = new Set(["type", "parent", "loc", "start", "end"]); | ||
|
|
||
| function getObjectProperty(value: unknown, key: string): unknown { | ||
| if (typeof value !== "object" || value === null) return null; | ||
| return Reflect.get(value, key); | ||
| } | ||
|
|
||
| export function isAstRecord(value: unknown): value is AstRecord { | ||
| return typeof getObjectProperty(value, "type") === "string"; | ||
| } | ||
|
|
||
| function toAstRecord(value: unknown): AstRecord | null { | ||
| return isAstRecord(value) ? value : null; | ||
| } | ||
|
|
||
| export function nodeArray(value: unknown): unknown[] { | ||
| return Array.isArray(value) ? value : []; | ||
| } | ||
|
|
||
| export function hasRange(node: AstRecord | null): node is AstRange { | ||
| return node !== null && typeof node.start === "number" && typeof node.end === "number"; | ||
| } | ||
|
|
||
| export function isIdentifierNamed(value: unknown, name: string): boolean { | ||
| return isAstRecord(value) && value.type === "Identifier" && value.name === name; | ||
| } | ||
|
|
||
| export function getAstName(value: unknown): string | null { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const node = toAstRecord(value); | ||
| if (!node) return null; | ||
| if (node.type === "Identifier" && typeof node.name === "string") return node.name; | ||
| if (typeof node.value === "string") return node.value; | ||
| return null; | ||
| } | ||
|
|
||
| export function forEachAstChild(node: AstRecord, callback: (child: AstRecord) => void): void { | ||
| for (const [key, value] of Object.entries(node)) { | ||
| if (SKIP_CHILD_KEYS.has(key)) continue; | ||
| const child = toAstRecord(value); | ||
| if (child) { | ||
| callback(child); | ||
| continue; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| for (const item of value) { | ||
| const itemNode = toAstRecord(item); | ||
| if (itemNode) callback(itemNode); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export function collectBindingNames(pattern: unknown, target: Set<string>): void { | ||
| const node = toAstRecord(pattern); | ||
| if (!node) return; | ||
|
|
||
| switch (node.type) { | ||
| case "Identifier": | ||
| if (typeof node.name === "string") target.add(node.name); | ||
| return; | ||
| case "RestElement": | ||
| collectBindingNames(node.argument, target); | ||
| return; | ||
| case "AssignmentPattern": | ||
| collectBindingNames(node.left, target); | ||
| return; | ||
| case "ArrayPattern": | ||
| for (const element of nodeArray(node.elements)) collectBindingNames(element, target); | ||
| return; | ||
| case "ObjectPattern": | ||
| for (const property of nodeArray(node.properties)) { | ||
| const propertyNode = toAstRecord(property); | ||
| if (!propertyNode) continue; | ||
| collectBindingNames( | ||
| propertyNode.type === "Property" ? propertyNode.value : propertyNode.argument, | ||
| target, | ||
| ); | ||
| } | ||
| return; | ||
| case "Property": | ||
| collectBindingNames(node.value, target); | ||
| return; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: the old inline walkers in
import-meta-url.tsskipped onlytype/start/end/locand did not skipparent. Addingparenthere is a safe, defensive change (avoids cycles if aparentbackref ever exists), but it is a behavioral difference worth a short comment so the intent is clear, e.g.// skip backrefs + positional metadata.