| 
									
										
										
										
											2019-07-18 21:05:32 +01: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
 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | import {makeDiagnostic} from '../../../src/ngtsc/diagnostics'; | 
					
						
							|  |  |  | import {absoluteFrom} from '../../../src/ngtsc/file_system'; | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  | import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | import {ClassDeclaration, Decorator, isNamedClassDeclaration} from '../../../src/ngtsc/reflection'; | 
					
						
							|  |  |  | import {getDeclaration} from '../../../src/ngtsc/testing'; | 
					
						
							|  |  |  | import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, TraitState} from '../../../src/ngtsc/transform'; | 
					
						
							|  |  |  | import {loadTestFiles} from '../../../test/helpers'; | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  | import {DefaultMigrationHost} from '../../src/analysis/migration_host'; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | import {NgccTraitCompiler} from '../../src/analysis/ngcc_trait_compiler'; | 
					
						
							|  |  |  | import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  | import {createComponentDecorator} from '../../src/migrations/utils'; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; | 
					
						
							|  |  |  | import {MockLogger} from '../helpers/mock_logger'; | 
					
						
							|  |  |  | import {makeTestEntryPointBundle} from '../helpers/utils'; | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  | runInEachFileSystem(() => { | 
					
						
							|  |  |  |   describe('DefaultMigrationHost', () => { | 
					
						
							|  |  |  |     let _: typeof absoluteFrom; | 
					
						
							|  |  |  |     let mockMetadata: any = {}; | 
					
						
							|  |  |  |     let mockEvaluator: any = {}; | 
					
						
							|  |  |  |     let mockClazz: any; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     let injectedDecorator: any = {name: 'InjectedDecorator'}; | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       _ = absoluteFrom; | 
					
						
							|  |  |  |       const mockSourceFile: any = { | 
					
						
							|  |  |  |         fileName: _('/node_modules/some-package/entry-point/test-file.js'), | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       mockClazz = { | 
					
						
							|  |  |  |         name: {text: 'MockClazz'}, | 
					
						
							|  |  |  |         getSourceFile: () => mockSourceFile, | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         getStart: () => 0, | 
					
						
							|  |  |  |         getWidth: () => 0, | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     function createMigrationHost({entryPoint, handlers}: { | 
					
						
							|  |  |  |       entryPoint: EntryPointBundle; handlers: DecoratorHandler<unknown, unknown, unknown>[] | 
					
						
							|  |  |  |     }) { | 
					
						
							|  |  |  |       const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, entryPoint.src); | 
					
						
							|  |  |  |       const compiler = new NgccTraitCompiler(handlers, reflectionHost); | 
					
						
							|  |  |  |       const host = new DefaultMigrationHost( | 
					
						
							|  |  |  |           reflectionHost, mockMetadata, mockEvaluator, compiler, entryPoint.entryPoint.path); | 
					
						
							|  |  |  |       return {compiler, host}; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     describe('injectSyntheticDecorator()', () => { | 
					
						
							|  |  |  |       it('should add the injected decorator into the compilation', () => { | 
					
						
							|  |  |  |         const handler = new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); | 
					
						
							|  |  |  |         loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host, compiler} = createMigrationHost({entryPoint, handlers: [handler]}); | 
					
						
							|  |  |  |         host.injectSyntheticDecorator(mockClazz, injectedDecorator); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         const record = compiler.recordFor(mockClazz)!; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         expect(record).toBeDefined(); | 
					
						
							|  |  |  |         expect(record.traits.length).toBe(1); | 
					
						
							|  |  |  |         expect(record.traits[0].detected.decorator).toBe(injectedDecorator); | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |       it('should mention the migration that failed in the diagnostics message', () => { | 
					
						
							|  |  |  |         const handler = new DiagnosticProducingHandler(); | 
					
						
							|  |  |  |         loadTestFiles([{name: _('/node_modules/test/index.js'), contents: ``}]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host, compiler} = createMigrationHost({entryPoint, handlers: [handler]}); | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  |         const decorator = createComponentDecorator(mockClazz, {selector: 'comp', exportAs: null}); | 
					
						
							|  |  |  |         host.injectSyntheticDecorator(mockClazz, decorator); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         const record = compiler.recordFor(mockClazz)!; | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         const migratedTrait = record.traits[0]; | 
					
						
							|  |  |  |         if (migratedTrait.state !== TraitState.ERRORED) { | 
					
						
							|  |  |  |           return fail('Expected migrated class trait to be in an error state'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(migratedTrait.diagnostics.length).toBe(1); | 
					
						
							|  |  |  |         expect(ts.flattenDiagnosticMessageText(migratedTrait.diagnostics[0].messageText, '\n')) | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  |             .toEqual( | 
					
						
							|  |  |  |                 `test diagnostic\n` + | 
					
						
							|  |  |  |                 `  Occurs for @Component decorator inserted by an automatic migration\n` + | 
					
						
							|  |  |  |                 `  @Component({ template: "", selector: "comp" })`); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     describe('getAllDecorators', () => { | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       it('should include injected decorators', () => { | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         const directiveHandler = new DetectDecoratorHandler('Directive', HandlerPrecedence.WEAK); | 
					
						
							|  |  |  |         const injectedHandler = | 
					
						
							|  |  |  |             new DetectDecoratorHandler('InjectedDecorator', HandlerPrecedence.WEAK); | 
					
						
							|  |  |  |         loadTestFiles([{ | 
					
						
							|  |  |  |           name: _('/node_modules/test/index.js'), | 
					
						
							|  |  |  |           contents: `
 | 
					
						
							|  |  |  |             import {Directive} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             export class MyClass {}; | 
					
						
							|  |  |  |             MyClass.decorators = [{ type: Directive }]; | 
					
						
							|  |  |  |           `
 | 
					
						
							|  |  |  |         }]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host, compiler} = | 
					
						
							|  |  |  |             createMigrationHost({entryPoint, handlers: [directiveHandler, injectedHandler]}); | 
					
						
							|  |  |  |         const myClass = getDeclaration( | 
					
						
							|  |  |  |             entryPoint.src.program, _('/node_modules/test/index.js'), 'MyClass', | 
					
						
							|  |  |  |             isNamedClassDeclaration); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         compiler.analyzeFile(entryPoint.src.file); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         host.injectSyntheticDecorator(myClass, injectedDecorator); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |         const decorators = host.getAllDecorators(myClass)!; | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |         expect(decorators.length).toBe(2); | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         expect(decorators[0].name).toBe('Directive'); | 
					
						
							|  |  |  |         expect(decorators[1].name).toBe('InjectedDecorator'); | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |     describe('isInScope', () => { | 
					
						
							|  |  |  |       it('should be true for nodes within the entry-point', () => { | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         loadTestFiles([ | 
					
						
							|  |  |  |           {name: _('/node_modules/test/index.js'), contents: `export * from './internal';`}, | 
					
						
							|  |  |  |           {name: _('/node_modules/test/internal.js'), contents: `export class InternalClass {}`}, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host} = createMigrationHost({entryPoint, handlers: []}); | 
					
						
							|  |  |  |         const internalClass = getDeclaration( | 
					
						
							|  |  |  |             entryPoint.src.program, _('/node_modules/test/internal.js'), 'InternalClass', | 
					
						
							|  |  |  |             isNamedClassDeclaration); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(host.isInScope(internalClass)).toBe(true); | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 15:13:20 +03:00
										 |  |  |       it('should be false for nodes outside the entry-point (in sibling package)', () => { | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |         loadTestFiles([ | 
					
						
							|  |  |  |           {name: _('/node_modules/external/index.js'), contents: `export class ExternalClass {}`}, | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: _('/node_modules/test/index.js'), | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |               export {ExternalClass} from 'external'; | 
					
						
							|  |  |  |               export class InternalClass {} | 
					
						
							|  |  |  |             `
 | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host} = createMigrationHost({entryPoint, handlers: []}); | 
					
						
							|  |  |  |         const externalClass = getDeclaration( | 
					
						
							|  |  |  |             entryPoint.src.program, _('/node_modules/external/index.js'), 'ExternalClass', | 
					
						
							|  |  |  |             isNamedClassDeclaration); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(host.isInScope(externalClass)).toBe(false); | 
					
						
							| 
									
										
										
										
											2019-10-20 20:40:48 +02:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-04-10 15:13:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it('should be false for nodes outside the entry-point (in nested `node_modules/`)', () => { | 
					
						
							|  |  |  |         loadTestFiles([ | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: _('/node_modules/test/index.js'), | 
					
						
							|  |  |  |             contents: `
 | 
					
						
							|  |  |  |               export {NestedDependencyClass} from 'nested'; | 
					
						
							|  |  |  |               export class InternalClass {} | 
					
						
							|  |  |  |             `,
 | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             name: _('/node_modules/test/node_modules/nested/index.js'), | 
					
						
							|  |  |  |             contents: `export class NestedDependencyClass {}`, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |         const entryPoint = | 
					
						
							|  |  |  |             makeTestEntryPointBundle('test', 'esm2015', false, [_('/node_modules/test/index.js')]); | 
					
						
							|  |  |  |         const {host} = createMigrationHost({entryPoint, handlers: []}); | 
					
						
							|  |  |  |         const nestedDepClass = getDeclaration( | 
					
						
							|  |  |  |             entryPoint.src.program, _('/node_modules/test/node_modules/nested/index.js'), | 
					
						
							|  |  |  |             'NestedDependencyClass', isNamedClassDeclaration); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(host.isInScope(nestedDepClass)).toBe(false); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | class DetectDecoratorHandler implements DecoratorHandler<unknown, unknown, unknown> { | 
					
						
							|  |  |  |   readonly name = DetectDecoratorHandler.name; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(private decorator: string, readonly precedence: HandlerPrecedence) {} | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 15:22:59 -08:00
										 |  |  |   detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined { | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     if (decorators === null) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const decorator = decorators.find(decorator => decorator.name === this.decorator); | 
					
						
							|  |  |  |     if (decorator === undefined) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return {trigger: node, decorator, metadata: {}}; | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |   analyze(node: ClassDeclaration): AnalysisOutput<unknown> { | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |   compile(node: ClassDeclaration): CompileResult|CompileResult[] { | 
					
						
							|  |  |  |     return []; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | class DiagnosticProducingHandler implements DecoratorHandler<unknown, unknown, unknown> { | 
					
						
							|  |  |  |   readonly name = DiagnosticProducingHandler.name; | 
					
						
							|  |  |  |   readonly precedence = HandlerPrecedence.PRIMARY; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 15:22:59 -08:00
										 |  |  |   detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined { | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  |     const decorator = decorators !== null ? decorators[0] : null; | 
					
						
							|  |  |  |     return {trigger: node, decorator, metadata: {}}; | 
					
						
							| 
									
										
										
										
											2019-07-18 21:05:32 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   analyze(node: ClassDeclaration): AnalysisOutput<any> { | 
					
						
							|  |  |  |     return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]}; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-01-06 23:12:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-06 08:30:08 +01:00
										 |  |  |   compile(node: ClassDeclaration): CompileResult|CompileResult[] { | 
					
						
							|  |  |  |     return []; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-11-23 18:54:07 +01:00
										 |  |  | } |