fix(ivy): semantic module check incorrectly handles nested arrays (#30993)
In View Engine, developers can pass bootstrap and entry components as nested arrays. e.g. ```ts export const MyOtherEntryComponents = [A, B, C] @NgModule({ entryComponents: [MyComp, MyOtherEntryComponents] }) ``` Currently using nested arrays for these properties causes unexpected errors to be reported in Ivy since the semantic NgModule checks aren't properly recursing into the nested entry/bootstrap components. This issue has been unveiled by enabling the strict function parameter checks. PR Close #30993
This commit is contained in:
parent
dda781ecce
commit
e061e638cb
|
@ -11,6 +11,7 @@ import '../util/ng_dev_mode';
|
||||||
import {OnDestroy} from '../interface/lifecycle_hooks';
|
import {OnDestroy} from '../interface/lifecycle_hooks';
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors';
|
import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors';
|
||||||
|
import {deepForEach} from '../util/array_utils';
|
||||||
import {stringify} from '../util/stringify';
|
import {stringify} from '../util/stringify';
|
||||||
|
|
||||||
import {resolveForwardRef} from './forward_ref';
|
import {resolveForwardRef} from './forward_ref';
|
||||||
|
@ -497,10 +498,6 @@ function makeRecord<T>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function deepForEach<T>(input: (T | any[])[], fn: (value: T) => void): void {
|
|
||||||
input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValueProvider(value: SingleProvider): value is ValueProvider {
|
function isValueProvider(value: SingleProvider): value is ValueProvider {
|
||||||
return value !== null && typeof value == 'object' && USE_VALUE in value;
|
return value !== null && typeof value == 'object' && USE_VALUE in value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {reflectDependencies} from '../../di/jit/util';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
import {Component} from '../../metadata';
|
import {Component} from '../../metadata';
|
||||||
import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module';
|
import {ModuleWithProviders, NgModule, NgModuleDef, NgModuleTransitiveScopes} from '../../metadata/ng_module';
|
||||||
import {flatten} from '../../util/array_utils';
|
import {deepForEach, flatten} from '../../util/array_utils';
|
||||||
import {assertDefined} from '../../util/assert';
|
import {assertDefined} from '../../util/assert';
|
||||||
import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition';
|
import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition';
|
||||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields';
|
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from '../fields';
|
||||||
|
@ -200,9 +200,10 @@ function verifySemanticsOfNgModuleDef(
|
||||||
verifySemanticsOfNgModuleImport(mod, moduleType);
|
verifySemanticsOfNgModuleImport(mod, moduleType);
|
||||||
verifySemanticsOfNgModuleDef(mod, false, moduleType);
|
verifySemanticsOfNgModuleDef(mod, false, moduleType);
|
||||||
});
|
});
|
||||||
ngModule.bootstrap && ngModule.bootstrap.forEach(verifyCorrectBootstrapType);
|
ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyCorrectBootstrapType);
|
||||||
ngModule.bootstrap && ngModule.bootstrap.forEach(verifyComponentIsPartOfNgModule);
|
ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyComponentIsPartOfNgModule);
|
||||||
ngModule.entryComponents && ngModule.entryComponents.forEach(verifyComponentIsPartOfNgModule);
|
ngModule.entryComponents &&
|
||||||
|
deepForEach(ngModule.entryComponents, verifyComponentIsPartOfNgModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw Error if any errors were detected.
|
// Throw Error if any errors were detected.
|
||||||
|
@ -273,7 +274,7 @@ function verifySemanticsOfNgModuleDef(
|
||||||
// We know we are component
|
// We know we are component
|
||||||
const component = getAnnotation<Component>(type, 'Component');
|
const component = getAnnotation<Component>(type, 'Component');
|
||||||
if (component && component.entryComponents) {
|
if (component && component.entryComponents) {
|
||||||
component.entryComponents.forEach(verifyComponentIsPartOfNgModule);
|
deepForEach(component.entryComponents, verifyComponentIsPartOfNgModule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,4 +38,8 @@ export function flatten(list: any[], dst?: any[]): any[] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deepForEach<T>(input: (T | any[])[], fn: (value: T) => void): void {
|
||||||
|
input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. 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 {Component, NgModule} from '@angular/core';
|
||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
describe('NgModule', () => {
|
||||||
|
@Component({template: 'hello'})
|
||||||
|
class TestCmp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: 'hello'})
|
||||||
|
class TestCmp2 {
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('entryComponents', () => {
|
||||||
|
it('should throw when specified entry component is not added to a module', () => {
|
||||||
|
@NgModule({entryComponents: [TestCmp, [TestCmp2]]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
TestBed.createComponent(TestCmp);
|
||||||
|
TestBed.createComponent(TestCmp2);
|
||||||
|
}).toThrowError(/not part of any NgModule/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when specified entry component is added to a module', () => {
|
||||||
|
@NgModule({declarations: [TestCmp, TestCmp2], entryComponents: [TestCmp, [TestCmp2]]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
TestBed.createComponent(TestCmp);
|
||||||
|
TestBed.createComponent(TestCmp2);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bootstrap', () => {
|
||||||
|
it('should throw when specified bootstrap component is not added to a module', () => {
|
||||||
|
@NgModule({bootstrap: [TestCmp, [TestCmp2]]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
TestBed.createComponent(TestCmp);
|
||||||
|
TestBed.createComponent(TestCmp2);
|
||||||
|
}).toThrowError(/not part of any NgModule/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when specified bootstrap component is added to a module', () => {
|
||||||
|
@NgModule({declarations: [TestCmp, TestCmp2], bootstrap: [TestCmp, [TestCmp2]]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
TestBed.createComponent(TestCmp);
|
||||||
|
TestBed.createComponent(TestCmp2);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue