Skip to content

Commit fcd0bb0

Browse files
anchmelevpkozlowski-opensource
authored andcommitted
fix(compiler-cli): prevent recursive scope checks for invalid NgModule imports
Avoid recursive local scope lookups when invalid NgModule imports create import cycles.
1 parent 12b3a1a commit fcd0bb0

File tree

2 files changed

+46
-13
lines changed

2 files changed

+46
-13
lines changed

packages/compiler-cli/src/ngtsc/scope/src/local.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,12 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
151151
registerPipeMetadata(pipe: PipeMeta): void {}
152152

153153
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope | null {
154-
const scope = !this.declarationToModule.has(clazz)
155-
? null
156-
: this.getScopeOfModule(this.declarationToModule.get(clazz)!.ngModule);
157-
return scope;
154+
if (!this.declarationToModule.has(clazz)) {
155+
return null;
156+
}
157+
158+
const module = this.declarationToModule.get(clazz)!.ngModule;
159+
return this.getScopeOfModule(module);
158160
}
159161

160162
/**
@@ -181,9 +183,12 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
181183
* defined, or the string `'error'` if the scope contained errors.
182184
*/
183185
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope | null {
184-
return this.moduleToRef.has(clazz)
185-
? this.getScopeOfModuleReference(this.moduleToRef.get(clazz)!)
186-
: null;
186+
if (!this.moduleToRef.has(clazz)) {
187+
return null;
188+
}
189+
190+
const scope = this.getScopeOfModuleReference(this.moduleToRef.get(clazz)!);
191+
return scope === 'cycle' ? null : scope;
187192
}
188193

189194
/**
@@ -249,13 +254,17 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
249254
/**
250255
* Implementation of `getScopeOfModule` which accepts a reference to a class.
251256
*/
252-
private getScopeOfModuleReference(ref: Reference<ClassDeclaration>): LocalModuleScope | null {
257+
private getScopeOfModuleReference(
258+
ref: Reference<ClassDeclaration>,
259+
): LocalModuleScope | null | 'cycle' {
253260
if (this.cache.has(ref.node)) {
254261
const cachedValue = this.cache.get(ref.node);
255262

256-
if (cachedValue !== IN_PROGRESS_RESOLUTION) {
257-
return cachedValue as LocalModuleScope | null;
263+
if (cachedValue === IN_PROGRESS_RESOLUTION) {
264+
return 'cycle';
258265
}
266+
267+
return cachedValue as LocalModuleScope | null;
259268
}
260269

261270
this.cache.set(ref.node, IN_PROGRESS_RESOLUTION);
@@ -593,7 +602,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
593602
}
594603
return this.dependencyScopeReader.resolve(ref);
595604
} else {
596-
if (this.cache.get(ref.node) === IN_PROGRESS_RESOLUTION) {
605+
const scope = this.getScopeOfModuleReference(ref);
606+
if (scope === 'cycle') {
597607
diagnostics.push(
598608
makeDiagnostic(
599609
type === 'import'
@@ -603,11 +613,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
603613
`NgModule "${type}" field contains a cycle`,
604614
),
605615
);
606-
return 'cycle';
607616
}
608617

609618
// The NgModule is declared locally in the current program. Resolve it from the registry.
610-
return this.getScopeOfModuleReference(ref);
619+
return scope;
611620
}
612621
}
613622

packages/compiler-cli/test/ngtsc/ngtsc_spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11492,6 +11492,30 @@ runInEachFileSystem((os: string) => {
1149211492
`The pipe 'TestPipe' appears in 'imports', but is not standalone`,
1149311493
);
1149411494
});
11495+
11496+
it('should not recurse when a non-standalone component is both declared and imported', () => {
11497+
env.write(
11498+
'/test.ts',
11499+
`
11500+
import {Component, NgModule} from '@angular/core';
11501+
11502+
@Component({standalone: false, template: ''})
11503+
export class TestComp {}
11504+
11505+
@NgModule({
11506+
declarations: [TestComp],
11507+
imports: [TestComp],
11508+
})
11509+
export class TestModule {}
11510+
`,
11511+
);
11512+
11513+
const diags = env.driveDiagnostics();
11514+
expect(diags.length).toBe(1);
11515+
expect(diags[0].messageText).toContain(
11516+
`The component 'TestComp' appears in 'imports', but is not standalone`,
11517+
);
11518+
});
1149511519
});
1149611520
});
1149711521

0 commit comments

Comments
 (0)