| 
									
										
										
										
											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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-23 17:51:19 -07:00
										 |  |  | import {createLoweredSymbol, isLoweredSymbol} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							| 
									
										
										
										
											2017-10-23 17:51:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  | import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index'; | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  | import {MetadataCache, MetadataTransformer, ValueTransform} from './metadata_cache'; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | export interface LoweringRequest { | 
					
						
							|  |  |  |   kind: ts.SyntaxKind; | 
					
						
							|  |  |  |   location: number; | 
					
						
							|  |  |  |   end: number; | 
					
						
							|  |  |  |   name: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type RequestLocationMap = Map<number, LoweringRequest>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  | const enum DeclarationOrder { BeforeStmt, AfterStmt } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | interface Declaration { | 
					
						
							|  |  |  |   name: string; | 
					
						
							|  |  |  |   node: ts.Node; | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |   order: DeclarationOrder; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface DeclarationInsert { | 
					
						
							|  |  |  |   declarations: Declaration[]; | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |   relativeTo: ts.Node; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function toMap<T, K>(items: T[], select: (item: T) => K): Map<K, T> { | 
					
						
							|  |  |  |   return new Map(items.map<[K, T]>(i => [select(i), i])); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  | // We will never lower expressions in a nested lexical scope so avoid entering them.
 | 
					
						
							|  |  |  | // This also avoids a bug in TypeScript 2.3 where the lexical scopes get out of sync
 | 
					
						
							|  |  |  | // when using visitEachChild.
 | 
					
						
							|  |  |  | function isLexicalScope(node: ts.Node): boolean { | 
					
						
							|  |  |  |   switch (node.kind) { | 
					
						
							|  |  |  |     case ts.SyntaxKind.ArrowFunction: | 
					
						
							|  |  |  |     case ts.SyntaxKind.FunctionExpression: | 
					
						
							|  |  |  |     case ts.SyntaxKind.FunctionDeclaration: | 
					
						
							|  |  |  |     case ts.SyntaxKind.ClassExpression: | 
					
						
							|  |  |  |     case ts.SyntaxKind.ClassDeclaration: | 
					
						
							|  |  |  |     case ts.SyntaxKind.FunctionType: | 
					
						
							|  |  |  |     case ts.SyntaxKind.TypeLiteral: | 
					
						
							|  |  |  |     case ts.SyntaxKind.ArrayType: | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | function transformSourceFile( | 
					
						
							|  |  |  |     sourceFile: ts.SourceFile, requests: RequestLocationMap, | 
					
						
							|  |  |  |     context: ts.TransformationContext): ts.SourceFile { | 
					
						
							|  |  |  |   const inserts: DeclarationInsert[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |   // Calculate the range of interesting locations. The transform will only visit nodes in this
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   // range to improve the performance on large files.
 | 
					
						
							|  |  |  |   const locations = Array.from(requests.keys()); | 
					
						
							|  |  |  |   const min = Math.min(...locations); | 
					
						
							|  |  |  |   const max = Math.max(...locations); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |   // Visit nodes matching the request and synthetic nodes added by tsickle
 | 
					
						
							|  |  |  |   function shouldVisit(pos: number, end: number): boolean { | 
					
						
							|  |  |  |     return (pos <= max && end >= min) || pos == -1; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   function visitSourceFile(sourceFile: ts.SourceFile): ts.SourceFile { | 
					
						
							| 
									
										
										
										
											2017-08-29 16:26:37 -07:00
										 |  |  |     function topLevelStatement(node: ts.Statement): ts.Statement { | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |       const declarations: Declaration[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       function visitNode(node: ts.Node): ts.Node { | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |         // Get the original node before tsickle
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |         const {pos, end, kind, parent: originalParent} = ts.getOriginalNode(node); | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |         const nodeRequest = requests.get(pos); | 
					
						
							|  |  |  |         if (nodeRequest && nodeRequest.kind == kind && nodeRequest.end == end) { | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |           // This node is requested to be rewritten as a reference to the exported name.
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |           if (originalParent && originalParent.kind === ts.SyntaxKind.VariableDeclaration) { | 
					
						
							|  |  |  |             // As the value represents the whole initializer of a variable declaration,
 | 
					
						
							|  |  |  |             // just refer to that variable. This e.g. helps to preserve closure comments
 | 
					
						
							|  |  |  |             // at the right place.
 | 
					
						
							|  |  |  |             const varParent = originalParent as ts.VariableDeclaration; | 
					
						
							|  |  |  |             if (varParent.name.kind === ts.SyntaxKind.Identifier) { | 
					
						
							|  |  |  |               const varName = varParent.name.text; | 
					
						
							|  |  |  |               const exportName = nodeRequest.name; | 
					
						
							|  |  |  |               declarations.push({ | 
					
						
							|  |  |  |                 name: exportName, | 
					
						
							|  |  |  |                 node: ts.createIdentifier(varName), | 
					
						
							|  |  |  |                 order: DeclarationOrder.AfterStmt | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  |               return node; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |           // Record that the node needs to be moved to an exported variable with the given name
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |           const exportName = nodeRequest.name; | 
					
						
							|  |  |  |           declarations.push({name: exportName, node, order: DeclarationOrder.BeforeStmt}); | 
					
						
							|  |  |  |           return ts.createIdentifier(exportName); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |         let result = node; | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |         if (shouldVisit(pos, end) && !isLexicalScope(node)) { | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |           result = ts.visitEachChild(node, visitNode, context); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return result; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  |       // Get the original node before tsickle
 | 
					
						
							|  |  |  |       const {pos, end} = ts.getOriginalNode(node); | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |       let resultStmt: ts.Statement; | 
					
						
							|  |  |  |       if (shouldVisit(pos, end)) { | 
					
						
							|  |  |  |         resultStmt = ts.visitEachChild(node, visitNode, context); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         resultStmt = node; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (declarations.length) { | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |         inserts.push({relativeTo: resultStmt, declarations}); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |       return resultStmt; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |     let newStatements = sourceFile.statements.map(topLevelStatement); | 
					
						
							| 
									
										
										
										
											2017-08-02 11:20:07 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     if (inserts.length) { | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |       // Insert the declarations relative to the rewritten statement that references them.
 | 
					
						
							|  |  |  |       const insertMap = toMap(inserts, i => i.relativeTo); | 
					
						
							|  |  |  |       const tmpStatements: ts.Statement[] = []; | 
					
						
							|  |  |  |       newStatements.forEach(statement => { | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |         const insert = insertMap.get(statement); | 
					
						
							|  |  |  |         if (insert) { | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |           const before = insert.declarations.filter(d => d.order === DeclarationOrder.BeforeStmt); | 
					
						
							|  |  |  |           if (before.length) { | 
					
						
							|  |  |  |             tmpStatements.push(createVariableStatementForDeclarations(before)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           tmpStatements.push(statement); | 
					
						
							|  |  |  |           const after = insert.declarations.filter(d => d.order === DeclarationOrder.AfterStmt); | 
					
						
							|  |  |  |           if (after.length) { | 
					
						
							|  |  |  |             tmpStatements.push(createVariableStatementForDeclarations(after)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           tmpStatements.push(statement); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Insert an exports clause to export the declarations
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  |       tmpStatements.push(ts.createExportDeclaration( | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |           /* decorators */ undefined, | 
					
						
							|  |  |  |           /* modifiers */ undefined, | 
					
						
							|  |  |  |           ts.createNamedExports( | 
					
						
							|  |  |  |               inserts | 
					
						
							|  |  |  |                   .reduce( | 
					
						
							|  |  |  |                       (accumulator, insert) => [...accumulator, ...insert.declarations], | 
					
						
							|  |  |  |                       [] as Declaration[]) | 
					
						
							|  |  |  |                   .map( | 
					
						
							|  |  |  |                       declaration => ts.createExportSpecifier( | 
					
						
							|  |  |  |                           /* propertyName */ undefined, declaration.name))))); | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       newStatements = tmpStatements; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-08-29 16:26:37 -07:00
										 |  |  |     // Note: We cannot use ts.updateSourcefile here as
 | 
					
						
							|  |  |  |     // it does not work well with decorators.
 | 
					
						
							|  |  |  |     // See https://github.com/Microsoft/TypeScript/issues/17384
 | 
					
						
							|  |  |  |     const newSf = ts.getMutableClone(sourceFile); | 
					
						
							|  |  |  |     if (!(sourceFile.flags & ts.NodeFlags.Synthesized)) { | 
					
						
							|  |  |  |       newSf.flags &= ~ts.NodeFlags.Synthesized; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     newSf.statements = ts.setTextRange(ts.createNodeArray(newStatements), sourceFile.statements); | 
					
						
							|  |  |  |     return newSf; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return visitSourceFile(sourceFile); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-01 16:27:35 -07:00
										 |  |  | function createVariableStatementForDeclarations(declarations: Declaration[]): ts.VariableStatement { | 
					
						
							|  |  |  |   const varDecls = declarations.map( | 
					
						
							|  |  |  |       i => ts.createVariableDeclaration(i.name, /* type */ undefined, i.node as ts.Expression)); | 
					
						
							|  |  |  |   return ts.createVariableStatement( | 
					
						
							|  |  |  |       /* modifiers */ undefined, ts.createVariableDeclarationList(varDecls, ts.NodeFlags.Const)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-24 11:26:04 -07:00
										 |  |  | export function getExpressionLoweringTransformFactory( | 
					
						
							|  |  |  |     requestsMap: RequestsMap, program: ts.Program): (context: ts.TransformationContext) => | 
					
						
							|  |  |  |     (sourceFile: ts.SourceFile) => ts.SourceFile { | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   // Return the factory
 | 
					
						
							|  |  |  |   return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile): ts.SourceFile => { | 
					
						
							| 
									
										
										
										
											2017-10-24 11:26:04 -07:00
										 |  |  |     // We need to use the original SourceFile for reading metadata, and not the transformed one.
 | 
					
						
							| 
									
										
										
										
											2018-02-08 08:59:25 -08:00
										 |  |  |     const originalFile = program.getSourceFile(sourceFile.fileName); | 
					
						
							|  |  |  |     if (originalFile) { | 
					
						
							|  |  |  |       const requests = requestsMap.getRequests(originalFile); | 
					
						
							|  |  |  |       if (requests && requests.size) { | 
					
						
							|  |  |  |         return transformSourceFile(sourceFile, requests, context); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     return sourceFile; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface RequestsMap { getRequests(sourceFile: ts.SourceFile): RequestLocationMap; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface MetadataAndLoweringRequests { | 
					
						
							|  |  |  |   metadata: ModuleMetadata|undefined; | 
					
						
							|  |  |  |   requests: RequestLocationMap; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-16 08:45:21 -08:00
										 |  |  | function isEligibleForLowering(node: ts.Node | undefined): boolean { | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |   if (node) { | 
					
						
							|  |  |  |     switch (node.kind) { | 
					
						
							|  |  |  |       case ts.SyntaxKind.SourceFile: | 
					
						
							|  |  |  |       case ts.SyntaxKind.Decorator: | 
					
						
							|  |  |  |         // Lower expressions that are local to the module scope or
 | 
					
						
							|  |  |  |         // in a decorator.
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       case ts.SyntaxKind.ClassDeclaration: | 
					
						
							|  |  |  |       case ts.SyntaxKind.InterfaceDeclaration: | 
					
						
							|  |  |  |       case ts.SyntaxKind.EnumDeclaration: | 
					
						
							|  |  |  |       case ts.SyntaxKind.FunctionDeclaration: | 
					
						
							|  |  |  |         // Don't lower expressions in a declaration.
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       case ts.SyntaxKind.VariableDeclaration: | 
					
						
							|  |  |  |         // Avoid lowering expressions already in an exported variable declaration
 | 
					
						
							| 
									
										
										
										
											2018-08-05 17:31:27 +02:00
										 |  |  |         return (ts.getCombinedModifierFlags(node as ts.VariableDeclaration) & | 
					
						
							|  |  |  |                 ts.ModifierFlags.Export) == 0; | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-02-16 08:45:21 -08:00
										 |  |  |     return isEligibleForLowering(node.parent); | 
					
						
							| 
									
										
										
										
											2017-08-08 12:40:08 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  | function isPrimitive(value: any): boolean { | 
					
						
							|  |  |  |   return Object(value) !== value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isRewritten(value: any): boolean { | 
					
						
							| 
									
										
										
										
											2017-10-23 17:51:19 -07:00
										 |  |  |   return isMetadataGlobalReferenceExpression(value) && isLoweredSymbol(value.name); | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean { | 
					
						
							|  |  |  |   if (node.parent && node.parent.kind == ts.SyntaxKind.PropertyAssignment) { | 
					
						
							|  |  |  |     const property = node.parent as ts.PropertyAssignment; | 
					
						
							|  |  |  |     if (property.parent && property.parent.kind == ts.SyntaxKind.ObjectLiteralExpression && | 
					
						
							|  |  |  |         property.name && property.name.kind == ts.SyntaxKind.Identifier) { | 
					
						
							|  |  |  |       const propertyName = property.name as ts.Identifier; | 
					
						
							|  |  |  |       return names.has(propertyName.text); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  | export class LowerMetadataTransform implements RequestsMap, MetadataTransformer { | 
					
						
							| 
									
										
										
										
											2018-06-18 16:38:33 -07:00
										 |  |  |   // TODO(issue/24571): remove '!'.
 | 
					
						
							|  |  |  |   private cache !: MetadataCache; | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |   private requests = new Map<string, RequestLocationMap>(); | 
					
						
							| 
									
										
										
										
											2018-02-16 08:45:21 -08:00
										 |  |  |   private lowerableFieldNames: Set<string>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(lowerableFieldNames: string[]) { | 
					
						
							|  |  |  |     this.lowerableFieldNames = new Set<string>(lowerableFieldNames); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |   // RequestMap
 | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |   getRequests(sourceFile: ts.SourceFile): RequestLocationMap { | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |     let result = this.requests.get(sourceFile.fileName); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     if (!result) { | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |       // Force the metadata for this source file to be collected which
 | 
					
						
							|  |  |  |       // will recursively call start() populating the request map;
 | 
					
						
							|  |  |  |       this.cache.getMetadata(sourceFile); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // If we still don't have the requested metadata, the file is not a module
 | 
					
						
							|  |  |  |       // or is a declaration file so return an empty map.
 | 
					
						
							|  |  |  |       result = this.requests.get(sourceFile.fileName) || new Map<number, LoweringRequest>(); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |   // MetadataTransformer
 | 
					
						
							|  |  |  |   connect(cache: MetadataCache): void { this.cache = cache; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   start(sourceFile: ts.SourceFile): ValueTransform|undefined { | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     let identNumber = 0; | 
					
						
							| 
									
										
										
										
											2017-10-23 17:51:19 -07:00
										 |  |  |     const freshIdent = () => createLoweredSymbol(identNumber++); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |     const requests = new Map<number, LoweringRequest>(); | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |     this.requests.set(sourceFile.fileName, requests); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const replaceNode = (node: ts.Node) => { | 
					
						
							|  |  |  |       const name = freshIdent(); | 
					
						
							|  |  |  |       requests.set(node.pos, {name, kind: node.kind, location: node.pos, end: node.end}); | 
					
						
							|  |  |  |       return {__symbolic: 'reference', name}; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const isExportedSymbol = (() => { | 
					
						
							|  |  |  |       let exportTable: Set<string>; | 
					
						
							|  |  |  |       return (node: ts.Node) => { | 
					
						
							|  |  |  |         if (node.kind == ts.SyntaxKind.Identifier) { | 
					
						
							|  |  |  |           const ident = node as ts.Identifier; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (!exportTable) { | 
					
						
							|  |  |  |             exportTable = createExportTableFor(sourceFile); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           return exportTable.has(ident.text); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     })(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-20 16:26:11 -07:00
										 |  |  |     const isExportedPropertyAccess = (node: ts.Node) => { | 
					
						
							|  |  |  |       if (node.kind === ts.SyntaxKind.PropertyAccessExpression) { | 
					
						
							|  |  |  |         const pae = node as ts.PropertyAccessExpression; | 
					
						
							|  |  |  |         if (isExportedSymbol(pae.expression)) { | 
					
						
							|  |  |  |           return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-16 08:45:21 -08:00
										 |  |  |     const hasLowerableParentCache = new Map<ts.Node, boolean>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const shouldBeLowered = (node: ts.Node | undefined): boolean => { | 
					
						
							|  |  |  |       if (node === undefined) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       let lowerable: boolean = false; | 
					
						
							|  |  |  |       if ((node.kind === ts.SyntaxKind.ArrowFunction || | 
					
						
							|  |  |  |            node.kind === ts.SyntaxKind.FunctionExpression) && | 
					
						
							|  |  |  |           isEligibleForLowering(node)) { | 
					
						
							|  |  |  |         lowerable = true; | 
					
						
							|  |  |  |       } else if ( | 
					
						
							|  |  |  |           isLiteralFieldNamed(node, this.lowerableFieldNames) && isEligibleForLowering(node) && | 
					
						
							|  |  |  |           !isExportedSymbol(node) && !isExportedPropertyAccess(node)) { | 
					
						
							|  |  |  |         lowerable = true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return lowerable; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const hasLowerableParent = (node: ts.Node | undefined): boolean => { | 
					
						
							|  |  |  |       if (node === undefined) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (!hasLowerableParentCache.has(node)) { | 
					
						
							|  |  |  |         hasLowerableParentCache.set( | 
					
						
							|  |  |  |             node, shouldBeLowered(node.parent) || hasLowerableParent(node.parent)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return hasLowerableParentCache.get(node) !; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const isLowerable = (node: ts.Node | undefined): boolean => { | 
					
						
							|  |  |  |       if (node === undefined) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return shouldBeLowered(node) && !hasLowerableParent(node); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-30 17:17:54 -08:00
										 |  |  |     return (value: MetadataValue, node: ts.Node): MetadataValue => { | 
					
						
							| 
									
										
										
										
											2018-02-16 08:45:21 -08:00
										 |  |  |       if (!isPrimitive(value) && !isRewritten(value) && isLowerable(node)) { | 
					
						
							|  |  |  |         return replaceNode(node); | 
					
						
							| 
									
										
										
										
											2017-07-13 14:25:17 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       return value; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function createExportTableFor(sourceFile: ts.SourceFile): Set<string> { | 
					
						
							|  |  |  |   const exportTable = new Set<string>(); | 
					
						
							|  |  |  |   // Lazily collect all the exports from the source file
 | 
					
						
							|  |  |  |   ts.forEachChild(sourceFile, function scan(node) { | 
					
						
							|  |  |  |     switch (node.kind) { | 
					
						
							|  |  |  |       case ts.SyntaxKind.ClassDeclaration: | 
					
						
							|  |  |  |       case ts.SyntaxKind.FunctionDeclaration: | 
					
						
							|  |  |  |       case ts.SyntaxKind.InterfaceDeclaration: | 
					
						
							| 
									
										
										
										
											2018-08-05 17:31:27 +02:00
										 |  |  |         if ((ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) != 0) { | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  |           const classDeclaration = | 
					
						
							|  |  |  |               node as(ts.ClassDeclaration | ts.FunctionDeclaration | ts.InterfaceDeclaration); | 
					
						
							|  |  |  |           const name = classDeclaration.name; | 
					
						
							|  |  |  |           if (name) exportTable.add(name.text); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case ts.SyntaxKind.VariableStatement: | 
					
						
							|  |  |  |         const variableStatement = node as ts.VariableStatement; | 
					
						
							|  |  |  |         for (const declaration of variableStatement.declarationList.declarations) { | 
					
						
							|  |  |  |           scan(declaration); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case ts.SyntaxKind.VariableDeclaration: | 
					
						
							|  |  |  |         const variableDeclaration = node as ts.VariableDeclaration; | 
					
						
							| 
									
										
										
										
											2018-08-05 17:31:27 +02:00
										 |  |  |         if ((ts.getCombinedModifierFlags(variableDeclaration) & ts.ModifierFlags.Export) != 0 && | 
					
						
							| 
									
										
										
										
											2017-08-23 10:22:17 -07:00
										 |  |  |             variableDeclaration.name.kind == ts.SyntaxKind.Identifier) { | 
					
						
							|  |  |  |           const name = variableDeclaration.name as ts.Identifier; | 
					
						
							|  |  |  |           exportTable.add(name.text); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case ts.SyntaxKind.ExportDeclaration: | 
					
						
							|  |  |  |         const exportDeclaration = node as ts.ExportDeclaration; | 
					
						
							|  |  |  |         const {moduleSpecifier, exportClause} = exportDeclaration; | 
					
						
							|  |  |  |         if (!moduleSpecifier && exportClause) { | 
					
						
							|  |  |  |           exportClause.elements.forEach(spec => { exportTable.add(spec.name.text); }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return exportTable; | 
					
						
							|  |  |  | } |