Skip to content

Commit

Permalink
refactor(core): verify that standalone entities are not declared in N…
Browse files Browse the repository at this point in the history
…gModule (angular#45777)

This commits adds JiT checks to verify that a standalone component, directive,
pipe are not declared in any NgModule.

PR Close angular#45777
  • Loading branch information
pkozlowski-opensource authored and dylhunn committed Apr 27, 2022
1 parent aafac72 commit 2f5fd41
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 2 deletions.
14 changes: 13 additions & 1 deletion packages/core/src/render3/jit/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<any>[] = [
...declarations.map(resolveForwardRef),
...flatten(imports.map(computeCombinedExports)).map(resolveForwardRef),
Expand Down Expand Up @@ -275,6 +276,17 @@ function verifySemanticsOfNgModuleDef(
}
}

function verifyNotStandalone(type: Type<any>, 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<any>) {
type = resolveForwardRef(type);
const kind = getComponentDef(type) && 'component' || getDirectiveDef(type) && 'directive' ||
Expand Down
88 changes: 87 additions & 1 deletion packages/core/test/acceptance/ng_module_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 2f5fd41

Please sign in to comment.