Browse Source

fix(schematics): avoid self-import for same-namespace generic base type argument

When an Angular proxy DTO inherits from a generic base type such as
PagedResultDto<TransactionDto> and the type argument lives in the same
namespace, the model generator emitted an invalid self-import inside
models.ts (e.g. `import type { TransactionDto } from './models'`),
breaking the Angular/TypeScript build.

The base-type generic arguments were not filtered by namespace the way
property refs already are: the outer guard only checked the base type's
own namespace (Volo.Abp...PagedResultDto), so every type in the generic
tree was imported, including same-namespace ones.

Skip generic arguments whose namespace equals the model's, mirroring the
existing property handling. The framework base type is still imported
from @abp/ng.core; only the bogus self-import is dropped.

Adds a regression test reproducing the reported scenario.

Resolves #25080

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/25704/head
Sweet-Dev-star 3 months ago
parent
commit
7176c112e4
  1. 82
      npm/ng-packs/packages/schematics/src/tests/proxy-self-import-generic-base.spec.ts
  2. 8
      npm/ng-packs/packages/schematics/src/utils/model.ts

82
npm/ng-packs/packages/schematics/src/tests/proxy-self-import-generic-base.spec.ts

@ -0,0 +1,82 @@
import { describe, expect, it } from 'vitest';
import { createImportRefsToModelReducer } from '../utils/model';
import { Type } from '../models/api-definition';
import { ModelGeneratorParams } from '../utils/model';
// Reproduces #25080: when a DTO inherits from a generic base type (e.g. PagedResultDto<T>)
// whose type argument T lives in the SAME namespace, the proxy generator used to emit an
// invalid self-import (`import type { TransactionDto } from './models'`) inside models.ts,
// breaking the Angular/TypeScript build.
describe('proxy generation - self-import for generic base type argument (#25080)', () => {
const solution = 'MyApp';
// TransactionPagedResultDto : PagedResultDto<TransactionDto>, both in MyApp.Transactions.
const types: Record<string, Type> = {
'MyApp.Transactions.TransactionDto': {
baseType: null,
isEnum: false,
enumNames: null,
enumValues: null,
genericArguments: null,
properties: [
{
name: 'Id',
jsonName: null,
type: 'System.Guid',
typeSimple: 'System.Guid',
isRequired: false,
isNullable: false,
},
],
},
'MyApp.Transactions.TransactionPagedResultDto': {
baseType: 'Volo.Abp.Application.Dtos.PagedResultDto<MyApp.Transactions.TransactionDto>',
isEnum: false,
enumNames: null,
enumValues: null,
genericArguments: null,
properties: [
{
name: 'AmountSummary',
jsonName: null,
type: 'System.Int32',
typeSimple: 'number',
isRequired: false,
isNullable: false,
},
],
},
};
const params: ModelGeneratorParams = {
targetPath: 'src/app/proxy',
solution,
types,
serviceImports: {},
modelImports: {},
};
it('does not emit a self-import for a same-namespace generic base argument', () => {
const reduce = createImportRefsToModelReducer(params);
const models = reduce([], ['MyApp.Transactions.TransactionPagedResultDto']);
const model = models.find(m => m.namespace === 'Transactions');
expect(model).toBeDefined();
// The buggy behavior produced an import with path './models' (a self-import).
const selfImports = model!.imports.filter(i => i.path === './models');
expect(selfImports).toEqual([]);
// Sanity: TransactionDto must NOT be imported at all, since it is declared in this same file.
const importsTransactionDto = model!.imports.some(i =>
i.specifiers.some(s => s === 'TransactionDto'),
);
expect(importsTransactionDto).toBe(false);
// The framework base type, however, should still be imported from @abp/ng.core.
const importsPagedResultDto = model!.imports.some(
i => i.path === '@abp/ng.core' && i.specifiers.includes('PagedResultDto'),
);
expect(importsPagedResultDto).toBe(true);
});
});

8
npm/ng-packs/packages/schematics/src/utils/model.ts

@ -74,6 +74,14 @@ export function createImportRefsToModelReducer(params: ModelGeneratorParams) {
if (baseType && parseNamespace(solution, baseType) !== model.namespace) { if (baseType && parseNamespace(solution, baseType) !== model.namespace) {
const baseTypeWithGenericParams = parseBaseTypeWithGenericTypes(baseType); const baseTypeWithGenericParams = parseBaseTypeWithGenericTypes(baseType);
baseTypeWithGenericParams.forEach(t => { baseTypeWithGenericParams.forEach(t => {
// A generic argument of the base type (e.g. T in PagedResultDto<T>) may live in the
// same namespace as this model, which means it is generated into the same models.ts
// file. Importing it would produce an invalid self-import (`from './models'`), so we
// skip same-namespace types here, mirroring the property handling below. See #25080.
if (parseNamespace(solution, t) === model.namespace) {
return;
}
toBeImported.push({ toBeImported.push({
type: t, type: t,
isEnum: false, isEnum: false,

Loading…
Cancel
Save