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