fix(core): verify standalone component imports in JiT (#45777)

This commits adds verifications assuring that items imported into
standalone components are one of:
- standalone component / directive / pipe;
- NgModule;
- forwardRef resolving to one of the above.
It explicitly disallows modules with providers.

PR Close #45777
This commit is contained in:
Pawel Kozlowski
2022-04-21 11:04:04 +02:00
committed by Dylan Hunn
parent 58e8f4b708
commit aafac7228f
4 changed files with 197 additions and 11 deletions
+46 -2
View File
@@ -8,7 +8,7 @@
import {getCompilerFacade, JitCompilerUsage, R3DirectiveMetadataFacade} from '../../compiler/compiler_facade';
import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface';
import {resolveForwardRef} from '../../di/forward_ref';
import {isForwardRef, resolveForwardRef} from '../../di/forward_ref';
import {getReflect, reflectDependencies} from '../../di/jit/util';
import {Type} from '../../interface/type';
import {Query} from '../../metadata/di';
@@ -26,6 +26,7 @@ import {stringifyForError} from '../util/stringify_utils';
import {angularCoreEnv} from './environment';
import {getJitOptions} from './jit_options';
import {flushModuleScopingQueueAsMuchAsPossible, patchComponentDefWithScope, transitiveScopesFor} from './module';
import {isModuleWithProviders} from './util';
/**
* Keep track of the compilation depth to avoid reentrancy issues during JIT compilation. This
@@ -186,6 +187,48 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
});
}
function getDependencyTypeForError(type: Type<any>) {
if (getComponentDef(type)) return 'component';
if (getDirectiveDef(type)) return 'directive';
if (getPipeDef(type)) return 'pipe';
return 'type';
}
function verifyStandaloneImport(depType: Type<unknown>, importingType: Type<unknown>) {
if (isForwardRef(depType)) {
depType = resolveForwardRef(depType);
if (!depType) {
throw new Error(`Expected forwardRef function, imported from "${
stringifyForError(importingType)}", to return a standalone entity or NgModule but got "${
stringifyForError(depType) || depType}".`);
}
}
if (getNgModuleDef(depType) == null) {
const def = getComponentDef(depType) || getDirectiveDef(depType) || getPipeDef(depType);
if (def != null) {
// if a component, directive or pipe is imported make sure that it is standalone
if (!def.standalone) {
throw new Error(`The "${stringifyForError(depType)}" ${
getDependencyTypeForError(depType)}, imported from "${
stringifyForError(
importingType)}", is not standalone. Did you forget to add the standalone: true flag?`);
}
} else {
// it can be either a module with provider or an unknown (not annotated) type
if (isModuleWithProviders(depType)) {
throw new Error(`A module with providers was imported from "${
stringifyForError(
importingType)}". Modules with providers are not supported in standalone components imports.`);
} else {
throw new Error(`The "${stringifyForError(depType)}" type, imported from "${
stringifyForError(
importingType)}", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?`);
}
}
}
}
/**
* Build memoized `directiveDefs` and `pipeDefs` functions for the component definition of a
* standalone component, which process `imports` and filter out directives and pipes. The use of
@@ -205,8 +248,9 @@ function getStandaloneDefFunctions(type: Type<any>, imports: Type<any>[]): {
cachedDirectiveDefs = [getComponentDef(type)!];
for (const rawDep of imports) {
const dep = resolveForwardRef(rawDep);
ngDevMode && verifyStandaloneImport(rawDep, type);
const dep = resolveForwardRef(rawDep);
if (!!getNgModuleDef(dep)) {
const scope = transitiveScopesFor(dep);
for (const dir of scope.exported.directives) {
+1 -8
View File
@@ -27,6 +27,7 @@ import {stringifyForError} from '../util/stringify_utils';
import {angularCoreEnv} from './environment';
import {patchModuleCompilation} from './module_patch';
import {isModuleWithProviders, isNgModule} from './util';
interface ModuleQueueItem {
moduleType: Type<any>;
@@ -610,11 +611,3 @@ function expandModuleWithProviders(value: Type<any>|ModuleWithProviders<{}>): Ty
}
return value;
}
function isModuleWithProviders(value: any): value is ModuleWithProviders<{}> {
return (value as {ngModule?: any}).ngModule !== undefined;
}
function isNgModule<T>(value: Type<T>): value is Type<T>&{ɵmod: NgModuleDef<T>} {
return !!getNgModuleDef(value);
}
+20
View File
@@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ModuleWithProviders} from '../../di/interface/provider';
import {Type} from '../../interface/type';
import {NgModuleDef} from '../../metadata/ng_module_def';
import {getNgModuleDef} from '../definition';
export function isModuleWithProviders(value: any): value is ModuleWithProviders<{}> {
return (value as {ngModule?: any}).ngModule !== undefined;
}
export function isNgModule<T>(value: Type<T>): value is Type<T>&{ɵmod: NgModuleDef<T>} {
return !!getNgModuleDef(value);
}