206 lines
5.9 KiB
TypeScript
206 lines
5.9 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { TypeParser, createTypeParser } from '../src/parsers/typeParser';
|
|
import { DependencyGraphBuilder, createDependencyGraphBuilder } from '../src/analyzers/dependencyGraph';
|
|
import { detectCircularDependencies } from '../src/analyzers/circularDetector';
|
|
|
|
describe('Integration Tests', () => {
|
|
const fixturesDir = path.join(__dirname, 'fixtures');
|
|
|
|
beforeAll(() => {
|
|
if (!fs.existsSync(fixturesDir)) {
|
|
fs.mkdirSync(fixturesDir, { recursive: true });
|
|
}
|
|
});
|
|
|
|
describe('Real-world TypeScript parsing', () => {
|
|
it('should parse complex interfaces with generics', () => {
|
|
const source = `
|
|
interface ApiResponse<T> {
|
|
data: T;
|
|
status: number;
|
|
message: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
}
|
|
|
|
type UserApiResponse = ApiResponse<User>;
|
|
`;
|
|
|
|
const parser = createTypeParser();
|
|
const result = parser.parse(source, 'test.ts');
|
|
|
|
expect(result.types.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('should parse conditional types', () => {
|
|
const source = `
|
|
type IfEquals<X, Y, A, B> =
|
|
(<T>() => T extends X ? 1 : 2) extends
|
|
(<T>() => T extends Y ? 1 : 2) ? A : B;
|
|
|
|
type DeepPartial<T> = {
|
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
};
|
|
`;
|
|
|
|
const parser = createTypeParser();
|
|
const result = parser.parse(source, 'test.ts');
|
|
|
|
expect(result.types.length).toBe(2);
|
|
});
|
|
|
|
it('should parse mapped types', () => {
|
|
const source = `
|
|
type Readonly<T> = {
|
|
readonly [P in keyof T]: T[P];
|
|
};
|
|
|
|
type Partial<T> = {
|
|
[P in keyof T]?: T[P];
|
|
};
|
|
`;
|
|
|
|
const parser = createTypeParser();
|
|
const result = parser.parse(source, 'test.ts');
|
|
|
|
expect(result.types.length).toBe(2);
|
|
});
|
|
|
|
it('should parse template literal types', () => {
|
|
const source = `
|
|
type EventName = \`on\${string}\`;
|
|
type Color = 'red' | 'green' | 'blue';
|
|
`;
|
|
|
|
const parser = createTypeParser();
|
|
const result = parser.parse(source, 'test.ts');
|
|
|
|
expect(result.types.length).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('Circular Dependency Detection Integration', () => {
|
|
it('should detect circular dependencies in a chain', () => {
|
|
const files = [
|
|
{
|
|
filePath: '/test/a.ts',
|
|
types: [
|
|
{ name: 'A', filePath: '/test/a.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['B'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
},
|
|
{
|
|
filePath: '/test/b.ts',
|
|
types: [
|
|
{ name: 'B', filePath: '/test/b.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['C'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
},
|
|
{
|
|
filePath: '/test/c.ts',
|
|
types: [
|
|
{ name: 'C', filePath: '/test/c.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['A'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
}
|
|
];
|
|
|
|
const builder = createDependencyGraphBuilder();
|
|
const graph = builder.build(files, '/test');
|
|
const cycles = detectCircularDependencies(graph);
|
|
|
|
expect(cycles.length).toBeGreaterThan(0);
|
|
expect(cycles[0].nodes).toContain('/test/a.ts#A');
|
|
expect(cycles[0].nodes).toContain('/test/b.ts#B');
|
|
expect(cycles[0].nodes).toContain('/test/c.ts#C');
|
|
});
|
|
|
|
it('should handle multiple independent circular dependencies', () => {
|
|
const files = [
|
|
{
|
|
filePath: '/test/a.ts',
|
|
types: [
|
|
{ name: 'A', filePath: '/test/a.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['B'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
},
|
|
{
|
|
filePath: '/test/b.ts',
|
|
types: [
|
|
{ name: 'B', filePath: '/test/b.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['A'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
},
|
|
{
|
|
filePath: '/test/c.ts',
|
|
types: [
|
|
{ name: 'C', filePath: '/test/c.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['D'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
},
|
|
{
|
|
filePath: '/test/d.ts',
|
|
types: [
|
|
{ name: 'D', filePath: '/test/d.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['C'], rawNode: undefined }
|
|
],
|
|
imports: [],
|
|
errors: []
|
|
}
|
|
];
|
|
|
|
const builder = createDependencyGraphBuilder();
|
|
const graph = builder.build(files, '/test');
|
|
const cycles = detectCircularDependencies(graph);
|
|
|
|
expect(cycles.length).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('Full Analysis Pipeline', () => {
|
|
it('should parse and build graph for complex type hierarchy', () => {
|
|
const source = `
|
|
interface Animal {
|
|
name: string;
|
|
}
|
|
|
|
interface Dog extends Animal {
|
|
breed: string;
|
|
}
|
|
|
|
interface Cat extends Animal {
|
|
color: string;
|
|
}
|
|
|
|
type Pet = Dog | Cat;
|
|
`;
|
|
|
|
const parser = createTypeParser();
|
|
const parsedFiles = parser.parse(source, 'test.ts');
|
|
|
|
const builder = createDependencyGraphBuilder();
|
|
const graph = builder.build([parsedFiles], '/test');
|
|
|
|
expect(graph.nodes.size).toBe(4);
|
|
expect(graph.edges.length).toBeGreaterThanOrEqual(2);
|
|
|
|
const dogNode = graph.nodes.get('test.ts#Dog');
|
|
expect(dogNode).toBeDefined();
|
|
|
|
const petNode = graph.nodes.get('test.ts#Pet');
|
|
expect(petNode).toBeDefined();
|
|
});
|
|
});
|
|
});
|