From 2f5fd41a1987509fc0250c1441554984b6e11abc Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 27 Apr 2022 13:24:35 +0200 Subject: [PATCH] refactor(core): verify that standalone entities are not declared in NgModule (#45777) This commits adds JiT checks to verify that a standalone component, directive, pipe are not declared in any NgModule. PR Close #45777 --- packages/core/src/render3/jit/module.ts | 14 ++- .../core/test/acceptance/ng_module_spec.ts | 88 ++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index f039b5d89eaa2..a1a46e67146ae 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -207,7 +207,7 @@ function verifySemanticsOfNgModuleDef( importingModule?: NgModuleType): void { if (verifiedNgModule.get(moduleType)) return; - // skip verifications of standalone components, direcrtives and pipes + // skip verifications of standalone components, directives and pipes if (isStandalone(moduleType)) return; verifiedNgModule.set(moduleType, true); @@ -232,6 +232,7 @@ function verifySemanticsOfNgModuleDef( const exports = maybeUnwrapFn(ngModuleDef.exports); declarations.forEach(verifyDeclarationsHaveDefinitions); declarations.forEach(verifyDirectivesHaveSelector); + declarations.forEach((declarationType) => verifyNotStandalone(declarationType, moduleType)); const combinedDeclarations: Type[] = [ ...declarations.map(resolveForwardRef), ...flatten(imports.map(computeCombinedExports)).map(resolveForwardRef), @@ -275,6 +276,17 @@ function verifySemanticsOfNgModuleDef( } } + function verifyNotStandalone(type: Type, moduleType: NgModuleType): void { + type = resolveForwardRef(type); + const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type); + if (def?.standalone) { + errors.push(`Unexpected "${stringifyForError(type)}" declaration in "${ + stringifyForError(moduleType)}" NgModule. "${ + stringifyForError( + type)}" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`); + } + } + function verifyExportsAreDeclaredOrReExported(type: Type) { type = resolveForwardRef(type); const kind = getComponentDef(type) && 'component' || getDirectiveDef(type) && 'directive' || diff --git a/packages/core/test/acceptance/ng_module_spec.ts b/packages/core/test/acceptance/ng_module_spec.ts index db111d7c8a9a5..ad2f3bbca7b35 100644 --- a/packages/core/test/acceptance/ng_module_spec.ts +++ b/packages/core/test/acceptance/ng_module_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {Component, createNgModuleRef, CUSTOM_ELEMENTS_SCHEMA, destroyPlatform, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element, ɵɵproperty as property} from '@angular/core'; +import {Component, createNgModuleRef, CUSTOM_ELEMENTS_SCHEMA, destroyPlatform, Directive, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, Pipe, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element, ɵɵproperty as property} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -102,6 +102,92 @@ describe('NgModule', () => { expect(TestBed.inject(Service).initializations).toEqual(['RoutesModule', 'AppModule']); }); + describe('standalone components, directives and pipes', () => { + it('should throw when a standalone component is added to NgModule declarations', () => { + @Component({ + selector: 'my-comp', + standalone: true, + template: '', + }) + class MyComp { + } + + @NgModule({ + declarations: [MyComp], + }) + class MyModule { + } + + TestBed.configureTestingModule({imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(MyComp); + }) + .toThrowError( + `Unexpected "MyComp" declaration in "MyModule" NgModule. "MyComp" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`); + }); + + it('should throw when a standalone directive is added to NgModule declarations', () => { + @Directive({ + selector: '[my-dir]', + standalone: true, + }) + class MyDir { + } + + @Component({ + selector: 'my-comp', + template: '', + }) + class MyComp { + } + + @NgModule({ + declarations: [MyDir], + }) + class MyModule { + } + + TestBed.configureTestingModule({declarations: [MyComp], imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(MyComp); + }) + .toThrowError( + `Unexpected "MyDir" declaration in "MyModule" NgModule. "MyDir" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`); + }); + + it('should throw when a standalone pipe is added to NgModule declarations', () => { + @Pipe({ + name: 'my-pipe', + standalone: true, + }) + class MyPipe { + } + + @Component({ + selector: 'my-comp', + template: '', + }) + class MyComp { + } + + @NgModule({ + declarations: [MyPipe], + }) + class MyModule { + } + + TestBed.configureTestingModule({declarations: [MyComp], imports: [MyModule]}); + + expect(() => { + TestBed.createComponent(MyComp); + }) + .toThrowError( + `Unexpected "MyPipe" declaration in "MyModule" NgModule. "MyPipe" is marked as standalone and can't be declared in any NgModule - did you intend to import it?`); + }); + }); + describe('destroy', () => { beforeEach(destroyPlatform); afterEach(destroyPlatform);