fix(compiler): throw descriptive error meesage for invalid NgModule providers (#10947)

Fixes #10714
This commit is contained in:
Pawel Kozlowski 2016-08-24 01:18:41 +02:00 committed by Kara
parent 5c93a8800a
commit aa5c8ca61f
3 changed files with 54 additions and 33 deletions

View File

@ -146,8 +146,8 @@ export class CompileMetadataResolver {
changeDetectionStrategy = cmpMeta.changeDetection; changeDetectionStrategy = cmpMeta.changeDetection;
if (isPresent(dirMeta.viewProviders)) { if (isPresent(dirMeta.viewProviders)) {
viewProviders = this.getProvidersMetadata( viewProviders = this.getProvidersMetadata(
verifyNonBlankProviders(directiveType, dirMeta.viewProviders, 'viewProviders'), dirMeta.viewProviders, entryComponentMetadata,
entryComponentMetadata); `viewProviders for "${stringify(directiveType)}"`);
} }
moduleUrl = componentModuleUrl(this._reflector, directiveType, cmpMeta); moduleUrl = componentModuleUrl(this._reflector, directiveType, cmpMeta);
if (cmpMeta.entryComponents) { if (cmpMeta.entryComponents) {
@ -169,8 +169,8 @@ export class CompileMetadataResolver {
var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; var providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata( providers = this.getProvidersMetadata(
verifyNonBlankProviders(directiveType, dirMeta.providers, 'providers'), dirMeta.providers, entryComponentMetadata,
entryComponentMetadata); `providers for "${stringify(directiveType)}"`);
} }
var queries: cpl.CompileQueryMetadata[] = []; var queries: cpl.CompileQueryMetadata[] = [];
var viewQueries: cpl.CompileQueryMetadata[] = []; var viewQueries: cpl.CompileQueryMetadata[] = [];
@ -227,8 +227,9 @@ export class CompileMetadataResolver {
const moduleWithProviders: ModuleWithProviders = importedType; const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule; importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) { if (moduleWithProviders.providers) {
providers.push( providers.push(...this.getProvidersMetadata(
...this.getProvidersMetadata(moduleWithProviders.providers, entryComponents)); moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringify(importedModuleType)}'`));
} }
} }
if (importedModuleType) { if (importedModuleType) {
@ -295,7 +296,9 @@ export class CompileMetadataResolver {
// The providers of the module have to go last // The providers of the module have to go last
// so that they overwrite any other provider we already added. // so that they overwrite any other provider we already added.
if (meta.providers) { if (meta.providers) {
providers.push(...this.getProvidersMetadata(meta.providers, entryComponents)); providers.push(...this.getProvidersMetadata(
meta.providers, entryComponents,
`provider for the NgModule '${stringify(moduleType)}'`));
} }
if (meta.entryComponents) { if (meta.entryComponents) {
entryComponents.push( entryComponents.push(
@ -550,17 +553,18 @@ export class CompileMetadataResolver {
return compileToken; return compileToken;
} }
getProvidersMetadata(providers: Provider[], targetEntryComponents: cpl.CompileTypeMetadata[]): getProvidersMetadata(
Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> { providers: Provider[], targetEntryComponents: cpl.CompileTypeMetadata[],
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
providers.forEach((provider: any) => { providers.forEach((provider: any, providerIdx: number) => {
provider = resolveForwardRef(provider); provider = resolveForwardRef(provider);
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) { if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
provider = new cpl.ProviderMeta(provider.provide, provider); provider = new cpl.ProviderMeta(provider.provide, provider);
} }
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]; let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
if (isArray(provider)) { if (isArray(provider)) {
compileProvider = this.getProvidersMetadata(provider, targetEntryComponents); compileProvider = this.getProvidersMetadata(provider, targetEntryComponents, debugInfo);
} else if (provider instanceof cpl.ProviderMeta) { } else if (provider instanceof cpl.ProviderMeta) {
let tokenMeta = this.getTokenMetadata(provider.token); let tokenMeta = this.getTokenMetadata(provider.token);
if (tokenMeta.equalsTo(identifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS))) { if (tokenMeta.equalsTo(identifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS))) {
@ -571,8 +575,22 @@ export class CompileMetadataResolver {
} else if (isValidType(provider)) { } else if (isValidType(provider)) {
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else { } else {
let providersInfo = (<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringify(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringify(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
return soFar;
},
[]))
.join(', ');
throw new BaseException( throw new BaseException(
`Invalid provider - only instances of Provider and Type are allowed, got: ${stringify(provider)}`); `Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
} }
if (compileProvider) { if (compileProvider) {
compileProviders.push(compileProvider); compileProviders.push(compileProvider);
@ -696,23 +714,6 @@ function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
return out; return out;
} }
function verifyNonBlankProviders(
directiveType: Type<any>, providersTree: any[], providersType: string): any[] {
var flat: any[] = [];
var errMsg: string;
flattenArray(providersTree, flat);
for (var i = 0; i < flat.length; i++) {
if (isBlank(flat[i])) {
errMsg = flat.map(provider => isBlank(provider) ? '?' : stringify(provider)).join(', ');
throw new BaseException(
`One or more of ${providersType} for "${stringify(directiveType)}" were not defined: [${errMsg}].`);
}
}
return providersTree;
}
function isValidType(value: any): boolean { function isValidType(value: any): boolean {
return cpl.isStaticSymbol(value) || (value instanceof Type); return cpl.isStaticSymbol(value) || (value instanceof Type);
} }

View File

@ -130,14 +130,14 @@ export function main() {
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp3)) expect(() => resolver.getDirectiveMetadata(MyBrokenComp3))
.toThrowError( .toThrowError(
`One or more of providers for "MyBrokenComp3" were not defined: [?, SimpleService, ?].`); `Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
})); }));
it('should throw with descriptive error message when one of viewProviders is not present', it('should throw with descriptive error message when one of viewProviders is not present',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp4)) expect(() => resolver.getDirectiveMetadata(MyBrokenComp4))
.toThrowError( .toThrowError(
`One or more of viewProviders for "MyBrokenComp4" were not defined: [?, SimpleService, ?].`); `Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
})); }));
it('should throw an error when the interpolation config has invalid symbols', it('should throw an error when the interpolation config has invalid symbols',
@ -220,7 +220,7 @@ class MyBrokenComp2 {
class SimpleService { class SimpleService {
} }
@Component({selector: 'my-broken-comp', template: '', providers: [null, SimpleService, [null]]}) @Component({selector: 'my-broken-comp', template: '', providers: [SimpleService, null, [null]]})
class MyBrokenComp3 { class MyBrokenComp3 {
} }

View File

@ -732,7 +732,13 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should throw when given invalid providers', () => { it('should throw when given invalid providers', () => {
expect(() => createInjector(<any>['blah'])) expect(() => createInjector(<any>['blah']))
.toThrowError( .toThrowError(
'Invalid provider - only instances of Provider and Type are allowed, got: blah'); `Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?blah?]`);
});
it('should throw when given blank providers', () => {
expect(() => createInjector(<any>[null, {provide: 'token', useValue: 'value'}]))
.toThrowError(
`Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?null?, ...]`);
}); });
it('should provide itself', () => { it('should provide itself', () => {
@ -1104,6 +1110,20 @@ function declareTests({useJit}: {useJit: boolean}) {
const injector = createModule(SomeModule).injector; const injector = createModule(SomeModule).injector;
expect(injector.get('token1')).toBe('imported2'); expect(injector.get('token1')).toBe('imported2');
}); });
it('should throw when given invalid providers in an imported ModuleWithProviders', () => {
@NgModule()
class ImportedModule1 {
}
@NgModule({imports: [{ngModule: ImportedModule1, providers: [<any>'broken']}]})
class SomeModule {
}
expect(() => createModule(SomeModule).injector)
.toThrowError(
`Invalid provider for the NgModule 'ImportedModule1' - only instances of Provider and Type are allowed, got: [?broken?]`);
});
}); });
}); });
}); });