310 lines
7.5 KiB
TypeScript
310 lines
7.5 KiB
TypeScript
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<typeof createTypeParser>;
|
|
|
|
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<typeof createInterfaceParser>;
|
|
|
|
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<T> {
|
|
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<typeof createTypeAliasParser>;
|
|
|
|
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<typeof createImportParser>;
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|