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