Add utility modules
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-30 01:01:09 +00:00
parent 0ef343c13d
commit c90209b826

167
src/utils/astUtils.ts Normal file
View File

@@ -0,0 +1,167 @@
import * as TSESTree from '@typescript-eslint/typescript-estree';
export interface ASTUtilsOptions {
includeComments?: boolean;
preserveWhitespace?: boolean;
}
export class ASTUtils {
private options: ASTUtilsOptions;
constructor(options: ASTUtilsOptions = {}) {
this.options = {
includeComments: options.includeComments ?? false,
preserveWhitespace: options.preserveWhitespace ?? false
};
}
parse(source: string, _filePath?: string): TSESTree.Program {
return TSESTree.parse(source, {
loc: true,
range: true,
comment: this.options.includeComments,
tokens: true,
useJSXTextNode: true,
ecmaVersion: 'latest',
sourceType: 'module'
});
}
getNodeByRange(node: TSESTree.Node, start: number, end: number): TSESTree.Node | null {
if (node.range[0] <= start && node.range[1] >= end) {
if (node.range[0] === start && node.range[1] === end) {
return node;
}
for (const child of this.getChildren(node)) {
const found = this.getNodeByRange(child, start, end);
if (found) {
return found;
}
}
}
return null;
}
getChildren(node: TSESTree.Node): TSESTree.Node[] {
const children: TSESTree.Node[] = [];
for (const key of Object.keys(node)) {
if (key === 'loc' || key === 'range' || key === 'parent') {
continue;
}
const value = (node as Record<string, unknown>)[key];
if (value && typeof value === 'object') {
if (Array.isArray(value)) {
children.push(...value.filter((v): v is TSESTree.Node => this.isNode(v)));
} else if (this.isNode(value)) {
children.push(value);
}
}
}
return children;
}
private isNode(value: unknown): value is TSESTree.Node {
return (
typeof value === 'object' &&
value !== null &&
'type' in value &&
'range' in value &&
Array.isArray((value as { range: unknown }).range)
);
}
findNodes<T extends TSESTree.Node>(
node: TSESTree.Node,
predicate: (n: TSESTree.Node) => boolean
): T[] {
const results: T[] = [];
const traverse = (n: TSESTree.Node): void => {
if (predicate(n)) {
results.push(n as T);
}
for (const child of this.getChildren(n)) {
traverse(child);
}
};
traverse(node);
return results;
}
findFirstNode<T extends TSESTree.Node>(
node: TSESTree.Node,
predicate: (n: TSESTree.Node) => boolean
): T | null {
if (predicate(node)) {
return node as T;
}
for (const child of this.getChildren(node)) {
const found = this.findFirstNode<T>(child, predicate);
if (found) {
return found;
}
}
return null;
}
getSourceCode(node: TSESTree.Node, source: string): string {
return source.substring(node.range[0], node.range[1]);
}
getLineNumber(node: TSESTree.Node): number {
return node.loc?.start?.line ?? 0;
}
getEndLineNumber(node: TSESTree.Node): number {
return node.loc?.end?.line ?? 0;
}
isExported(node: TSESTree.Node): boolean {
if (node.parent) {
const parent = node.parent as TSESTree.Node;
if (parent.type === 'ExportNamedDeclaration' || parent.type === 'ExportDefaultDeclaration') {
return true;
}
}
return false;
}
getImports(node: TSESTree.Program): TSESTree.ImportDeclaration[] {
return this.findNodes<TSESTree.ImportDeclaration>(
node,
(n): n is TSESTree.ImportDeclaration => n.type === 'ImportDeclaration'
);
}
getExports(node: TSESTree.Program): TSESTree.ExportDeclaration[] {
return this.findNodes<TSESTree.ExportDeclaration>(
node,
(n): n is TSESTree.ExportDeclaration =>
n.type === 'ExportNamedDeclaration' || n.type === 'ExportDefaultDeclaration'
);
}
getInterfaces(node: TSESTree.Program): TSESTree.TSInterfaceDeclaration[] {
return this.findNodes<TSESTree.TSInterfaceDeclaration>(
node,
(n): n is TSESTree.TSInterfaceDeclaration => n.type === 'TSInterfaceDeclaration'
);
}
getTypeAliases(node: TSESTree.Program): TSESTree.TSTypeAliasDeclaration[] {
return this.findNodes<TSESTree.TSTypeAliasDeclaration>(
node,
(n): n is TSESTree.TSTypeAliasDeclaration => n.type === 'TSTypeAliasDeclaration'
);
}
getEnums(node: TSESTree.Program): TSESTree.TSEnumDeclaration[] {
return this.findNodes<TSESTree.TSEnumDeclaration>(
node,
(n): n is TSESTree.TSEnumDeclaration => n.type === 'TSEnumDeclaration'
);
}
}
export function createASTUtils(options?: ASTUtilsOptions): ASTUtils {
return new ASTUtils(options);
}