fix(core): Improve cyclic dependency check in the DI container (#12600)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2025-01-15 14:15:07 +01:00 committed by GitHub
parent eceee7f3f8
commit c3c4a20002
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 48 additions and 11 deletions

View file

@ -1,2 +1,7 @@
/** @type {import('jest').Config} */
module.exports = require('../../../jest.config');
module.exports = {
...require('../../../jest.config'),
transform: {
'^.+\\.ts$': ['ts-jest', { isolatedModules: false }],
},
};

View file

@ -0,0 +1,17 @@
import { ServiceA } from './fixtures/ServiceA';
import { ServiceB } from './fixtures/ServiceB';
import { Container } from '../di';
describe('DI Container', () => {
describe('circular dependency', () => {
it('should detect multilevel circular dependencies', () => {
expect(() => Container.get(ServiceA)).toThrow(
'[DI] Circular dependency detected in ServiceB at index 0.\nServiceA -> ServiceB',
);
expect(() => Container.get(ServiceB)).toThrow(
'[DI] Circular dependency detected in ServiceB at index 0.\nServiceB',
);
});
});
});

View file

@ -0,0 +1,8 @@
// eslint-disable-next-line import/no-cycle
import { ServiceB } from './ServiceB';
import { Service } from '../../di';
@Service()
export class ServiceA {
constructor(readonly b: ServiceB) {}
}

View file

@ -0,0 +1,8 @@
// eslint-disable-next-line import/no-cycle
import { ServiceA } from './ServiceA';
import { Service } from '../../di';
@Service()
export class ServiceB {
constructor(readonly a: ServiceA) {}
}

View file

@ -78,13 +78,6 @@ class ContainerClass {
if (metadata?.instance) return metadata.instance as T;
// Check for circular dependencies before proceeding with instantiation
if (resolutionStack.includes(type)) {
throw new DIError(
`Circular dependency detected. ${resolutionStack.map((t) => t.name).join(' -> ')}`,
);
}
// Add current type to resolution stack before resolving dependencies
resolutionStack.push(type);
@ -96,9 +89,15 @@ class ContainerClass {
} else {
const paramTypes = (Reflect.getMetadata('design:paramtypes', type) ??
[]) as Constructable[];
const dependencies = paramTypes.map(<P>(paramType: Constructable<P>) =>
this.get(paramType),
);
const dependencies = paramTypes.map(<P>(paramType: Constructable<P>, index: number) => {
if (paramType === undefined) {
throw new DIError(
`Circular dependency detected in ${type.name} at index ${index}.\n${resolutionStack.map((t) => t.name).join(' -> ')}\n`,
);
}
return this.get(paramType);
});
// Create new instance with resolved dependencies
instance = new (type as Constructable)(...dependencies) as T;
}