| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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 * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {AbsoluteFsPath, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system'; | 
					
						
							|  |  |  | import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; | 
					
						
							|  |  |  | import {loadFakeCore, loadTestFiles} from '../../../test/helpers'; | 
					
						
							|  |  |  | import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; | 
					
						
							|  |  |  | import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; | 
					
						
							|  |  |  | import {DecorationAnalyses} from '../../src/analysis/types'; | 
					
						
							|  |  |  | import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; | 
					
						
							|  |  |  | import {MissingInjectableMigration, getAngularCoreDecoratorName} from '../../src/migrations/missing_injectable_migration'; | 
					
						
							|  |  |  | import {MockLogger} from '../helpers/mock_logger'; | 
					
						
							|  |  |  | import {getRootFiles, makeTestEntryPointBundle} from '../helpers/utils'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | runInEachFileSystem(() => { | 
					
						
							|  |  |  |   describe('MissingInjectableMigration', () => { | 
					
						
							|  |  |  |     let _: typeof absoluteFrom; | 
					
						
							|  |  |  |     let INDEX_FILENAME: AbsoluteFsPath; | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       _ = absoluteFrom; | 
					
						
							|  |  |  |       INDEX_FILENAME = _('/node_modules/test-package/index.js'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('NgModule', () => runTests('NgModule', 'providers')); | 
					
						
							|  |  |  |     describe('Directive', () => runTests('Directive', 'providers')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('Component', () => { | 
					
						
							|  |  |  |       runTests('Component', 'providers'); | 
					
						
							|  |  |  |       runTests('Component', 'viewProviders'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should migrate all providers defined in "viewProviders" and "providers" in the same ' + | 
					
						
							|  |  |  |              'component', | 
					
						
							|  |  |  |          () => { | 
					
						
							|  |  |  |            const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |              name: INDEX_FILENAME, | 
					
						
							|  |  |  |              contents: `
 | 
					
						
							|  |  |  |             import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  |             export class ServiceB {} | 
					
						
							|  |  |  |             export class ServiceC {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: Component, args: [{ | 
					
						
							|  |  |  |                   template: "", | 
					
						
							|  |  |  |                   providers: [ServiceA], | 
					
						
							|  |  |  |                   viewProviders: [ServiceB], | 
					
						
							|  |  |  |                 }] | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |            }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |            expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); | 
					
						
							|  |  |  |            expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); | 
					
						
							|  |  |  |            expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(false); | 
					
						
							|  |  |  |          }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function runTests( | 
					
						
							|  |  |  |         type: 'NgModule' | 'Directive' | 'Component', propName: 'providers' | 'viewProviders') { | 
					
						
							|  |  |  |       const args = type === 'Component' ? 'template: "", ' : ''; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate type provider in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             export class OtherService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'OtherService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate object literal provider in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             export class OtherService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [{provide: MyService}]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'OtherService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate object literal provider with forwardRef in ${type}`, async() => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}, forwardRef} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [{provide: forwardRef(() => MyService) }]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should not migrate object literal provider with "useValue" in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [{provide: MyService, useValue: null }]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should not migrate object literal provider with "useFactory" in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [{provide: MyService, useFactory: () => null }]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should not migrate object literal provider with "useExisting" in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             export class MyToken {} | 
					
						
							|  |  |  |             export class MyTokenAlias {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [ | 
					
						
							|  |  |  |                 MyService, | 
					
						
							|  |  |  |                 {provide: MyToken, useExisting: MyService}, | 
					
						
							|  |  |  |                 {provide: MyTokenAlias, useExisting: MyToken}, | 
					
						
							|  |  |  |               ]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyToken')).toBe(false); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyTokenAlias')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate object literal provider with "useClass" in ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             export class MyToken {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [{provide: MyToken, useClass: MyService}]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyToken')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should not migrate provider which is already decorated with @Injectable', () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {Injectable, ${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             MyService.decorators = [ | 
					
						
							|  |  |  |               { type: Injectable } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(getInjectableDecorators(index, analysis, 'MyService').length).toBe(1); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should not migrate provider which is already decorated with @Directive', () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {Directive, ${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             MyService.decorators = [ | 
					
						
							|  |  |  |               { type: Directive } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should not migrate provider which is already decorated with @Component', () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {Component, ${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             MyService.decorators = [ | 
					
						
							|  |  |  |               { type: Component, args: [{template: ""}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should not migrate provider which is already decorated with @Pipe', () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {Pipe, ${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  |             MyService.decorators = [ | 
					
						
							|  |  |  |               { type: Pipe, args: [{name: "pipe"}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate multiple providers in same ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  |             export class ServiceB {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [ServiceA, ServiceB]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate multiple mixed providers in same ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  |             export class ServiceB {} | 
					
						
							|  |  |  |             export class ServiceC {} | 
					
						
							|  |  |  |             export class ServiceD {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [ | 
					
						
							|  |  |  |                   ServiceA, | 
					
						
							|  |  |  |                   {provide: ServiceB}, | 
					
						
							|  |  |  |                   {provide: SomeToken, useClass: ServiceC}, | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |               }] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceD')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate multiple nested providers in same ${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |           import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           export class ServiceA {} | 
					
						
							|  |  |  |           export class ServiceB {} | 
					
						
							|  |  |  |           export class ServiceC {} | 
					
						
							|  |  |  |           export class ServiceD {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           export class TestClass {} | 
					
						
							|  |  |  |           TestClass.decorators = [ | 
					
						
							|  |  |  |             { type: ${type}, args: [{${args}${propName}: [ | 
					
						
							|  |  |  |                 ServiceA, | 
					
						
							|  |  |  |                 [ | 
					
						
							|  |  |  |                   {provide: ServiceB}, | 
					
						
							|  |  |  |                   ServiceC, | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ]}] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           ]; | 
					
						
							|  |  |  |          `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceD')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should migrate providers referenced indirectly', () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  |             export class ServiceB {} | 
					
						
							|  |  |  |             export class ServiceC {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const PROVIDERS = [ServiceA, ServiceB]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: PROVIDERS}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceB')).toBe(true); | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceC')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate provider once if referenced in multiple ${type} definitions`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  |             export class ServiceB {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClassA {} | 
					
						
							|  |  |  |             TestClassA.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [ServiceA]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClassB {} | 
					
						
							|  |  |  |             TestClassB.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [ServiceA, ServiceB]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(getInjectableDecorators(index, analysis, 'ServiceA').length).toBe(1); | 
					
						
							|  |  |  |         expect(getInjectableDecorators(index, analysis, 'ServiceB').length).toBe(1); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       type !== 'Component' && it(`should support @${type} without metadata argument`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class ServiceA {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'ServiceA')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should migrate services in a different file`, () => { | 
					
						
							|  |  |  |         const SERVICE_FILENAME = _('/node_modules/test-package/service.js'); | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: INDEX_FILENAME, | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  |             import {MyService} from './service'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: SERVICE_FILENAME, | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |             export declare class MyService {} | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(SERVICE_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should not migrate services in a different package`, () => { | 
					
						
							|  |  |  |         const SERVICE_FILENAME = _('/node_modules/external/index.d.ts'); | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: INDEX_FILENAME, | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |             import {${type}} from '@angular/core'; | 
					
						
							|  |  |  |             import {MyService} from 'external'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: SERVICE_FILENAME, | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |             export declare class MyService {} | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(SERVICE_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should deal with renamed imports for @${type}`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type} as Renamed} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: Renamed, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(true); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it(`should deal with decorators named @${type} not from '@angular/core'`, () => { | 
					
						
							|  |  |  |         const {program, analysis} = setUpAndAnalyzeProgram([{ | 
					
						
							|  |  |  |           name: INDEX_FILENAME, | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {${type}} from 'other'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyService {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class TestClass {} | 
					
						
							|  |  |  |             TestClass.decorators = [ | 
					
						
							|  |  |  |               { type: ${type}, args: [{${args}${propName}: [MyService]}] } | 
					
						
							|  |  |  |             ]; | 
					
						
							|  |  |  |           `,
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const index = program.getSourceFile(INDEX_FILENAME) !; | 
					
						
							|  |  |  |         expect(hasInjectableDecorator(index, analysis, 'MyService')).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function setUpAndAnalyzeProgram(testFiles: TestFile[]) { | 
					
						
							|  |  |  |       loadTestFiles(testFiles); | 
					
						
							|  |  |  |       loadFakeCore(getFileSystem()); | 
					
						
							|  |  |  |       const errors: ts.Diagnostic[] = []; | 
					
						
							|  |  |  |       const rootFiles = getRootFiles(testFiles); | 
					
						
							|  |  |  |       const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles); | 
					
						
							|  |  |  |       const program = bundle.src.program; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-18 14:03:04 +00:00
										 |  |  |       const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, bundle.src); | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       const referencesRegistry = new NgccReferencesRegistry(reflectionHost); | 
					
						
							|  |  |  |       const analyzer = new DecorationAnalyzer( | 
					
						
							|  |  |  |           getFileSystem(), bundle, reflectionHost, referencesRegistry, error => errors.push(error)); | 
					
						
							|  |  |  |       analyzer.migrations = [new MissingInjectableMigration()]; | 
					
						
							|  |  |  |       return {program, analysis: analyzer.analyzeProgram(), errors}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function getInjectableDecorators( | 
					
						
							|  |  |  |         sourceFile: ts.SourceFile, analysis: DecorationAnalyses, className: string) { | 
					
						
							|  |  |  |       const file = analysis.get(sourceFile); | 
					
						
							|  |  |  |       if (file === undefined) { | 
					
						
							|  |  |  |         return []; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const clazz = file.compiledClasses.find(c => c.name === className); | 
					
						
							|  |  |  |       if (clazz === undefined || clazz.decorators === null) { | 
					
						
							|  |  |  |         return []; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return clazz.decorators.filter( | 
					
						
							|  |  |  |           decorator => getAngularCoreDecoratorName(decorator) === 'Injectable'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function hasInjectableDecorator( | 
					
						
							|  |  |  |         sourceFile: ts.SourceFile, analysis: DecorationAnalyses, className: string) { | 
					
						
							|  |  |  |       return getInjectableDecorators(sourceFile, analysis, className).length > 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); |