| 
									
										
										
										
											2017-07-13 14:25:17 -07: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 {LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions'; | 
					
						
							|  |  |  | import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('Expression lowering', () => { | 
					
						
							|  |  |  |   it('should be able to lower a simple expression', () => { | 
					
						
							|  |  |  |     expect(convert('const a = 1 +◊b: 2◊;')).toBe('const b = 2; const a = 1 + b; export { b };'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to lower an expression in a decorator', () => { | 
					
						
							|  |  |  |     expect(convert(`
 | 
					
						
							|  |  |  |         import {Component} from '@angular/core'; | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         @Component({ | 
					
						
							|  |  |  |           provider: [{provide: 'someToken', useFactory:◊l: () => null◊}] | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         class MyClass {} | 
					
						
							|  |  |  |     `)).toContain('const l = () => null; exports.l = l;');
 | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function convert(annotatedSource: string) { | 
					
						
							|  |  |  |   const annotations: {start: number, length: number, name: string}[] = []; | 
					
						
							|  |  |  |   let adjustment = 0; | 
					
						
							|  |  |  |   const unannotatedSource = annotatedSource.replace( | 
					
						
							|  |  |  |       /◊([a-zA-Z]+):(.*)◊/g, | 
					
						
							|  |  |  |       (text: string, name: string, source: string, index: number): string => { | 
					
						
							|  |  |  |         annotations.push({start: index + adjustment, length: source.length, name}); | 
					
						
							|  |  |  |         adjustment -= text.length - source.length; | 
					
						
							|  |  |  |         return source; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const baseFileName = 'someFile'; | 
					
						
							|  |  |  |   const moduleName = '/' + baseFileName; | 
					
						
							|  |  |  |   const fileName = moduleName + '.ts'; | 
					
						
							|  |  |  |   const context = new MockAotContext('/', {[baseFileName + '.ts']: unannotatedSource}); | 
					
						
							|  |  |  |   const host = new MockCompilerHost(context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const sourceFile = ts.createSourceFile( | 
					
						
							|  |  |  |       fileName, unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true); | 
					
						
							|  |  |  |   const requests = new Map<number, LoweringRequest>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const annotation of annotations) { | 
					
						
							|  |  |  |     const node = findNode(sourceFile, annotation.start, annotation.length); | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |     if (!node) throw new Error('Invalid test specification. Could not find the node to substitute'); | 
					
						
							|  |  |  |     const location = node.pos; | 
					
						
							|  |  |  |     requests.set(location, {name: annotation.name, kind: node.kind, location, end: node.end}); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const program = ts.createProgram( | 
					
						
							|  |  |  |       [fileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host); | 
					
						
							|  |  |  |   const moduleSourceFile = program.getSourceFile(fileName); | 
					
						
							|  |  |  |   const transformers: ts.CustomTransformers = { | 
					
						
							|  |  |  |     before: [getExpressionLoweringTransformFactory({ | 
					
						
							|  |  |  |       getRequests(sourceFile: ts.SourceFile): RequestLocationMap{ | 
					
						
							|  |  |  |         if (sourceFile.fileName == moduleSourceFile.fileName) { | 
					
						
							|  |  |  |           return requests; | 
					
						
							|  |  |  |         } else {return new Map();} | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     })] | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   let result: string = ''; | 
					
						
							|  |  |  |   const emitResult = program.emit( | 
					
						
							|  |  |  |       moduleSourceFile, (emittedFileName, data, writeByteOrderMark, onError, sourceFiles) => { | 
					
						
							|  |  |  |         if (fileName.startsWith(moduleName)) { | 
					
						
							|  |  |  |           result = data; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, undefined, undefined, transformers); | 
					
						
							|  |  |  |   return normalizeResult(result); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function findNode(node: ts.Node, start: number, length: number): ts.Node|undefined { | 
					
						
							|  |  |  |   function find(node: ts.Node): ts.Node|undefined { | 
					
						
							|  |  |  |     if (node.getFullStart() == start && node.getEnd() == start + length) { | 
					
						
							|  |  |  |       return node; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (node.getFullStart() <= start && node.getEnd() >= start + length) { | 
					
						
							|  |  |  |       return ts.forEachChild(node, find); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return ts.forEachChild(node, find); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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, ''); | 
					
						
							|  |  |  | } |