diff --git a/test/parser.test.ts b/test/parser.test.ts new file mode 100644 index 0000000..ee4b5a3 --- /dev/null +++ b/test/parser.test.ts @@ -0,0 +1,309 @@ +import { TypeParser, createTypeParser } from '../src/parsers/typeParser'; +import { InterfaceParser, createInterfaceParser } from '../src/parsers/interfaceParser'; +import { TypeAliasParser, createTypeAliasParser } from '../src/parsers/typeAliasParser'; +import { ImportParser, createImportParser } from '../src/parsers/importParser'; + +describe('TypeParser', () => { + let parser: ReturnType; + + beforeEach(() => { + parser = createTypeParser(); + }); + + describe('parse', () => { + it('should parse a simple interface', () => { + const source = ` + interface User { + id: number; + name: string; + email: string; + } + `; + + const result = parser.parse(source, 'test.ts'); + + expect(result.types).toHaveLength(1); + expect(result.types[0].name).toBe('User'); + expect(result.types[0].kind).toBe('interface'); + }); + + it('should parse multiple interfaces', () => { + const source = ` + interface A { + id: number; + } + + interface B { + name: string; + } + `; + + const result = parser.parse(source, 'test.ts'); + + expect(result.types).toHaveLength(2); + }); + + it('should parse type aliases', () => { + const source = ` + type ID = number | string; + type UserResponse = { + id: ID; + name: string; + }; + `; + + const result = parser.parse(source, 'test.ts'); + + expect(result.types.length).toBeGreaterThanOrEqual(1); + }); + + it('should parse enums', () => { + const source = ` + enum Status { + Pending = 'pending', + Approved = 'approved', + Rejected = 'rejected' + } + `; + + const result = parser.parse(source, 'test.ts'); + + expect(result.types.some(t => t.kind === 'enum')).toBe(true); + }); + + it('should parse imports', () => { + const source = ` + import { A, B } from './module'; + import C from './default'; + import * as D from './namespace'; + `; + + const result = parser.parse(source, 'test.ts'); + + expect(result.imports.length).toBe(3); + }); + + it('should handle empty source', () => { + const source = ''; + + const result = parser.parse(source, 'test.ts'); + + expect(result.types).toHaveLength(0); + expect(result.imports).toHaveLength(0); + }); + + it('should track dependencies', () => { + const source = ` + interface A { + b: B; + } + + interface B { + c: C; + } + `; + + const result = parser.parse(source, 'test.ts'); + + const a = result.types.find(t => t.name === 'A'); + expect(a?.dependencies).toContain('B'); + }); + }); +}); + +describe('InterfaceParser', () => { + let parser: ReturnType; + + beforeEach(() => { + parser = createInterfaceParser(); + }); + + describe('parseInterface', () => { + it('should parse interface properties', () => { + const source = ` + interface User { + id: number; + name: string; + age?: number; + } + `; + + const interfaces = parser.parse(source, 'test.ts'); + + expect(interfaces).toHaveLength(1); + expect(interfaces[0].members.length).toBe(3); + }); + + it('should parse interface with extends', () => { + const source = ` + interface A {} + interface B extends A {} + `; + + const interfaces = parser.parse(source, 'test.ts'); + + const b = interfaces.find(i => i.name === 'B'); + expect(b?.extends).toContain('A'); + }); + + it('should parse generic parameters', () => { + const source = ` + interface Container { + value: T; + } + `; + + const interfaces = parser.parse(source, 'test.ts'); + + expect(interfaces[0].generics).toHaveLength(1); + expect(interfaces[0].generics[0].name).toBe('T'); + }); + + it('should parse readonly properties', () => { + const source = ` + interface A { + readonly id: number; + } + `; + + const interfaces = parser.parse(source, 'test.ts'); + const member = interfaces[0].members.find(m => m.name === 'id'); + + expect(member?.isReadonly).toBe(true); + }); + }); +}); + +describe('TypeAliasParser', () => { + let parser: ReturnType; + + beforeEach(() => { + parser = createTypeAliasParser(); + }); + + describe('parseTypeAlias', () => { + it('should parse simple type alias', () => { + const source = ` + type ID = number; + `; + + const typeAliases = parser.parse(source, 'test.ts'); + + expect(typeAliases).toHaveLength(1); + expect(typeAliases[0].name).toBe('ID'); + }); + + it('should parse union type', () => { + const source = ` + type Status = 'pending' | 'approved' | 'rejected'; + `; + + const typeAliases = parser.parse(source, 'test.ts'); + + expect(typeAliases[0].typeAnnotation).toContain('|'); + }); + + it('should parse intersection type', () => { + const source = ` + type Combined = A & B; + `; + + const typeAliases = parser.parse(source, 'test.ts'); + + expect(typeAliases[0].typeAnnotation).toContain('&'); + }); + + it('should extract dependencies from union types', () => { + const source = ` + type A = {}; + type B = {}; + type C = A | B; + `; + + const typeAliases = parser.parse(source, 'test.ts'); + const c = typeAliases.find(t => t.name === 'C'); + + expect(c?.dependencies).toContain('A'); + expect(c?.dependencies).toContain('B'); + }); + }); + + describe('getComplexityScore', () => { + it('should calculate complexity score', () => { + const source = ` + type Simple = number; + type Complex = A | B | C | D | E; + `; + + const typeAliases = parser.parse(source, 'test.ts'); + const simple = typeAliases.find(t => t.name === 'Simple'); + const complex = typeAliases.find(t => t.name === 'Complex'); + + const simpleScore = parser.getComplexityScore(simple!); + const complexScore = parser.getComplexityScore(complex!); + + expect(complexScore).toBeGreaterThan(simpleScore); + }); + }); +}); + +describe('ImportParser', () => { + let parser: ReturnType; + + beforeEach(() => { + parser = createImportParser(); + }); + + describe('parse', () => { + it('should parse named imports', () => { + const source = ` + import { A, B, C } from './module'; + `; + + const imports = parser.parse(source, 'test.ts'); + + expect(imports).toHaveLength(1); + expect(imports[0].namedImports).toHaveLength(3); + }); + + it('should parse default import', () => { + const source = ` + import MyDefault from './module'; + `; + + const imports = parser.parse(source, 'test.ts'); + + expect(imports[0].defaultImport).toBe('MyDefault'); + }); + + it('should parse namespace import', () => { + const source = ` + import * as Namespace from './module'; + `; + + const imports = parser.parse(source, 'test.ts'); + + expect(imports[0].namespaceImport).toBe('Namespace'); + }); + + it('should detect external modules', () => { + const source = ` + import { something } from 'external-package'; + `; + + const imports = parser.parse(source, 'test.ts'); + + expect(imports[0].isExternal).toBe(true); + }); + + it('should detect relative imports', () => { + const source = ` + import { something } from './local-module'; + `; + + const imports = parser.parse(source, 'test.ts'); + + expect(imports[0].isExternal).toBe(false); + }); + }); +});