180 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			180 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @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 {ErrorCode} from '../../../src/ngtsc/diagnostics';
							 | 
						||
| 
								 | 
							
								import {ClassDeclaration, ClassSymbol, Decorator} from '../../../src/ngtsc/reflection';
							 | 
						||
| 
								 | 
							
								import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
							 | 
						||
| 
								 | 
							
								import {DefaultMigrationHost} from '../../src/analysis/migration_host';
							 | 
						||
| 
								 | 
							
								import {AnalyzedClass, AnalyzedFile} from '../../src/analysis/types';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								describe('DefaultMigrationHost', () => {
							 | 
						||
| 
								 | 
							
								  describe('injectSyntheticDecorator()', () => {
							 | 
						||
| 
								 | 
							
								    const mockHost: any = {
							 | 
						||
| 
								 | 
							
								      getClassSymbol: (node: any): ClassSymbol | undefined =>
							 | 
						||
| 
								 | 
							
								                          ({ valueDeclaration: node, name: node.name.text } as any),
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    const mockMetadata: any = {};
							 | 
						||
| 
								 | 
							
								    const mockEvaluator: any = {};
							 | 
						||
| 
								 | 
							
								    const mockClazz: any = {
							 | 
						||
| 
								 | 
							
								      name: {text: 'MockClazz'},
							 | 
						||
| 
								 | 
							
								      getSourceFile: () => { fileName: 'test-file.js'; },
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    const mockDecorator: any = {name: 'MockDecorator'};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should call `detect()` on each of the provided handlers', () => {
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      const handler1 = new TestHandler('handler1', log);
							 | 
						||
| 
								 | 
							
								      const handler2 = new TestHandler('handler2', log);
							 | 
						||
| 
								 | 
							
								      const host =
							 | 
						||
| 
								 | 
							
								          new DefaultMigrationHost(mockHost, mockMetadata, mockEvaluator, [handler1, handler2], []);
							 | 
						||
| 
								 | 
							
								      host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual([
							 | 
						||
| 
								 | 
							
								        `handler1:detect:MockClazz:MockDecorator`,
							 | 
						||
| 
								 | 
							
								        `handler2:detect:MockClazz:MockDecorator`,
							 | 
						||
| 
								 | 
							
								      ]);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should call `analyze()` on each of the provided handlers whose `detect()` call returns a result',
							 | 
						||
| 
								 | 
							
								       () => {
							 | 
						||
| 
								 | 
							
								         const log: string[] = [];
							 | 
						||
| 
								 | 
							
								         const handler1 = new TestHandler('handler1', log);
							 | 
						||
| 
								 | 
							
								         const handler2 = new AlwaysDetectHandler('handler2', log);
							 | 
						||
| 
								 | 
							
								         const handler3 = new TestHandler('handler3', log);
							 | 
						||
| 
								 | 
							
								         const host = new DefaultMigrationHost(
							 | 
						||
| 
								 | 
							
								             mockHost, mockMetadata, mockEvaluator, [handler1, handler2, handler3], []);
							 | 
						||
| 
								 | 
							
								         host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								         expect(log).toEqual([
							 | 
						||
| 
								 | 
							
								           `handler1:detect:MockClazz:MockDecorator`,
							 | 
						||
| 
								 | 
							
								           `handler2:detect:MockClazz:MockDecorator`,
							 | 
						||
| 
								 | 
							
								           `handler3:detect:MockClazz:MockDecorator`,
							 | 
						||
| 
								 | 
							
								           'handler2:analyze:MockClazz',
							 | 
						||
| 
								 | 
							
								         ]);
							 | 
						||
| 
								 | 
							
								       });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should add a newly `AnalyzedFile` to the `analyzedFiles` object', () => {
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      const handler = new AlwaysDetectHandler('handler', log);
							 | 
						||
| 
								 | 
							
								      const analyzedFiles: AnalyzedFile[] = [];
							 | 
						||
| 
								 | 
							
								      const host =
							 | 
						||
| 
								 | 
							
								          new DefaultMigrationHost(mockHost, mockMetadata, mockEvaluator, [handler], analyzedFiles);
							 | 
						||
| 
								 | 
							
								      host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses[0].name).toEqual('MockClazz');
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should add a newly `AnalyzedClass` to an existing `AnalyzedFile` object', () => {
							 | 
						||
| 
								 | 
							
								      const DUMMY_CLASS_1: any = {};
							 | 
						||
| 
								 | 
							
								      const DUMMY_CLASS_2: any = {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      const handler = new AlwaysDetectHandler('handler', log);
							 | 
						||
| 
								 | 
							
								      const analyzedFiles: AnalyzedFile[] = [{
							 | 
						||
| 
								 | 
							
								        sourceFile: mockClazz.getSourceFile(),
							 | 
						||
| 
								 | 
							
								        analyzedClasses: [DUMMY_CLASS_1, DUMMY_CLASS_2],
							 | 
						||
| 
								 | 
							
								      }];
							 | 
						||
| 
								 | 
							
								      const host =
							 | 
						||
| 
								 | 
							
								          new DefaultMigrationHost(mockHost, mockMetadata, mockEvaluator, [handler], analyzedFiles);
							 | 
						||
| 
								 | 
							
								      host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses.length).toEqual(3);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses[2].name).toEqual('MockClazz');
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should add a new decorator into an already existing `AnalyzedClass`', () => {
							 | 
						||
| 
								 | 
							
								      const analyzedClass: AnalyzedClass = {
							 | 
						||
| 
								 | 
							
								        name: 'MockClazz',
							 | 
						||
| 
								 | 
							
								        declaration: mockClazz,
							 | 
						||
| 
								 | 
							
								        matches: [],
							 | 
						||
| 
								 | 
							
								        decorators: null,
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      const handler = new AlwaysDetectHandler('handler', log);
							 | 
						||
| 
								 | 
							
								      const analyzedFiles: AnalyzedFile[] = [{
							 | 
						||
| 
								 | 
							
								        sourceFile: mockClazz.getSourceFile(),
							 | 
						||
| 
								 | 
							
								        analyzedClasses: [analyzedClass],
							 | 
						||
| 
								 | 
							
								      }];
							 | 
						||
| 
								 | 
							
								      const host =
							 | 
						||
| 
								 | 
							
								          new DefaultMigrationHost(mockHost, mockMetadata, mockEvaluator, [handler], analyzedFiles);
							 | 
						||
| 
								 | 
							
								      host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedFiles[0].analyzedClasses[0]).toBe(analyzedClass);
							 | 
						||
| 
								 | 
							
								      expect(analyzedClass.decorators !.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								      expect(analyzedClass.decorators ![0].name).toEqual('MockDecorator');
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should merge a new decorator into pre-existing decorators an already existing `AnalyzedClass`',
							 | 
						||
| 
								 | 
							
								       () => {
							 | 
						||
| 
								 | 
							
								         const analyzedClass: AnalyzedClass = {
							 | 
						||
| 
								 | 
							
								           name: 'MockClazz',
							 | 
						||
| 
								 | 
							
								           declaration: mockClazz,
							 | 
						||
| 
								 | 
							
								           matches: [],
							 | 
						||
| 
								 | 
							
								           decorators: [{name: 'OtherDecorator'} as Decorator],
							 | 
						||
| 
								 | 
							
								         };
							 | 
						||
| 
								 | 
							
								         const log: string[] = [];
							 | 
						||
| 
								 | 
							
								         const handler = new AlwaysDetectHandler('handler', log);
							 | 
						||
| 
								 | 
							
								         const analyzedFiles: AnalyzedFile[] = [{
							 | 
						||
| 
								 | 
							
								           sourceFile: mockClazz.getSourceFile(),
							 | 
						||
| 
								 | 
							
								           analyzedClasses: [analyzedClass],
							 | 
						||
| 
								 | 
							
								         }];
							 | 
						||
| 
								 | 
							
								         const host = new DefaultMigrationHost(
							 | 
						||
| 
								 | 
							
								             mockHost, mockMetadata, mockEvaluator, [handler], analyzedFiles);
							 | 
						||
| 
								 | 
							
								         host.injectSyntheticDecorator(mockClazz, mockDecorator);
							 | 
						||
| 
								 | 
							
								         expect(analyzedFiles.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								         expect(analyzedFiles[0].analyzedClasses.length).toEqual(1);
							 | 
						||
| 
								 | 
							
								         expect(analyzedFiles[0].analyzedClasses[0]).toBe(analyzedClass);
							 | 
						||
| 
								 | 
							
								         expect(analyzedClass.decorators !.length).toEqual(2);
							 | 
						||
| 
								 | 
							
								         expect(analyzedClass.decorators ![1].name).toEqual('MockDecorator');
							 | 
						||
| 
								 | 
							
								       });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should throw an error if the injected decorator already exists', () => {
							 | 
						||
| 
								 | 
							
								      const analyzedClass: AnalyzedClass = {
							 | 
						||
| 
								 | 
							
								        name: 'MockClazz',
							 | 
						||
| 
								 | 
							
								        declaration: mockClazz,
							 | 
						||
| 
								 | 
							
								        matches: [],
							 | 
						||
| 
								 | 
							
								        decorators: [{name: 'MockDecorator'} as Decorator],
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      const handler = new AlwaysDetectHandler('handler', log);
							 | 
						||
| 
								 | 
							
								      const analyzedFiles: AnalyzedFile[] = [{
							 | 
						||
| 
								 | 
							
								        sourceFile: mockClazz.getSourceFile(),
							 | 
						||
| 
								 | 
							
								        analyzedClasses: [analyzedClass],
							 | 
						||
| 
								 | 
							
								      }];
							 | 
						||
| 
								 | 
							
								      const host =
							 | 
						||
| 
								 | 
							
								          new DefaultMigrationHost(mockHost, mockMetadata, mockEvaluator, [handler], analyzedFiles);
							 | 
						||
| 
								 | 
							
								      expect(() => host.injectSyntheticDecorator(mockClazz, mockDecorator))
							 | 
						||
| 
								 | 
							
								          .toThrow(
							 | 
						||
| 
								 | 
							
								              jasmine.objectContaining({code: ErrorCode.NGCC_MIGRATION_DECORATOR_INJECTION_ERROR}));
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestHandler implements DecoratorHandler<any, any> {
							 | 
						||
| 
								 | 
							
								  constructor(protected name: string, protected log: string[]) {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  precedence = HandlerPrecedence.PRIMARY;
							 | 
						||
| 
								 | 
							
								  detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<any>|undefined {
							 | 
						||
| 
								 | 
							
								    this.log.push(`${this.name}:detect:${node.name.text}:${decorators !.map(d => d.name)}`);
							 | 
						||
| 
								 | 
							
								    return undefined;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  analyze(node: ClassDeclaration): AnalysisOutput<any> {
							 | 
						||
| 
								 | 
							
								    this.log.push(this.name + ':analyze:' + node.name.text);
							 | 
						||
| 
								 | 
							
								    return {};
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  compile(node: ClassDeclaration): CompileResult|CompileResult[] {
							 | 
						||
| 
								 | 
							
								    this.log.push(this.name + ':compile:' + node.name.text);
							 | 
						||
| 
								 | 
							
								    return [];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AlwaysDetectHandler extends TestHandler {
							 | 
						||
| 
								 | 
							
								  detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<any>|undefined {
							 | 
						||
| 
								 | 
							
								    super.detect(node, decorators);
							 | 
						||
| 
								 | 
							
								    return {trigger: node, metadata: {}};
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |