This commit is contained in:
167
src/utils/astUtils.ts
Normal file
167
src/utils/astUtils.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user