| 
									
										
										
										
											2017-11-20 10:21:17 -08: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 {PartialModule} from '@angular/compiler'; | 
					
						
							|  |  |  | import * as o from '@angular/compiler/src/output/output_ast'; | 
					
						
							|  |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {getAngularClassTransformerFactory} from '../../src/transformers/r3_transform'; | 
					
						
							|  |  |  | import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const someGenFilePath = '/somePackage/someGenFile'; | 
					
						
							|  |  |  | const someGenFileName = someGenFilePath + '.ts'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('r3_transform_spec', () => { | 
					
						
							|  |  |  |   let context: MockAotContext; | 
					
						
							|  |  |  |   let host: MockCompilerHost; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     context = new MockAotContext('/', FILES); | 
					
						
							|  |  |  |     host = new MockCompilerHost(context); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to generate a simple identity function', () => { | 
					
						
							|  |  |  |     expect(emitStaticMethod(new o.ReturnStatement(o.variable('v')), ['v'])) | 
					
						
							|  |  |  |         .toContain('static someMethod(v) { return v; }'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  |   it('should be able to generate a static field declaration', () => { | 
					
						
							|  |  |  |     expect(emitStaticField(o.literal(10))).toContain('SomeClass.someField = 10'); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to import a symbol', () => { | 
					
						
							|  |  |  |     expect(emitStaticMethod(new o.ReturnStatement( | 
					
						
							|  |  |  |                o.importExpr(new o.ExternalReference('@angular/core', 'Component'))))) | 
					
						
							|  |  |  |         .toContain('static someMethod() { return i0.Component; } }'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to modify multiple classes in the same module', () => { | 
					
						
							| 
									
										
										
										
											2020-03-07 17:14:25 +01:00
										 |  |  |     const result = emit(getAngularClassTransformerFactory( | 
					
						
							|  |  |  |         [{ | 
					
						
							|  |  |  |           fileName: someGenFileName, | 
					
						
							|  |  |  |           statements: [ | 
					
						
							|  |  |  |             classMethod(new o.ReturnStatement(o.variable('v')), ['v'], 'someMethod', 'SomeClass'), | 
					
						
							|  |  |  |             classMethod( | 
					
						
							|  |  |  |                 new o.ReturnStatement(o.variable('v')), ['v'], 'someOtherMethod', 'SomeOtherClass') | 
					
						
							|  |  |  |           ] | 
					
						
							|  |  |  |         }], | 
					
						
							|  |  |  |         false)); | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  |     expect(result).toContain('static someMethod(v) { return v; }'); | 
					
						
							|  |  |  |     expect(result).toContain('static someOtherMethod(v) { return v; }'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should insert imports after existing imports', () => { | 
					
						
							|  |  |  |     context = context.override({ | 
					
						
							|  |  |  |       somePackage: { | 
					
						
							|  |  |  |         'someGenFile.ts': `
 | 
					
						
							|  |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @Component({selector: 'some-class', template: 'hello!'}) | 
					
						
							|  |  |  |         export class SomeClass {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         export class SomeOtherClass {} | 
					
						
							|  |  |  |       `
 | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     host = new MockCompilerHost(context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(emitStaticMethod(new o.ReturnStatement( | 
					
						
							|  |  |  |                o.importExpr(new o.ExternalReference('@angular/core', 'Component'))))) | 
					
						
							|  |  |  |         .toContain('const core_1 = require("@angular/core"); const i0 = require("@angular/core");'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function emit(factory: ts.TransformerFactory<ts.SourceFile>): string { | 
					
						
							|  |  |  |     let result: string = ''; | 
					
						
							|  |  |  |     const program = ts.createProgram( | 
					
						
							|  |  |  |         [someGenFileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host); | 
					
						
							|  |  |  |     const moduleSourceFile = program.getSourceFile(someGenFileName); | 
					
						
							|  |  |  |     const transformers: ts.CustomTransformers = {before: [factory]}; | 
					
						
							|  |  |  |     const emitResult = program.emit( | 
					
						
							|  |  |  |         moduleSourceFile, (fileName, data, writeByteOrderMark, onError, sourceFiles) => { | 
					
						
							|  |  |  |           if (fileName.startsWith(someGenFilePath)) { | 
					
						
							|  |  |  |             result = data; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, undefined, undefined, transformers); | 
					
						
							|  |  |  |     return normalizeResult(result); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function emitStaticMethod( | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  |       stmt: o.Statement|o.Statement[], parameters: string[] = [], methodName: string = 'someMethod', | 
					
						
							|  |  |  |       className: string = 'SomeClass'): string { | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  |     const module: PartialModule = { | 
					
						
							|  |  |  |       fileName: someGenFileName, | 
					
						
							|  |  |  |       statements: [classMethod(stmt, parameters, methodName, className)] | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-03-07 17:14:25 +01:00
										 |  |  |     return emit(getAngularClassTransformerFactory([module], false)); | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function emitStaticField( | 
					
						
							|  |  |  |       initializer: o.Expression, fieldName: string = 'someField', | 
					
						
							|  |  |  |       className: string = 'SomeClass'): string { | 
					
						
							|  |  |  |     const module: PartialModule = { | 
					
						
							|  |  |  |       fileName: someGenFileName, | 
					
						
							|  |  |  |       statements: [classField(initializer, fieldName, className)] | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-03-07 17:14:25 +01:00
										 |  |  |     return emit(getAngularClassTransformerFactory([module], false)); | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const FILES: Directory = { | 
					
						
							|  |  |  |   somePackage: { | 
					
						
							|  |  |  |     'someGenFile.ts': `
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   export class SomeClass {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   export class SomeOtherClass {} | 
					
						
							|  |  |  | `
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function classMethod( | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  |     stmt: o.Statement|o.Statement[], parameters: string[] = [], methodName: string = 'someMethod', | 
					
						
							| 
									
										
										
										
											2017-11-20 10:21:17 -08:00
										 |  |  |     className: string = 'SomeClass'): o.ClassStmt { | 
					
						
							|  |  |  |   const statements = Array.isArray(stmt) ? stmt : [stmt]; | 
					
						
							|  |  |  |   return new o.ClassStmt( | 
					
						
							|  |  |  |       /* name */ className, | 
					
						
							|  |  |  |       /* parent */ null, | 
					
						
							|  |  |  |       /* fields */[], | 
					
						
							|  |  |  |       /* getters */[], | 
					
						
							|  |  |  |       /* constructorMethod */ new o.ClassMethod(null, [], []), | 
					
						
							|  |  |  |       /* methods */[new o.ClassMethod( | 
					
						
							|  |  |  |           methodName, parameters.map(name => new o.FnParam(name)), statements, null, | 
					
						
							|  |  |  |           [o.StmtModifier.Static])]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function classField( | 
					
						
							|  |  |  |     initializer: o.Expression, fieldName: string = 'someField', | 
					
						
							|  |  |  |     className: string = 'SomeClass'): o.ClassStmt { | 
					
						
							|  |  |  |   return new o.ClassStmt( | 
					
						
							|  |  |  |       /* name */ className, | 
					
						
							|  |  |  |       /* parent */ null, | 
					
						
							|  |  |  |       /* fields */[new o.ClassField(fieldName, null, [o.StmtModifier.Static], initializer)], | 
					
						
							|  |  |  |       /* getters */[], | 
					
						
							|  |  |  |       /* constructorMethod */ new o.ClassMethod(null, [], []), | 
					
						
							|  |  |  |       /* methods */[]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function normalizeResult(result: string): string { | 
					
						
							|  |  |  |   // Remove TypeScript prefixes
 | 
					
						
							|  |  |  |   // Remove new lines
 | 
					
						
							|  |  |  |   // Squish adjacent spaces
 | 
					
						
							|  |  |  |   // Remove prefix and postfix spaces
 | 
					
						
							|  |  |  |   return result.replace('"use strict";', ' ') | 
					
						
							|  |  |  |       .replace('exports.__esModule = true;', ' ') | 
					
						
							|  |  |  |       .replace('Object.defineProperty(exports, "__esModule", { value: true });', ' ') | 
					
						
							|  |  |  |       .replace(/\n/g, ' ') | 
					
						
							|  |  |  |       .replace(/ +/g, ' ') | 
					
						
							|  |  |  |       .replace(/^ /g, '') | 
					
						
							|  |  |  |       .replace(/ $/g, ''); | 
					
						
							|  |  |  | } |