From 657442c9a745608e7c1057eb999e53146115b478 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 00:57:42 +0000 Subject: [PATCH] Add parser modules --- src/parsers/typeParser.ts | 260 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/parsers/typeParser.ts diff --git a/src/parsers/typeParser.ts b/src/parsers/typeParser.ts new file mode 100644 index 0000000..6525987 --- /dev/null +++ b/src/parsers/typeParser.ts @@ -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; + 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); +}