1/*@internal*/ 2namespace ts { 3 export function transformSystemModule(context: TransformationContext) { 4 interface DependencyGroup { 5 name: StringLiteral; 6 externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; 7 } 8 9 const { 10 factory, 11 startLexicalEnvironment, 12 endLexicalEnvironment, 13 hoistVariableDeclaration 14 } = context; 15 16 const compilerOptions = context.getCompilerOptions(); 17 const resolver = context.getEmitResolver(); 18 const host = context.getEmitHost(); 19 const previousOnSubstituteNode = context.onSubstituteNode; 20 const previousOnEmitNode = context.onEmitNode; 21 context.onSubstituteNode = onSubstituteNode; 22 context.onEmitNode = onEmitNode; 23 context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols. 24 context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes expression identifiers for imported symbols 25 context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols. 26 context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols. 27 context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); // Substitutes updates to exported symbols. 28 context.enableSubstitution(SyntaxKind.MetaProperty); // Substitutes 'import.meta' 29 context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file. 30 31 const moduleInfoMap: ExternalModuleInfo[] = []; // The ExternalModuleInfo for each file. 32 const deferredExports: (Statement[] | undefined)[] = []; // Exports to defer until an EndOfDeclarationMarker is found. 33 const exportFunctionsMap: Identifier[] = []; // The export function associated with a source file. 34 const noSubstitutionMap: boolean[][] = []; // Set of nodes for which substitution rules should be ignored for each file. 35 const contextObjectMap: Identifier[] = []; // The context object associated with a source file. 36 37 let currentSourceFile: SourceFile; // The current file. 38 let moduleInfo: ExternalModuleInfo; // ExternalModuleInfo for the current file. 39 let exportFunction: Identifier; // The export function for the current file. 40 let contextObject: Identifier; // The context object for the current file. 41 let hoistedStatements: Statement[] | undefined; 42 let enclosingBlockScopedContainer: Node; 43 let noSubstitution: boolean[] | undefined; // Set of nodes for which substitution rules should be ignored. 44 45 return chainBundle(context, transformSourceFile); 46 47 /** 48 * Transforms the module aspects of a SourceFile. 49 * 50 * @param node The SourceFile node. 51 */ 52 function transformSourceFile(node: SourceFile) { 53 if (node.isDeclarationFile || !(isEffectiveExternalModule(node, compilerOptions) || node.transformFlags & TransformFlags.ContainsDynamicImport)) { 54 return node; 55 } 56 57 const id = getOriginalNodeId(node); 58 currentSourceFile = node; 59 enclosingBlockScopedContainer = node; 60 61 // System modules have the following shape: 62 // 63 // System.register(['dep-1', ... 'dep-n'], function(exports) {/* module body function */}) 64 // 65 // The parameter 'exports' here is a callback '<T>(name: string, value: T) => T' that 66 // is used to publish exported values. 'exports' returns its 'value' argument so in 67 // most cases expressions that mutate exported values can be rewritten as: 68 // 69 // expr -> exports('name', expr) 70 // 71 // The only exception in this rule is postfix unary operators, 72 // see comment to 'substitutePostfixUnaryExpression' for more details 73 74 // Collect information about the external module and dependency groups. 75 moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(context, node, resolver, compilerOptions); 76 77 // Make sure that the name of the 'exports' function does not conflict with 78 // existing identifiers. 79 exportFunction = factory.createUniqueName("exports"); 80 exportFunctionsMap[id] = exportFunction; 81 contextObject = contextObjectMap[id] = factory.createUniqueName("context"); 82 83 // Add the body of the module. 84 const dependencyGroups = collectDependencyGroups(moduleInfo.externalImports); 85 const moduleBodyBlock = createSystemModuleBody(node, dependencyGroups); 86 const moduleBodyFunction = factory.createFunctionExpression( 87 /*modifiers*/ undefined, 88 /*asteriskToken*/ undefined, 89 /*name*/ undefined, 90 /*typeParameters*/ undefined, 91 [ 92 factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, exportFunction), 93 factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, contextObject) 94 ], 95 /*type*/ undefined, 96 moduleBodyBlock 97 ); 98 99 // Write the call to `System.register` 100 // Clear the emit-helpers flag for later passes since we'll have already used it in the module body 101 // So the helper will be emit at the correct position instead of at the top of the source-file 102 const moduleName = tryGetModuleNameFromFile(factory, node, host, compilerOptions); 103 const dependencies = factory.createArrayLiteralExpression(map(dependencyGroups, dependencyGroup => dependencyGroup.name)); 104 const updated = setEmitFlags( 105 factory.updateSourceFile( 106 node, 107 setTextRange( 108 factory.createNodeArray([ 109 factory.createExpressionStatement( 110 factory.createCallExpression( 111 factory.createPropertyAccessExpression(factory.createIdentifier("System"), "register"), 112 /*typeArguments*/ undefined, 113 moduleName 114 ? [moduleName, dependencies, moduleBodyFunction] 115 : [dependencies, moduleBodyFunction] 116 ) 117 ) 118 ]), 119 node.statements 120 ) 121 ), EmitFlags.NoTrailingComments); 122 123 if (!outFile(compilerOptions)) { 124 moveEmitHelpers(updated, moduleBodyBlock, helper => !helper.scoped); 125 } 126 127 if (noSubstitution) { 128 noSubstitutionMap[id] = noSubstitution; 129 noSubstitution = undefined; 130 } 131 132 currentSourceFile = undefined!; 133 moduleInfo = undefined!; 134 exportFunction = undefined!; 135 contextObject = undefined!; 136 hoistedStatements = undefined!; 137 enclosingBlockScopedContainer = undefined!; 138 return updated; 139 } 140 141 /** 142 * Collects the dependency groups for this files imports. 143 * 144 * @param externalImports The imports for the file. 145 */ 146 function collectDependencyGroups(externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]) { 147 const groupIndices = new Map<string, number>(); 148 const dependencyGroups: DependencyGroup[] = []; 149 for (const externalImport of externalImports) { 150 const externalModuleName = getExternalModuleNameLiteral(factory, externalImport, currentSourceFile, host, resolver, compilerOptions); 151 if (externalModuleName) { 152 const text = externalModuleName.text; 153 const groupIndex = groupIndices.get(text); 154 if (groupIndex !== undefined) { 155 // deduplicate/group entries in dependency list by the dependency name 156 dependencyGroups[groupIndex].externalImports.push(externalImport); 157 } 158 else { 159 groupIndices.set(text, dependencyGroups.length); 160 dependencyGroups.push({ 161 name: externalModuleName, 162 externalImports: [externalImport] 163 }); 164 } 165 } 166 } 167 168 return dependencyGroups; 169 } 170 171 /** 172 * Adds the statements for the module body function for the source file. 173 * 174 * @param node The source file for the module. 175 * @param dependencyGroups The grouped dependencies of the module. 176 */ 177 function createSystemModuleBody(node: SourceFile, dependencyGroups: DependencyGroup[]) { 178 // Shape of the body in system modules: 179 // 180 // function (exports) { 181 // <list of local aliases for imports> 182 // <hoisted variable declarations> 183 // <hoisted function declarations> 184 // return { 185 // setters: [ 186 // <list of setter function for imports> 187 // ], 188 // execute: function() { 189 // <module statements> 190 // } 191 // } 192 // <temp declarations> 193 // } 194 // 195 // i.e: 196 // 197 // import {x} from 'file1' 198 // var y = 1; 199 // export function foo() { return y + x(); } 200 // console.log(y); 201 // 202 // Will be transformed to: 203 // 204 // function(exports) { 205 // function foo() { return y + file_1.x(); } 206 // exports("foo", foo); 207 // var file_1, y; 208 // return { 209 // setters: [ 210 // function(v) { file_1 = v } 211 // ], 212 // execute(): function() { 213 // y = 1; 214 // console.log(y); 215 // } 216 // }; 217 // } 218 219 const statements: Statement[] = []; 220 221 // We start a new lexical environment in this function body, but *not* in the 222 // body of the execute function. This allows us to emit temporary declarations 223 // only in the outer module body and not in the inner one. 224 startLexicalEnvironment(); 225 226 // Add any prologue directives. 227 const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile)); 228 const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict, sourceElementVisitor); 229 230 // var __moduleName = context_1 && context_1.id; 231 statements.push( 232 factory.createVariableStatement( 233 /*modifiers*/ undefined, 234 factory.createVariableDeclarationList([ 235 factory.createVariableDeclaration( 236 "__moduleName", 237 /*exclamationToken*/ undefined, 238 /*type*/ undefined, 239 factory.createLogicalAnd( 240 contextObject, 241 factory.createPropertyAccessExpression(contextObject, "id") 242 ) 243 ) 244 ]) 245 ) 246 ); 247 248 // Visit the synthetic external helpers import declaration if present 249 visitNode(moduleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement); 250 251 // Visit the statements of the source file, emitting any transformations into 252 // the `executeStatements` array. We do this *before* we fill the `setters` array 253 // as we both emit transformations as well as aggregate some data used when creating 254 // setters. This allows us to reduce the number of times we need to loop through the 255 // statements of the source file. 256 const executeStatements = visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset); 257 258 // Emit early exports for function declarations. 259 addRange(statements, hoistedStatements); 260 261 // We emit hoisted variables early to align roughly with our previous emit output. 262 // Two key differences in this approach are: 263 // - Temporary variables will appear at the top rather than at the bottom of the file 264 insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); 265 266 const exportStarFunction = addExportStarIfNeeded(statements)!; // TODO: GH#18217 267 const modifiers = node.transformFlags & TransformFlags.ContainsAwait ? 268 factory.createModifiersFromModifierFlags(ModifierFlags.Async) : 269 undefined; 270 const moduleObject = factory.createObjectLiteralExpression([ 271 factory.createPropertyAssignment("setters", 272 createSettersArray(exportStarFunction, dependencyGroups) 273 ), 274 factory.createPropertyAssignment("execute", 275 factory.createFunctionExpression( 276 modifiers, 277 /*asteriskToken*/ undefined, 278 /*name*/ undefined, 279 /*typeParameters*/ undefined, 280 /*parameters*/ [], 281 /*type*/ undefined, 282 factory.createBlock(executeStatements, /*multiLine*/ true) 283 ) 284 ) 285 ], /*multiLine*/ true); 286 287 statements.push(factory.createReturnStatement(moduleObject)); 288 return factory.createBlock(statements, /*multiLine*/ true); 289 } 290 291 /** 292 * Adds an exportStar function to a statement list if it is needed for the file. 293 * 294 * @param statements A statement list. 295 */ 296 function addExportStarIfNeeded(statements: Statement[]) { 297 if (!moduleInfo.hasExportStarsToExportValues) { 298 return; 299 } 300 301 // when resolving exports local exported entries/indirect exported entries in the module 302 // should always win over entries with similar names that were added via star exports 303 // to support this we store names of local/indirect exported entries in a set. 304 // this set is used to filter names brought by star expors. 305 306 // local names set should only be added if we have anything exported 307 if (!moduleInfo.exportedNames && moduleInfo.exportSpecifiers.size === 0) { 308 // no exported declarations (export var ...) or export specifiers (export {x}) 309 // check if we have any non star export declarations. 310 let hasExportDeclarationWithExportClause = false; 311 for (const externalImport of moduleInfo.externalImports) { 312 if (externalImport.kind === SyntaxKind.ExportDeclaration && externalImport.exportClause) { 313 hasExportDeclarationWithExportClause = true; 314 break; 315 } 316 } 317 318 if (!hasExportDeclarationWithExportClause) { 319 // we still need to emit exportStar helper 320 const exportStarFunction = createExportStarFunction(/*localNames*/ undefined); 321 statements.push(exportStarFunction); 322 return exportStarFunction.name; 323 } 324 } 325 326 const exportedNames: ObjectLiteralElementLike[] = []; 327 if (moduleInfo.exportedNames) { 328 for (const exportedLocalName of moduleInfo.exportedNames) { 329 if (exportedLocalName.escapedText === "default") { 330 continue; 331 } 332 333 // write name of exported declaration, i.e 'export var x...' 334 exportedNames.push( 335 factory.createPropertyAssignment( 336 factory.createStringLiteralFromNode(exportedLocalName), 337 factory.createTrue() 338 ) 339 ); 340 } 341 } 342 343 const exportedNamesStorageRef = factory.createUniqueName("exportedNames"); 344 statements.push( 345 factory.createVariableStatement( 346 /*modifiers*/ undefined, 347 factory.createVariableDeclarationList([ 348 factory.createVariableDeclaration( 349 exportedNamesStorageRef, 350 /*exclamationToken*/ undefined, 351 /*type*/ undefined, 352 factory.createObjectLiteralExpression(exportedNames, /*multiline*/ true) 353 ) 354 ]) 355 ) 356 ); 357 358 const exportStarFunction = createExportStarFunction(exportedNamesStorageRef); 359 statements.push(exportStarFunction); 360 return exportStarFunction.name; 361 } 362 363 /** 364 * Creates an exportStar function for the file, with an optional set of excluded local 365 * names. 366 * 367 * @param localNames An optional reference to an object containing a set of excluded local 368 * names. 369 */ 370 function createExportStarFunction(localNames: Identifier | undefined) { 371 const exportStarFunction = factory.createUniqueName("exportStar"); 372 const m = factory.createIdentifier("m"); 373 const n = factory.createIdentifier("n"); 374 const exports = factory.createIdentifier("exports"); 375 let condition: Expression = factory.createStrictInequality(n, factory.createStringLiteral("default")); 376 if (localNames) { 377 condition = factory.createLogicalAnd( 378 condition, 379 factory.createLogicalNot( 380 factory.createCallExpression( 381 factory.createPropertyAccessExpression(localNames, "hasOwnProperty"), 382 /*typeArguments*/ undefined, 383 [n] 384 ) 385 ) 386 ); 387 } 388 389 return factory.createFunctionDeclaration( 390 /*decorators*/ undefined, 391 /*modifiers*/ undefined, 392 /*asteriskToken*/ undefined, 393 exportStarFunction, 394 /*typeParameters*/ undefined, 395 [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, m)], 396 /*type*/ undefined, 397 factory.createBlock([ 398 factory.createVariableStatement( 399 /*modifiers*/ undefined, 400 factory.createVariableDeclarationList([ 401 factory.createVariableDeclaration( 402 exports, 403 /*exclamationToken*/ undefined, 404 /*type*/ undefined, 405 factory.createObjectLiteralExpression([]) 406 ) 407 ]) 408 ), 409 factory.createForInStatement( 410 factory.createVariableDeclarationList([ 411 factory.createVariableDeclaration(n) 412 ]), 413 m, 414 factory.createBlock([ 415 setEmitFlags( 416 factory.createIfStatement( 417 condition, 418 factory.createExpressionStatement( 419 factory.createAssignment( 420 factory.createElementAccessExpression(exports, n), 421 factory.createElementAccessExpression(m, n) 422 ) 423 ) 424 ), 425 EmitFlags.SingleLine 426 ) 427 ]) 428 ), 429 factory.createExpressionStatement( 430 factory.createCallExpression( 431 exportFunction, 432 /*typeArguments*/ undefined, 433 [exports] 434 ) 435 ) 436 ], /*multiline*/ true) 437 ); 438 } 439 440 /** 441 * Creates an array setter callbacks for each dependency group. 442 * 443 * @param exportStarFunction A reference to an exportStarFunction for the file. 444 * @param dependencyGroups An array of grouped dependencies. 445 */ 446 function createSettersArray(exportStarFunction: Identifier, dependencyGroups: DependencyGroup[]) { 447 const setters: Expression[] = []; 448 for (const group of dependencyGroups) { 449 // derive a unique name for parameter from the first named entry in the group 450 const localName = forEach(group.externalImports, i => getLocalNameForExternalImport(factory, i, currentSourceFile)); 451 const parameterName = localName ? factory.getGeneratedNameForNode(localName) : factory.createUniqueName(""); 452 const statements: Statement[] = []; 453 for (const entry of group.externalImports) { 454 const importVariableName = getLocalNameForExternalImport(factory, entry, currentSourceFile)!; // TODO: GH#18217 455 switch (entry.kind) { 456 case SyntaxKind.ImportDeclaration: 457 if (!entry.importClause) { 458 // 'import "..."' case 459 // module is imported only for side-effects, no emit required 460 break; 461 } 462 // falls through 463 464 case SyntaxKind.ImportEqualsDeclaration: 465 Debug.assert(importVariableName !== undefined); 466 // save import into the local 467 statements.push( 468 factory.createExpressionStatement( 469 factory.createAssignment(importVariableName, parameterName) 470 ) 471 ); 472 break; 473 474 case SyntaxKind.ExportDeclaration: 475 Debug.assert(importVariableName !== undefined); 476 if (entry.exportClause) { 477 if (isNamedExports(entry.exportClause)) { 478 // export {a, b as c} from 'foo' 479 // 480 // emit as: 481 // 482 // exports_({ 483 // "a": _["a"], 484 // "c": _["b"] 485 // }); 486 const properties: PropertyAssignment[] = []; 487 for (const e of entry.exportClause.elements) { 488 properties.push( 489 factory.createPropertyAssignment( 490 factory.createStringLiteral(idText(e.name)), 491 factory.createElementAccessExpression( 492 parameterName, 493 factory.createStringLiteral(idText(e.propertyName || e.name)) 494 ) 495 ) 496 ); 497 } 498 499 statements.push( 500 factory.createExpressionStatement( 501 factory.createCallExpression( 502 exportFunction, 503 /*typeArguments*/ undefined, 504 [factory.createObjectLiteralExpression(properties, /*multiline*/ true)] 505 ) 506 ) 507 ); 508 } 509 else { 510 statements.push( 511 factory.createExpressionStatement( 512 factory.createCallExpression( 513 exportFunction, 514 /*typeArguments*/ undefined, 515 [ 516 factory.createStringLiteral(idText(entry.exportClause.name)), 517 parameterName 518 ] 519 ) 520 ) 521 ); 522 } 523 } 524 else { 525 // export * from 'foo' 526 // 527 // emit as: 528 // 529 // exportStar(foo_1_1); 530 statements.push( 531 factory.createExpressionStatement( 532 factory.createCallExpression( 533 exportStarFunction, 534 /*typeArguments*/ undefined, 535 [parameterName] 536 ) 537 ) 538 ); 539 } 540 break; 541 } 542 } 543 544 setters.push( 545 factory.createFunctionExpression( 546 /*modifiers*/ undefined, 547 /*asteriskToken*/ undefined, 548 /*name*/ undefined, 549 /*typeParameters*/ undefined, 550 [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, parameterName)], 551 /*type*/ undefined, 552 factory.createBlock(statements, /*multiLine*/ true) 553 ) 554 ); 555 } 556 557 return factory.createArrayLiteralExpression(setters, /*multiLine*/ true); 558 } 559 560 // 561 // Top-level Source Element Visitors 562 // 563 564 /** 565 * Visit source elements at the top-level of a module. 566 * 567 * @param node The node to visit. 568 */ 569 function sourceElementVisitor(node: Node): VisitResult<Node> { 570 switch (node.kind) { 571 case SyntaxKind.ImportDeclaration: 572 return visitImportDeclaration(<ImportDeclaration>node); 573 574 case SyntaxKind.ImportEqualsDeclaration: 575 return visitImportEqualsDeclaration(<ImportEqualsDeclaration>node); 576 577 case SyntaxKind.ExportDeclaration: 578 return visitExportDeclaration(<ExportDeclaration>node); 579 580 case SyntaxKind.ExportAssignment: 581 return visitExportAssignment(<ExportAssignment>node); 582 583 default: 584 return nestedElementVisitor(node); 585 } 586 } 587 588 /** 589 * Visits an ImportDeclaration node. 590 * 591 * @param node The node to visit. 592 */ 593 function visitImportDeclaration(node: ImportDeclaration): VisitResult<Statement> { 594 let statements: Statement[] | undefined; 595 if (node.importClause) { 596 hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217 597 } 598 599 if (hasAssociatedEndOfDeclarationMarker(node)) { 600 // Defer exports until we encounter an EndOfDeclarationMarker node 601 const id = getOriginalNodeId(node); 602 deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node); 603 } 604 else { 605 statements = appendExportsOfImportDeclaration(statements, node); 606 } 607 608 return singleOrMany(statements); 609 } 610 611 function visitExportDeclaration(node: ExportDeclaration): VisitResult<Statement> { 612 Debug.assertIsDefined(node); 613 return undefined; 614 } 615 616 /** 617 * Visits an ImportEqualsDeclaration node. 618 * 619 * @param node The node to visit. 620 */ 621 function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult<Statement> { 622 Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); 623 624 let statements: Statement[] | undefined; 625 hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217 626 627 if (hasAssociatedEndOfDeclarationMarker(node)) { 628 // Defer exports until we encounter an EndOfDeclarationMarker node 629 const id = getOriginalNodeId(node); 630 deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node); 631 } 632 else { 633 statements = appendExportsOfImportEqualsDeclaration(statements, node); 634 } 635 636 return singleOrMany(statements); 637 } 638 639 /** 640 * Visits an ExportAssignment node. 641 * 642 * @param node The node to visit. 643 */ 644 function visitExportAssignment(node: ExportAssignment): VisitResult<Statement> { 645 if (node.isExportEquals) { 646 // Elide `export=` as it is illegal in a SystemJS module. 647 return undefined; 648 } 649 650 const expression = visitNode(node.expression, destructuringAndImportCallVisitor, isExpression); 651 const original = node.original; 652 if (original && hasAssociatedEndOfDeclarationMarker(original)) { 653 // Defer exports until we encounter an EndOfDeclarationMarker node 654 const id = getOriginalNodeId(node); 655 deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), expression, /*allowComments*/ true); 656 } 657 else { 658 return createExportStatement(factory.createIdentifier("default"), expression, /*allowComments*/ true); 659 } 660 } 661 662 /** 663 * Visits a FunctionDeclaration, hoisting it to the outer module body function. 664 * 665 * @param node The node to visit. 666 */ 667 function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult<Statement> { 668 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 669 hoistedStatements = append(hoistedStatements, 670 factory.updateFunctionDeclaration( 671 node, 672 node.decorators, 673 visitNodes(node.modifiers, modifierVisitor, isModifier), 674 node.asteriskToken, 675 factory.getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), 676 /*typeParameters*/ undefined, 677 visitNodes(node.parameters, destructuringAndImportCallVisitor, isParameterDeclaration), 678 /*type*/ undefined, 679 visitNode(node.body, destructuringAndImportCallVisitor, isBlock))); 680 } 681 else { 682 hoistedStatements = append(hoistedStatements, visitEachChild(node, destructuringAndImportCallVisitor, context)); 683 } 684 685 if (hasAssociatedEndOfDeclarationMarker(node)) { 686 // Defer exports until we encounter an EndOfDeclarationMarker node 687 const id = getOriginalNodeId(node); 688 deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); 689 } 690 else { 691 hoistedStatements = appendExportsOfHoistedDeclaration(hoistedStatements, node); 692 } 693 694 return undefined; 695 } 696 697 /** 698 * Visits a ClassDeclaration, hoisting its name to the outer module body function. 699 * 700 * @param node The node to visit. 701 */ 702 function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> { 703 let statements: Statement[] | undefined; 704 705 // Hoist the name of the class declaration to the outer module body function. 706 const name = factory.getLocalName(node); 707 hoistVariableDeclaration(name); 708 709 // Rewrite the class declaration into an assignment of a class expression. 710 statements = append(statements, 711 setTextRange( 712 factory.createExpressionStatement( 713 factory.createAssignment( 714 name, 715 setTextRange( 716 factory.createClassExpression( 717 visitNodes(node.decorators, destructuringAndImportCallVisitor, isDecorator), 718 /*modifiers*/ undefined, 719 node.name, 720 /*typeParameters*/ undefined, 721 visitNodes(node.heritageClauses, destructuringAndImportCallVisitor, isHeritageClause), 722 visitNodes(node.members, destructuringAndImportCallVisitor, isClassElement) 723 ), 724 node 725 ) 726 ) 727 ), 728 node 729 ) 730 ); 731 732 if (hasAssociatedEndOfDeclarationMarker(node)) { 733 // Defer exports until we encounter an EndOfDeclarationMarker node 734 const id = getOriginalNodeId(node); 735 deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node); 736 } 737 else { 738 statements = appendExportsOfHoistedDeclaration(statements, node); 739 } 740 741 return singleOrMany(statements); 742 } 743 744 /** 745 * Visits a variable statement, hoisting declared names to the top-level module body. 746 * Each declaration is rewritten into an assignment expression. 747 * 748 * @param node The node to visit. 749 */ 750 function visitVariableStatement(node: VariableStatement): VisitResult<Statement> { 751 if (!shouldHoistVariableDeclarationList(node.declarationList)) { 752 return visitNode(node, destructuringAndImportCallVisitor, isStatement); 753 } 754 755 let expressions: Expression[] | undefined; 756 const isExportedDeclaration = hasSyntacticModifier(node, ModifierFlags.Export); 757 const isMarkedDeclaration = hasAssociatedEndOfDeclarationMarker(node); 758 for (const variable of node.declarationList.declarations) { 759 if (variable.initializer) { 760 expressions = append(expressions, transformInitializedVariable(variable, isExportedDeclaration && !isMarkedDeclaration)); 761 } 762 else { 763 hoistBindingElement(variable); 764 } 765 } 766 767 let statements: Statement[] | undefined; 768 if (expressions) { 769 statements = append(statements, setTextRange(factory.createExpressionStatement(factory.inlineExpressions(expressions)), node)); 770 } 771 772 if (isMarkedDeclaration) { 773 // Defer exports until we encounter an EndOfDeclarationMarker node 774 const id = getOriginalNodeId(node); 775 deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node, isExportedDeclaration); 776 } 777 else { 778 statements = appendExportsOfVariableStatement(statements, node, /*exportSelf*/ false); 779 } 780 781 return singleOrMany(statements); 782 } 783 784 /** 785 * Hoists the declared names of a VariableDeclaration or BindingElement. 786 * 787 * @param node The declaration to hoist. 788 */ 789 function hoistBindingElement(node: VariableDeclaration | BindingElement): void { 790 if (isBindingPattern(node.name)) { 791 for (const element of node.name.elements) { 792 if (!isOmittedExpression(element)) { 793 hoistBindingElement(element); 794 } 795 } 796 } 797 else { 798 hoistVariableDeclaration(factory.cloneNode(node.name)); 799 } 800 } 801 802 /** 803 * Determines whether a VariableDeclarationList should be hoisted. 804 * 805 * @param node The node to test. 806 */ 807 function shouldHoistVariableDeclarationList(node: VariableDeclarationList) { 808 // hoist only non-block scoped declarations or block scoped declarations parented by source file 809 return (getEmitFlags(node) & EmitFlags.NoHoisting) === 0 810 && (enclosingBlockScopedContainer.kind === SyntaxKind.SourceFile 811 || (getOriginalNode(node).flags & NodeFlags.BlockScoped) === 0); 812 } 813 814 /** 815 * Transform an initialized variable declaration into an expression. 816 * 817 * @param node The node to transform. 818 * @param isExportedDeclaration A value indicating whether the variable is exported. 819 */ 820 function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression { 821 const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment; 822 return isBindingPattern(node.name) 823 ? flattenDestructuringAssignment( 824 node, 825 destructuringAndImportCallVisitor, 826 context, 827 FlattenLevel.All, 828 /*needsValue*/ false, 829 createAssignment 830 ) 831 : node.initializer ? createAssignment(node.name, visitNode(node.initializer, destructuringAndImportCallVisitor, isExpression)) : node.name; 832 } 833 834 /** 835 * Creates an assignment expression for an exported variable declaration. 836 * 837 * @param name The name of the variable. 838 * @param value The value of the variable's initializer. 839 * @param location The source map location for the assignment. 840 */ 841 function createExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) { 842 return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ true); 843 } 844 845 /** 846 * Creates an assignment expression for a non-exported variable declaration. 847 * 848 * @param name The name of the variable. 849 * @param value The value of the variable's initializer. 850 * @param location The source map location for the assignment. 851 */ 852 function createNonExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) { 853 return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ false); 854 } 855 856 /** 857 * Creates an assignment expression for a variable declaration. 858 * 859 * @param name The name of the variable. 860 * @param value The value of the variable's initializer. 861 * @param location The source map location for the assignment. 862 * @param isExportedDeclaration A value indicating whether the variable is exported. 863 */ 864 function createVariableAssignment(name: Identifier, value: Expression, location: TextRange | undefined, isExportedDeclaration: boolean) { 865 hoistVariableDeclaration(factory.cloneNode(name)); 866 return isExportedDeclaration 867 ? createExportExpression(name, preventSubstitution(setTextRange(factory.createAssignment(name, value), location))) 868 : preventSubstitution(setTextRange(factory.createAssignment(name, value), location)); 869 } 870 871 /** 872 * Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged 873 * and transformed declaration. 874 * 875 * @param node The node to visit. 876 */ 877 function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult<Statement> { 878 // For an EnumDeclaration or ModuleDeclaration that merges with a preceeding 879 // declaration we do not emit a leading variable declaration. To preserve the 880 // begin/end semantics of the declararation and to properly handle exports 881 // we wrapped the leading variable declaration in a `MergeDeclarationMarker`. 882 // 883 // To balance the declaration, we defer the exports of the elided variable 884 // statement until we visit this declaration's `EndOfDeclarationMarker`. 885 if (hasAssociatedEndOfDeclarationMarker(node) && node.original!.kind === SyntaxKind.VariableStatement) { 886 const id = getOriginalNodeId(node); 887 const isExportedDeclaration = hasSyntacticModifier(node.original!, ModifierFlags.Export); 888 deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], <VariableStatement>node.original, isExportedDeclaration); 889 } 890 891 return node; 892 } 893 894 /** 895 * Determines whether a node has an associated EndOfDeclarationMarker. 896 * 897 * @param node The node to test. 898 */ 899 function hasAssociatedEndOfDeclarationMarker(node: Node) { 900 return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0; 901 } 902 903 /** 904 * Visits a DeclarationMarker used as a placeholder for the end of a transformed 905 * declaration. 906 * 907 * @param node The node to visit. 908 */ 909 function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult<Statement> { 910 // For some transformations we emit an `EndOfDeclarationMarker` to mark the actual 911 // end of the transformed declaration. We use this marker to emit any deferred exports 912 // of the declaration. 913 const id = getOriginalNodeId(node); 914 const statements = deferredExports[id]; 915 if (statements) { 916 delete deferredExports[id]; 917 return append(statements, node); 918 } 919 else { 920 const original = getOriginalNode(node); 921 if (isModuleOrEnumDeclaration(original)) { 922 return append(appendExportsOfDeclaration(statements, original), node); 923 } 924 } 925 926 return node; 927 } 928 929 /** 930 * Appends the exports of an ImportDeclaration to a statement list, returning the 931 * statement list. 932 * 933 * @param statements A statement list to which the down-level export statements are to be 934 * appended. If `statements` is `undefined`, a new array is allocated if statements are 935 * appended. 936 * @param decl The declaration whose exports are to be recorded. 937 */ 938 function appendExportsOfImportDeclaration(statements: Statement[] | undefined, decl: ImportDeclaration) { 939 if (moduleInfo.exportEquals) { 940 return statements; 941 } 942 943 const importClause = decl.importClause; 944 if (!importClause) { 945 return statements; 946 } 947 948 if (importClause.name) { 949 statements = appendExportsOfDeclaration(statements, importClause); 950 } 951 952 const namedBindings = importClause.namedBindings; 953 if (namedBindings) { 954 switch (namedBindings.kind) { 955 case SyntaxKind.NamespaceImport: 956 statements = appendExportsOfDeclaration(statements, namedBindings); 957 break; 958 959 case SyntaxKind.NamedImports: 960 for (const importBinding of namedBindings.elements) { 961 statements = appendExportsOfDeclaration(statements, importBinding); 962 } 963 964 break; 965 } 966 } 967 968 return statements; 969 } 970 971 /** 972 * Appends the export of an ImportEqualsDeclaration to a statement list, returning the 973 * statement list. 974 * 975 * @param statements A statement list to which the down-level export statements are to be 976 * appended. If `statements` is `undefined`, a new array is allocated if statements are 977 * appended. 978 * @param decl The declaration whose exports are to be recorded. 979 */ 980 function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, decl: ImportEqualsDeclaration): Statement[] | undefined { 981 if (moduleInfo.exportEquals) { 982 return statements; 983 } 984 985 return appendExportsOfDeclaration(statements, decl); 986 } 987 988 /** 989 * Appends the exports of a VariableStatement to a statement list, returning the statement 990 * list. 991 * 992 * @param statements A statement list to which the down-level export statements are to be 993 * appended. If `statements` is `undefined`, a new array is allocated if statements are 994 * appended. 995 * @param node The VariableStatement whose exports are to be recorded. 996 * @param exportSelf A value indicating whether to also export each VariableDeclaration of 997 * `nodes` declaration list. 998 */ 999 function appendExportsOfVariableStatement(statements: Statement[] | undefined, node: VariableStatement, exportSelf: boolean): Statement[] | undefined { 1000 if (moduleInfo.exportEquals) { 1001 return statements; 1002 } 1003 1004 for (const decl of node.declarationList.declarations) { 1005 if (decl.initializer || exportSelf) { 1006 statements = appendExportsOfBindingElement(statements, decl, exportSelf); 1007 } 1008 } 1009 1010 return statements; 1011 } 1012 1013 /** 1014 * Appends the exports of a VariableDeclaration or BindingElement to a statement list, 1015 * returning the statement list. 1016 * 1017 * @param statements A statement list to which the down-level export statements are to be 1018 * appended. If `statements` is `undefined`, a new array is allocated if statements are 1019 * appended. 1020 * @param decl The declaration whose exports are to be recorded. 1021 * @param exportSelf A value indicating whether to also export the declaration itself. 1022 */ 1023 function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement, exportSelf: boolean): Statement[] | undefined { 1024 if (moduleInfo.exportEquals) { 1025 return statements; 1026 } 1027 1028 if (isBindingPattern(decl.name)) { 1029 for (const element of decl.name.elements) { 1030 if (!isOmittedExpression(element)) { 1031 statements = appendExportsOfBindingElement(statements, element, exportSelf); 1032 } 1033 } 1034 } 1035 else if (!isGeneratedIdentifier(decl.name)) { 1036 let excludeName: string | undefined; 1037 if (exportSelf) { 1038 statements = appendExportStatement(statements, decl.name, factory.getLocalName(decl)); 1039 excludeName = idText(decl.name); 1040 } 1041 1042 statements = appendExportsOfDeclaration(statements, decl, excludeName); 1043 } 1044 1045 return statements; 1046 } 1047 1048 /** 1049 * Appends the exports of a ClassDeclaration or FunctionDeclaration to a statement list, 1050 * returning the statement list. 1051 * 1052 * @param statements A statement list to which the down-level export statements are to be 1053 * appended. If `statements` is `undefined`, a new array is allocated if statements are 1054 * appended. 1055 * @param decl The declaration whose exports are to be recorded. 1056 */ 1057 function appendExportsOfHoistedDeclaration(statements: Statement[] | undefined, decl: ClassDeclaration | FunctionDeclaration): Statement[] | undefined { 1058 if (moduleInfo.exportEquals) { 1059 return statements; 1060 } 1061 1062 let excludeName: string | undefined; 1063 if (hasSyntacticModifier(decl, ModifierFlags.Export)) { 1064 const exportName = hasSyntacticModifier(decl, ModifierFlags.Default) ? factory.createStringLiteral("default") : decl.name!; 1065 statements = appendExportStatement(statements, exportName, factory.getLocalName(decl)); 1066 excludeName = getTextOfIdentifierOrLiteral(exportName); 1067 } 1068 1069 if (decl.name) { 1070 statements = appendExportsOfDeclaration(statements, decl, excludeName); 1071 } 1072 1073 return statements; 1074 } 1075 1076 /** 1077 * Appends the exports of a declaration to a statement list, returning the statement list. 1078 * 1079 * @param statements A statement list to which the down-level export statements are to be 1080 * appended. If `statements` is `undefined`, a new array is allocated if statements are 1081 * appended. 1082 * @param decl The declaration to export. 1083 * @param excludeName An optional name to exclude from exports. 1084 */ 1085 function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration, excludeName?: string): Statement[] | undefined { 1086 if (moduleInfo.exportEquals) { 1087 return statements; 1088 } 1089 1090 const name = factory.getDeclarationName(decl); 1091 const exportSpecifiers = moduleInfo.exportSpecifiers.get(idText(name)); 1092 if (exportSpecifiers) { 1093 for (const exportSpecifier of exportSpecifiers) { 1094 if (exportSpecifier.name.escapedText !== excludeName) { 1095 statements = appendExportStatement(statements, exportSpecifier.name, name); 1096 } 1097 } 1098 } 1099 return statements; 1100 } 1101 1102 /** 1103 * Appends the down-level representation of an export to a statement list, returning the 1104 * statement list. 1105 * 1106 * @param statements A statement list to which the down-level export statements are to be 1107 * appended. If `statements` is `undefined`, a new array is allocated if statements are 1108 * appended. 1109 * @param exportName The name of the export. 1110 * @param expression The expression to export. 1111 * @param allowComments Whether to allow comments on the export. 1112 */ 1113 function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier | StringLiteral, expression: Expression, allowComments?: boolean): Statement[] | undefined { 1114 statements = append(statements, createExportStatement(exportName, expression, allowComments)); 1115 return statements; 1116 } 1117 1118 /** 1119 * Creates a call to the current file's export function to export a value. 1120 * 1121 * @param name The bound name of the export. 1122 * @param value The exported value. 1123 * @param allowComments An optional value indicating whether to emit comments for the statement. 1124 */ 1125 function createExportStatement(name: Identifier | StringLiteral, value: Expression, allowComments?: boolean) { 1126 const statement = factory.createExpressionStatement(createExportExpression(name, value)); 1127 startOnNewLine(statement); 1128 if (!allowComments) { 1129 setEmitFlags(statement, EmitFlags.NoComments); 1130 } 1131 1132 return statement; 1133 } 1134 1135 /** 1136 * Creates a call to the current file's export function to export a value. 1137 * 1138 * @param name The bound name of the export. 1139 * @param value The exported value. 1140 */ 1141 function createExportExpression(name: Identifier | StringLiteral, value: Expression) { 1142 const exportName = isIdentifier(name) ? factory.createStringLiteralFromNode(name) : name; 1143 setEmitFlags(value, getEmitFlags(value) | EmitFlags.NoComments); 1144 return setCommentRange(factory.createCallExpression(exportFunction, /*typeArguments*/ undefined, [exportName, value]), value); 1145 } 1146 1147 // 1148 // Top-Level or Nested Source Element Visitors 1149 // 1150 1151 /** 1152 * Visit nested elements at the top-level of a module. 1153 * 1154 * @param node The node to visit. 1155 */ 1156 function nestedElementVisitor(node: Node): VisitResult<Node> { 1157 switch (node.kind) { 1158 case SyntaxKind.VariableStatement: 1159 return visitVariableStatement(<VariableStatement>node); 1160 1161 case SyntaxKind.FunctionDeclaration: 1162 return visitFunctionDeclaration(<FunctionDeclaration>node); 1163 1164 case SyntaxKind.ClassDeclaration: 1165 return visitClassDeclaration(<ClassDeclaration>node); 1166 1167 case SyntaxKind.ForStatement: 1168 return visitForStatement(<ForStatement>node); 1169 1170 case SyntaxKind.ForInStatement: 1171 return visitForInStatement(<ForInStatement>node); 1172 1173 case SyntaxKind.ForOfStatement: 1174 return visitForOfStatement(<ForOfStatement>node); 1175 1176 case SyntaxKind.DoStatement: 1177 return visitDoStatement(<DoStatement>node); 1178 1179 case SyntaxKind.WhileStatement: 1180 return visitWhileStatement(<WhileStatement>node); 1181 1182 case SyntaxKind.LabeledStatement: 1183 return visitLabeledStatement(<LabeledStatement>node); 1184 1185 case SyntaxKind.WithStatement: 1186 return visitWithStatement(<WithStatement>node); 1187 1188 case SyntaxKind.SwitchStatement: 1189 return visitSwitchStatement(<SwitchStatement>node); 1190 1191 case SyntaxKind.CaseBlock: 1192 return visitCaseBlock(<CaseBlock>node); 1193 1194 case SyntaxKind.CaseClause: 1195 return visitCaseClause(<CaseClause>node); 1196 1197 case SyntaxKind.DefaultClause: 1198 return visitDefaultClause(<DefaultClause>node); 1199 1200 case SyntaxKind.TryStatement: 1201 return visitTryStatement(<TryStatement>node); 1202 1203 case SyntaxKind.CatchClause: 1204 return visitCatchClause(<CatchClause>node); 1205 1206 case SyntaxKind.Block: 1207 return visitBlock(<Block>node); 1208 1209 case SyntaxKind.MergeDeclarationMarker: 1210 return visitMergeDeclarationMarker(<MergeDeclarationMarker>node); 1211 1212 case SyntaxKind.EndOfDeclarationMarker: 1213 return visitEndOfDeclarationMarker(<EndOfDeclarationMarker>node); 1214 1215 default: 1216 return destructuringAndImportCallVisitor(node); 1217 } 1218 } 1219 1220 /** 1221 * Visits the body of a ForStatement to hoist declarations. 1222 * 1223 * @param node The node to visit. 1224 */ 1225 function visitForStatement(node: ForStatement): VisitResult<Statement> { 1226 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1227 enclosingBlockScopedContainer = node; 1228 1229 node = factory.updateForStatement( 1230 node, 1231 node.initializer && visitForInitializer(node.initializer), 1232 visitNode(node.condition, destructuringAndImportCallVisitor, isExpression), 1233 visitNode(node.incrementor, destructuringAndImportCallVisitor, isExpression), 1234 visitNode(node.statement, nestedElementVisitor, isStatement) 1235 ); 1236 1237 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1238 return node; 1239 } 1240 1241 /** 1242 * Visits the body of a ForInStatement to hoist declarations. 1243 * 1244 * @param node The node to visit. 1245 */ 1246 function visitForInStatement(node: ForInStatement): VisitResult<Statement> { 1247 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1248 enclosingBlockScopedContainer = node; 1249 1250 node = factory.updateForInStatement( 1251 node, 1252 visitForInitializer(node.initializer), 1253 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1254 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock) 1255 ); 1256 1257 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1258 return node; 1259 } 1260 1261 /** 1262 * Visits the body of a ForOfStatement to hoist declarations. 1263 * 1264 * @param node The node to visit. 1265 */ 1266 function visitForOfStatement(node: ForOfStatement): VisitResult<Statement> { 1267 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1268 enclosingBlockScopedContainer = node; 1269 1270 node = factory.updateForOfStatement( 1271 node, 1272 node.awaitModifier, 1273 visitForInitializer(node.initializer), 1274 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1275 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock) 1276 ); 1277 1278 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1279 return node; 1280 } 1281 1282 /** 1283 * Determines whether to hoist the initializer of a ForStatement, ForInStatement, or 1284 * ForOfStatement. 1285 * 1286 * @param node The node to test. 1287 */ 1288 function shouldHoistForInitializer(node: ForInitializer): node is VariableDeclarationList { 1289 return isVariableDeclarationList(node) 1290 && shouldHoistVariableDeclarationList(node); 1291 } 1292 1293 /** 1294 * Visits the initializer of a ForStatement, ForInStatement, or ForOfStatement 1295 * 1296 * @param node The node to visit. 1297 */ 1298 function visitForInitializer(node: ForInitializer): ForInitializer { 1299 if (shouldHoistForInitializer(node)) { 1300 let expressions: Expression[] | undefined; 1301 for (const variable of node.declarations) { 1302 expressions = append(expressions, transformInitializedVariable(variable, /*isExportedDeclaration*/ false)); 1303 if (!variable.initializer) { 1304 hoistBindingElement(variable); 1305 } 1306 } 1307 1308 return expressions ? factory.inlineExpressions(expressions) : factory.createOmittedExpression(); 1309 } 1310 else { 1311 return visitEachChild(node, nestedElementVisitor, context); 1312 } 1313 } 1314 1315 /** 1316 * Visits the body of a DoStatement to hoist declarations. 1317 * 1318 * @param node The node to visit. 1319 */ 1320 function visitDoStatement(node: DoStatement): VisitResult<Statement> { 1321 return factory.updateDoStatement( 1322 node, 1323 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock), 1324 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression) 1325 ); 1326 } 1327 1328 /** 1329 * Visits the body of a WhileStatement to hoist declarations. 1330 * 1331 * @param node The node to visit. 1332 */ 1333 function visitWhileStatement(node: WhileStatement): VisitResult<Statement> { 1334 return factory.updateWhileStatement( 1335 node, 1336 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1337 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock) 1338 ); 1339 } 1340 1341 /** 1342 * Visits the body of a LabeledStatement to hoist declarations. 1343 * 1344 * @param node The node to visit. 1345 */ 1346 function visitLabeledStatement(node: LabeledStatement): VisitResult<Statement> { 1347 return factory.updateLabeledStatement( 1348 node, 1349 node.label, 1350 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock) 1351 ); 1352 } 1353 1354 /** 1355 * Visits the body of a WithStatement to hoist declarations. 1356 * 1357 * @param node The node to visit. 1358 */ 1359 function visitWithStatement(node: WithStatement): VisitResult<Statement> { 1360 return factory.updateWithStatement( 1361 node, 1362 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1363 visitNode(node.statement, nestedElementVisitor, isStatement, factory.liftToBlock) 1364 ); 1365 } 1366 1367 /** 1368 * Visits the body of a SwitchStatement to hoist declarations. 1369 * 1370 * @param node The node to visit. 1371 */ 1372 function visitSwitchStatement(node: SwitchStatement): VisitResult<Statement> { 1373 return factory.updateSwitchStatement( 1374 node, 1375 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1376 visitNode(node.caseBlock, nestedElementVisitor, isCaseBlock) 1377 ); 1378 } 1379 1380 /** 1381 * Visits the body of a CaseBlock to hoist declarations. 1382 * 1383 * @param node The node to visit. 1384 */ 1385 function visitCaseBlock(node: CaseBlock): CaseBlock { 1386 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1387 enclosingBlockScopedContainer = node; 1388 1389 node = factory.updateCaseBlock( 1390 node, 1391 visitNodes(node.clauses, nestedElementVisitor, isCaseOrDefaultClause) 1392 ); 1393 1394 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1395 return node; 1396 } 1397 1398 /** 1399 * Visits the body of a CaseClause to hoist declarations. 1400 * 1401 * @param node The node to visit. 1402 */ 1403 function visitCaseClause(node: CaseClause): VisitResult<CaseOrDefaultClause> { 1404 return factory.updateCaseClause( 1405 node, 1406 visitNode(node.expression, destructuringAndImportCallVisitor, isExpression), 1407 visitNodes(node.statements, nestedElementVisitor, isStatement) 1408 ); 1409 } 1410 1411 /** 1412 * Visits the body of a DefaultClause to hoist declarations. 1413 * 1414 * @param node The node to visit. 1415 */ 1416 function visitDefaultClause(node: DefaultClause): VisitResult<CaseOrDefaultClause> { 1417 return visitEachChild(node, nestedElementVisitor, context); 1418 } 1419 1420 /** 1421 * Visits the body of a TryStatement to hoist declarations. 1422 * 1423 * @param node The node to visit. 1424 */ 1425 function visitTryStatement(node: TryStatement): VisitResult<Statement> { 1426 return visitEachChild(node, nestedElementVisitor, context); 1427 } 1428 1429 /** 1430 * Visits the body of a CatchClause to hoist declarations. 1431 * 1432 * @param node The node to visit. 1433 */ 1434 function visitCatchClause(node: CatchClause): CatchClause { 1435 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1436 enclosingBlockScopedContainer = node; 1437 1438 node = factory.updateCatchClause( 1439 node, 1440 node.variableDeclaration, 1441 visitNode(node.block, nestedElementVisitor, isBlock) 1442 ); 1443 1444 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1445 return node; 1446 } 1447 1448 /** 1449 * Visits the body of a Block to hoist declarations. 1450 * 1451 * @param node The node to visit. 1452 */ 1453 function visitBlock(node: Block): Block { 1454 const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer; 1455 enclosingBlockScopedContainer = node; 1456 1457 node = visitEachChild(node, nestedElementVisitor, context); 1458 1459 enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer; 1460 return node; 1461 } 1462 1463 // 1464 // Destructuring Assignment Visitors 1465 // 1466 1467 /** 1468 * Visit nodes to flatten destructuring assignments to exported symbols. 1469 * 1470 * @param node The node to visit. 1471 */ 1472 function destructuringAndImportCallVisitor(node: Node): VisitResult<Node> { 1473 if (isDestructuringAssignment(node)) { 1474 return visitDestructuringAssignment(node); 1475 } 1476 else if (isImportCall(node)) { 1477 return visitImportCallExpression(node); 1478 } 1479 else if ((node.transformFlags & TransformFlags.ContainsDestructuringAssignment) || (node.transformFlags & TransformFlags.ContainsDynamicImport)) { 1480 return visitEachChild(node, destructuringAndImportCallVisitor, context); 1481 } 1482 else { 1483 return node; 1484 } 1485 } 1486 1487 function visitImportCallExpression(node: ImportCall): Expression { 1488 // import("./blah") 1489 // emit as 1490 // System.register([], function (_export, _context) { 1491 // return { 1492 // setters: [], 1493 // execute: () => { 1494 // _context.import('./blah'); 1495 // } 1496 // }; 1497 // }); 1498 const externalModuleName = getExternalModuleNameLiteral(factory, node, currentSourceFile, host, resolver, compilerOptions); 1499 const firstArgument = visitNode(firstOrUndefined(node.arguments), destructuringAndImportCallVisitor); 1500 // Only use the external module name if it differs from the first argument. This allows us to preserve the quote style of the argument on output. 1501 const argument = externalModuleName && (!firstArgument || !isStringLiteral(firstArgument) || firstArgument.text !== externalModuleName.text) ? externalModuleName : firstArgument; 1502 return factory.createCallExpression( 1503 factory.createPropertyAccessExpression( 1504 contextObject, 1505 factory.createIdentifier("import") 1506 ), 1507 /*typeArguments*/ undefined, 1508 argument ? [argument] : [] 1509 ); 1510 } 1511 1512 /** 1513 * Visits a DestructuringAssignment to flatten destructuring to exported symbols. 1514 * 1515 * @param node The node to visit. 1516 */ 1517 function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult<Expression> { 1518 if (hasExportedReferenceInDestructuringTarget(node.left)) { 1519 return flattenDestructuringAssignment( 1520 node, 1521 destructuringAndImportCallVisitor, 1522 context, 1523 FlattenLevel.All, 1524 /*needsValue*/ true 1525 ); 1526 } 1527 1528 return visitEachChild(node, destructuringAndImportCallVisitor, context); 1529 } 1530 1531 /** 1532 * Determines whether the target of a destructuring assigment refers to an exported symbol. 1533 * 1534 * @param node The destructuring target. 1535 */ 1536 function hasExportedReferenceInDestructuringTarget(node: Expression | ObjectLiteralElementLike): boolean { 1537 if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) { 1538 return hasExportedReferenceInDestructuringTarget(node.left); 1539 } 1540 else if (isSpreadElement(node)) { 1541 return hasExportedReferenceInDestructuringTarget(node.expression); 1542 } 1543 else if (isObjectLiteralExpression(node)) { 1544 return some(node.properties, hasExportedReferenceInDestructuringTarget); 1545 } 1546 else if (isArrayLiteralExpression(node)) { 1547 return some(node.elements, hasExportedReferenceInDestructuringTarget); 1548 } 1549 else if (isShorthandPropertyAssignment(node)) { 1550 return hasExportedReferenceInDestructuringTarget(node.name); 1551 } 1552 else if (isPropertyAssignment(node)) { 1553 return hasExportedReferenceInDestructuringTarget(node.initializer); 1554 } 1555 else if (isIdentifier(node)) { 1556 const container = resolver.getReferencedExportContainer(node); 1557 return container !== undefined && container.kind === SyntaxKind.SourceFile; 1558 } 1559 else { 1560 return false; 1561 } 1562 } 1563 1564 // 1565 // Modifier Visitors 1566 // 1567 1568 /** 1569 * Visit nodes to elide module-specific modifiers. 1570 * 1571 * @param node The node to visit. 1572 */ 1573 function modifierVisitor(node: Node): VisitResult<Node> { 1574 switch (node.kind) { 1575 case SyntaxKind.ExportKeyword: 1576 case SyntaxKind.DefaultKeyword: 1577 return undefined; 1578 } 1579 return node; 1580 } 1581 1582 // 1583 // Emit Notification 1584 // 1585 1586 /** 1587 * Hook for node emit notifications. 1588 * 1589 * @param hint A hint as to the intended usage of the node. 1590 * @param node The node to emit. 1591 * @param emitCallback A callback used to emit the node in the printer. 1592 */ 1593 function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { 1594 if (node.kind === SyntaxKind.SourceFile) { 1595 const id = getOriginalNodeId(node); 1596 currentSourceFile = <SourceFile>node; 1597 moduleInfo = moduleInfoMap[id]; 1598 exportFunction = exportFunctionsMap[id]; 1599 noSubstitution = noSubstitutionMap[id]; 1600 contextObject = contextObjectMap[id]; 1601 1602 if (noSubstitution) { 1603 delete noSubstitutionMap[id]; 1604 } 1605 1606 previousOnEmitNode(hint, node, emitCallback); 1607 1608 currentSourceFile = undefined!; 1609 moduleInfo = undefined!; 1610 exportFunction = undefined!; 1611 contextObject = undefined!; 1612 noSubstitution = undefined; 1613 } 1614 else { 1615 previousOnEmitNode(hint, node, emitCallback); 1616 } 1617 } 1618 1619 // 1620 // Substitutions 1621 // 1622 1623 /** 1624 * Hooks node substitutions. 1625 * 1626 * @param hint A hint as to the intended usage of the node. 1627 * @param node The node to substitute. 1628 */ 1629 function onSubstituteNode(hint: EmitHint, node: Node) { 1630 node = previousOnSubstituteNode(hint, node); 1631 if (isSubstitutionPrevented(node)) { 1632 return node; 1633 } 1634 1635 if (hint === EmitHint.Expression) { 1636 return substituteExpression(<Expression>node); 1637 } 1638 else if (hint === EmitHint.Unspecified) { 1639 return substituteUnspecified(node); 1640 } 1641 1642 return node; 1643 } 1644 1645 /** 1646 * Substitute the node, if necessary. 1647 * 1648 * @param node The node to substitute. 1649 */ 1650 function substituteUnspecified(node: Node) { 1651 switch (node.kind) { 1652 case SyntaxKind.ShorthandPropertyAssignment: 1653 return substituteShorthandPropertyAssignment(<ShorthandPropertyAssignment>node); 1654 } 1655 return node; 1656 } 1657 1658 /** 1659 * Substitution for a ShorthandPropertyAssignment whose name that may contain an imported or exported symbol. 1660 * 1661 * @param node The node to substitute. 1662 */ 1663 function substituteShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { 1664 const name = node.name; 1665 if (!isGeneratedIdentifier(name) && !isLocalName(name)) { 1666 const importDeclaration = resolver.getReferencedImportDeclaration(name); 1667 if (importDeclaration) { 1668 if (isImportClause(importDeclaration)) { 1669 return setTextRange( 1670 factory.createPropertyAssignment( 1671 factory.cloneNode(name), 1672 factory.createPropertyAccessExpression( 1673 factory.getGeneratedNameForNode(importDeclaration.parent), 1674 factory.createIdentifier("default") 1675 ) 1676 ), 1677 /*location*/ node 1678 ); 1679 } 1680 else if (isImportSpecifier(importDeclaration)) { 1681 return setTextRange( 1682 factory.createPropertyAssignment( 1683 factory.cloneNode(name), 1684 factory.createPropertyAccessExpression( 1685 factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration), 1686 factory.cloneNode(importDeclaration.propertyName || importDeclaration.name) 1687 ), 1688 ), 1689 /*location*/ node 1690 ); 1691 } 1692 } 1693 } 1694 return node; 1695 } 1696 1697 /** 1698 * Substitute the expression, if necessary. 1699 * 1700 * @param node The node to substitute. 1701 */ 1702 function substituteExpression(node: Expression) { 1703 switch (node.kind) { 1704 case SyntaxKind.Identifier: 1705 return substituteExpressionIdentifier(<Identifier>node); 1706 case SyntaxKind.BinaryExpression: 1707 return substituteBinaryExpression(<BinaryExpression>node); 1708 case SyntaxKind.PrefixUnaryExpression: 1709 case SyntaxKind.PostfixUnaryExpression: 1710 return substituteUnaryExpression(<PrefixUnaryExpression | PostfixUnaryExpression>node); 1711 case SyntaxKind.MetaProperty: 1712 return substituteMetaProperty(<MetaProperty>node); 1713 } 1714 1715 return node; 1716 } 1717 1718 /** 1719 * Substitution for an Identifier expression that may contain an imported or exported symbol. 1720 * 1721 * @param node The node to substitute. 1722 */ 1723 function substituteExpressionIdentifier(node: Identifier): Expression { 1724 if (getEmitFlags(node) & EmitFlags.HelperName) { 1725 const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); 1726 if (externalHelpersModuleName) { 1727 return factory.createPropertyAccessExpression(externalHelpersModuleName, node); 1728 } 1729 1730 return node; 1731 } 1732 1733 // When we see an identifier in an expression position that 1734 // points to an imported symbol, we should substitute a qualified 1735 // reference to the imported symbol if one is needed. 1736 // 1737 // - We do not substitute generated identifiers for any reason. 1738 // - We do not substitute identifiers tagged with the LocalName flag. 1739 if (!isGeneratedIdentifier(node) && !isLocalName(node)) { 1740 const importDeclaration = resolver.getReferencedImportDeclaration(node); 1741 if (importDeclaration) { 1742 if (isImportClause(importDeclaration)) { 1743 return setTextRange( 1744 factory.createPropertyAccessExpression( 1745 factory.getGeneratedNameForNode(importDeclaration.parent), 1746 factory.createIdentifier("default") 1747 ), 1748 /*location*/ node 1749 ); 1750 } 1751 else if (isImportSpecifier(importDeclaration)) { 1752 return setTextRange( 1753 factory.createPropertyAccessExpression( 1754 factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration), 1755 factory.cloneNode(importDeclaration.propertyName || importDeclaration.name) 1756 ), 1757 /*location*/ node 1758 ); 1759 } 1760 } 1761 } 1762 1763 return node; 1764 } 1765 1766 /** 1767 * Substitution for a BinaryExpression that may contain an imported or exported symbol. 1768 * 1769 * @param node The node to substitute. 1770 */ 1771 function substituteBinaryExpression(node: BinaryExpression): Expression { 1772 // When we see an assignment expression whose left-hand side is an exported symbol, 1773 // we should ensure all exports of that symbol are updated with the correct value. 1774 // 1775 // - We do not substitute generated identifiers for any reason. 1776 // - We do not substitute identifiers tagged with the LocalName flag. 1777 // - We do not substitute identifiers that were originally the name of an enum or 1778 // namespace due to how they are transformed in TypeScript. 1779 // - We only substitute identifiers that are exported at the top level. 1780 if (isAssignmentOperator(node.operatorToken.kind) 1781 && isIdentifier(node.left) 1782 && !isGeneratedIdentifier(node.left) 1783 && !isLocalName(node.left) 1784 && !isDeclarationNameOfEnumOrNamespace(node.left)) { 1785 const exportedNames = getExports(node.left); 1786 if (exportedNames) { 1787 // For each additional export of the declaration, apply an export assignment. 1788 let expression: Expression = node; 1789 for (const exportName of exportedNames) { 1790 expression = createExportExpression(exportName, preventSubstitution(expression)); 1791 } 1792 1793 return expression; 1794 } 1795 } 1796 1797 return node; 1798 } 1799 1800 /** 1801 * Substitution for a UnaryExpression that may contain an imported or exported symbol. 1802 * 1803 * @param node The node to substitute. 1804 */ 1805 function substituteUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression): Expression { 1806 // When we see a prefix or postfix increment expression whose operand is an exported 1807 // symbol, we should ensure all exports of that symbol are updated with the correct 1808 // value. 1809 // 1810 // - We do not substitute generated identifiers for any reason. 1811 // - We do not substitute identifiers tagged with the LocalName flag. 1812 // - We do not substitute identifiers that were originally the name of an enum or 1813 // namespace due to how they are transformed in TypeScript. 1814 // - We only substitute identifiers that are exported at the top level. 1815 if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) 1816 && isIdentifier(node.operand) 1817 && !isGeneratedIdentifier(node.operand) 1818 && !isLocalName(node.operand) 1819 && !isDeclarationNameOfEnumOrNamespace(node.operand)) { 1820 const exportedNames = getExports(node.operand); 1821 if (exportedNames) { 1822 let expression: Expression = node.kind === SyntaxKind.PostfixUnaryExpression 1823 ? setTextRange( 1824 factory.createPrefixUnaryExpression( 1825 node.operator, 1826 node.operand 1827 ), 1828 node 1829 ) 1830 : node; 1831 1832 for (const exportName of exportedNames) { 1833 expression = createExportExpression(exportName, preventSubstitution(expression)); 1834 } 1835 1836 if (node.kind === SyntaxKind.PostfixUnaryExpression) { 1837 expression = node.operator === SyntaxKind.PlusPlusToken 1838 ? factory.createSubtract(preventSubstitution(expression), factory.createNumericLiteral(1)) 1839 : factory.createAdd(preventSubstitution(expression), factory.createNumericLiteral(1)); 1840 } 1841 1842 return expression; 1843 } 1844 } 1845 1846 return node; 1847 } 1848 1849 function substituteMetaProperty(node: MetaProperty) { 1850 if (isImportMeta(node)) { 1851 return factory.createPropertyAccessExpression(contextObject, factory.createIdentifier("meta")); 1852 } 1853 return node; 1854 } 1855 1856 /** 1857 * Gets the exports of a name. 1858 * 1859 * @param name The name. 1860 */ 1861 function getExports(name: Identifier) { 1862 let exportedNames: Identifier[] | undefined; 1863 if (!isGeneratedIdentifier(name)) { 1864 const valueDeclaration = resolver.getReferencedImportDeclaration(name) 1865 || resolver.getReferencedValueDeclaration(name); 1866 1867 if (valueDeclaration) { 1868 const exportContainer = resolver.getReferencedExportContainer(name, /*prefixLocals*/ false); 1869 if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { 1870 exportedNames = append(exportedNames, factory.getDeclarationName(valueDeclaration)); 1871 } 1872 1873 exportedNames = addRange(exportedNames, moduleInfo && moduleInfo.exportedBindings[getOriginalNodeId(valueDeclaration)]); 1874 } 1875 } 1876 1877 return exportedNames; 1878 } 1879 1880 /** 1881 * Prevent substitution of a node for this transformer. 1882 * 1883 * @param node The node which should not be substituted. 1884 */ 1885 function preventSubstitution<T extends Node>(node: T): T { 1886 if (noSubstitution === undefined) noSubstitution = []; 1887 noSubstitution[getNodeId(node)] = true; 1888 return node; 1889 } 1890 1891 /** 1892 * Determines whether a node should not be substituted. 1893 * 1894 * @param node The node to test. 1895 */ 1896 function isSubstitutionPrevented(node: Node) { 1897 return noSubstitution && node.id && noSubstitution[node.id]; 1898 } 1899 } 1900} 1901