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

This commit is contained in:
2026-01-30 00:57:42 +00:00
parent f2b284f1bc
commit 657442c9a7

260
src/parsers/typeParser.ts Normal file
View File

@@ -0,0 +1,260 @@
import { TSInterfaceDeclaration, TSInterfaceHeritage, TSTypeParameterDeclaration, TSTypeParameter, TSTypeReference, TSPropertySignature, TSMethodSignature, TSTypeAliasDeclaration, TSEnumDeclaration } from '../types/typescript-estree';
import { createASTUtils } from '../utils/astUtils';
import { typeToString } from '../utils/typeUtils';
import { TypeDeclaration, ParsedFile, TypeReference, ImportDeclaration as CustomImportDeclaration } from '../types';
import type * as TSESTree from '@typescript-eslint/typescript-estree';
export interface TypeParserOptions {
includePrivate?: boolean;
includeInternal?: boolean;
skipErrors?: boolean;
}
export class TypeParser {
private astUtils: ReturnType<typeof createASTUtils>;
private options: TypeParserOptions;
constructor(options: TypeParserOptions = {}) {
this.astUtils = createASTUtils();
this.options = {
includePrivate: options.includePrivate ?? false,
includeInternal: options.includeInternal ?? false,
skipErrors: options.skipErrors ?? false
};
}
parse(source: string, filePath: string): ParsedFile {
const result: ParsedFile = {
filePath,
types: [],
imports: [],
errors: []
};
try {
const ast = this.astUtils.parse(source, filePath);
const interfaces = this.astUtils.getInterfaces(ast);
const typeAliases = this.astUtils.getTypeAliases(ast);
const enums = this.astUtils.getEnums(ast);
const imports = this.astUtils.getImports(ast);
for (const iface of interfaces) {
try {
const typeDecl = this.parseInterface(iface, filePath);
if (typeDecl) {
result.types.push(typeDecl);
}
} catch (error) {
this.addError(result, `Failed to parse interface: ${(error as Error).message}`, iface.loc?.start?.line ?? 0);
}
}
for (const typeAlias of typeAliases) {
try {
const typeDecl = this.parseTypeAlias(typeAlias, filePath);
if (typeDecl) {
result.types.push(typeDecl);
}
} catch (error) {
this.addError(result, `Failed to parse type alias: ${(error as Error).message}`, typeAlias.loc?.start?.line ?? 0);
}
}
for (const enumDecl of enums) {
try {
const typeDecl = this.parseEnum(enumDecl, filePath);
if (typeDecl) {
result.types.push(typeDecl);
}
} catch (error) {
this.addError(result, `Failed to parse enum: ${(error as Error).message}`, enumDecl.loc?.start?.line ?? 0);
}
}
for (const importDecl of imports) {
try {
const importType = this.parseImport(importDecl, filePath);
if (importType) {
result.imports.push(importType);
}
} catch (error) {
this.addError(result, `Failed to parse import: ${(error as Error).message}`, importDecl.loc?.start?.line ?? 0);
}
}
} catch (error) {
this.addError(result, `Failed to parse file: ${(error as Error).message}`, 1);
}
return result;
}
private parseInterface(node: TSInterfaceDeclaration, filePath: string): TypeDeclaration | null {
const members = this.parseInterfaceMembers(node.body.body);
const extendsTypes = this.parseExtendsClause(node.extends ?? null);
const generics = this.parseGenericParameters(node.typeParameters ?? null);
return {
name: node.id.name,
filePath,
startLine: node.loc?.start?.line ?? 0,
endLine: node.loc?.end?.line ?? 0,
kind: 'interface',
dependencies: this.extractDependencies(node),
rawNode: node
};
}
private parseInterfaceMembers(members: any[]): import('../types').InterfaceMember[] {
return members
.filter((m): m is TSPropertySignature | TSMethodSignature => {
return m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature';
})
.map((member) => {
const isProperty = member.type === 'TSPropertySignature';
const propertyMember = member as TSPropertySignature;
return {
name: member.key.type === 'Identifier' ? member.key.name : typeToString(member.key),
type: isProperty && propertyMember.typeAnnotation ? typeToString(propertyMember.typeAnnotation.typeAnnotation) : 'unknown',
isOptional: isProperty ? (propertyMember.optional ?? false) : false,
isReadonly: isProperty ? (propertyMember.readonly ?? false) : false,
modifiers: []
};
});
}
private parseExtendsClause(extendsClause: TSInterfaceHeritage[] | null | undefined): string[] {
if (!extendsClause) {
return [];
}
return extendsClause.map((ext) => {
if (ext.expression.type === 'Identifier') {
return ext.expression.name;
}
return typeToString(ext.expression);
});
}
private parseGenericParameters(params: TSTypeParameterDeclaration | null | undefined): import('../types').GenericParameter[] {
if (!params || !params.params) {
return [];
}
return params.params.map((param: TSTypeParameter) => ({
name: param.name.name,
constraint: param.constraint ? typeToString(param.constraint) : undefined,
default: param.default ? typeToString(param.default) : undefined
}));
}
private parseTypeAlias(node: TSTypeAliasDeclaration, filePath: string): TypeDeclaration | null {
const generics = this.parseGenericParameters(node.typeParameters ?? null);
return {
name: node.id.name,
filePath,
startLine: node.loc?.start?.line ?? 0,
endLine: node.loc?.end?.line ?? 0,
kind: 'type_alias',
dependencies: this.extractDependencies(node),
rawNode: node
};
}
private parseEnum(node: TSEnumDeclaration, filePath: string): TypeDeclaration | null {
return {
name: node.id.name,
filePath,
startLine: node.loc?.start?.line ?? 0,
endLine: node.loc?.end?.line ?? 0,
kind: 'enum',
dependencies: [],
rawNode: node
};
}
private parseImport(node: TSESTree.ImportDeclaration, filePath: string): CustomImportDeclaration | null {
const namedImports: string[] = [];
let defaultImport: string | undefined;
let namespaceImport: string | undefined;
if (node.specifiers) {
for (const specifier of node.specifiers) {
if (specifier.type === 'ImportSpecifier') {
namedImports.push(specifier.local.name);
} else if (specifier.type === 'ImportDefaultSpecifier') {
defaultImport = specifier.local.name;
} else if (specifier.type === 'ImportNamespaceSpecifier') {
namespaceImport = specifier.local.name;
}
}
}
return {
name: node.source.value,
filePath,
startLine: node.loc?.start?.line ?? 0,
endLine: node.loc?.end?.line ?? 0,
kind: 'import',
dependencies: [],
rawNode: node,
moduleName: node.source.value,
namedImports,
defaultImport,
namespaceImport
};
}
private extractDependencies(node: any): string[] {
const dependencies: string[] = [];
const typeRefs = this.astUtils.findNodes<TSTypeReference>(
node,
(n): n is TSTypeReference => n.type === 'TSTypeReference'
);
for (const ref of typeRefs) {
if (ref.typeName.type === 'Identifier') {
const typeName = (ref.typeName as { type: 'Identifier'; name: string }).name;
if (!dependencies.includes(typeName)) {
dependencies.push(typeName);
}
} else if (ref.typeName.type === 'TSQualifiedName') {
const qualifiedName = ref.typeName as { left: { name: string }; right: { name: string } };
const typeName = `${qualifiedName.left.name}.${qualifiedName.right.name}`;
if (!dependencies.includes(typeName)) {
dependencies.push(typeName);
}
}
}
const heritage = this.astUtils.findNodes<TSInterfaceHeritage>(
node,
(n): n is TSInterfaceHeritage => n.type === 'TSInterfaceHeritage'
);
for (const h of heritage) {
if (h.expression.type === 'Identifier') {
const typeName = h.expression.name;
if (!dependencies.includes(typeName)) {
dependencies.push(typeName);
}
}
}
return dependencies;
}
private addError(result: ParsedFile, message: string, line: number): void {
if (!this.options.skipErrors) {
result.errors.push({
message,
line,
column: 0,
severity: 'error'
});
}
}
}
export function createTypeParser(options?: TypeParserOptions): TypeParser {
return new TypeParser(options);
}