fix(compiler): throw descriptive error meesage for invalid NgModule providers (#10947)
Fixes #10714
This commit is contained in:
		
							parent
							
								
									5c93a8800a
								
							
						
					
					
						commit
						aa5c8ca61f
					
				| @ -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); | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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?]`); | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user