1/* @internal */ 2namespace ts { 3 function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory<SourceFile | Bundle> { 4 switch (moduleKind) { 5 case ModuleKind.ESNext: 6 case ModuleKind.ES2020: 7 case ModuleKind.ES2015: 8 return transformECMAScriptModule; 9 case ModuleKind.System: 10 return transformSystemModule; 11 default: 12 return transformModule; 13 } 14 } 15 16 const enum TransformationState { 17 Uninitialized, 18 Initialized, 19 Completed, 20 Disposed 21 } 22 23 const enum SyntaxKindFeatureFlags { 24 Substitution = 1 << 0, 25 EmitNotifications = 1 << 1, 26 } 27 28 export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; 29 30 export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { 31 return { 32 scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), 33 declarationTransformers: getDeclarationTransformers(customTransformers), 34 }; 35 } 36 37 function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { 38 if (emitOnlyDtsFiles) return emptyArray; 39 40 const languageVersion = getEmitScriptTarget(compilerOptions); 41 const moduleKind = getEmitModuleKind(compilerOptions); 42 const transformers: TransformerFactory<SourceFile | Bundle>[] = []; 43 44 addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); 45 46 transformers.push(transformTypeScript); 47 transformers.push(transformClassFields); 48 49 if (getJSXTransformEnabled(compilerOptions)) { 50 transformers.push(transformJsx); 51 } 52 53 if (languageVersion < ScriptTarget.ESNext) { 54 transformers.push(transformESNext); 55 } 56 57 if (languageVersion < ScriptTarget.ES2020) { 58 transformers.push(transformES2020); 59 } 60 61 if (languageVersion < ScriptTarget.ES2019) { 62 transformers.push(transformES2019); 63 } 64 65 if (languageVersion < ScriptTarget.ES2018) { 66 transformers.push(transformES2018); 67 } 68 69 if (languageVersion < ScriptTarget.ES2017) { 70 transformers.push(transformES2017); 71 } 72 73 if (languageVersion < ScriptTarget.ES2016) { 74 transformers.push(transformES2016); 75 } 76 77 if (languageVersion < ScriptTarget.ES2015) { 78 transformers.push(transformES2015); 79 transformers.push(transformGenerators); 80 } 81 82 transformers.push(getModuleTransformer(moduleKind)); 83 84 // The ES5 transformer is last so that it can substitute expressions like `exports.default` 85 // for ES3. 86 if (languageVersion < ScriptTarget.ES5) { 87 transformers.push(transformES5); 88 } 89 90 addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); 91 return transformers; 92 } 93 94 function getDeclarationTransformers(customTransformers?: CustomTransformers) { 95 const transformers: TransformerFactory<SourceFile | Bundle>[] = []; 96 transformers.push(transformDeclarations); 97 addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); 98 return transformers; 99 } 100 101 /** 102 * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. 103 */ 104 function wrapCustomTransformer(transformer: CustomTransformer): Transformer<Bundle | SourceFile> { 105 return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); 106 } 107 108 /** 109 * Wrap a transformer factory that may return a custom script or declaration transformer object. 110 */ 111 function wrapCustomTransformerFactory<T extends SourceFile | Bundle>(transformer: TransformerFactory<T> | CustomTransformerFactory, handleDefault: (context: TransformationContext, tx: Transformer<T>) => Transformer<Bundle | SourceFile>): TransformerFactory<Bundle | SourceFile> { 112 return context => { 113 const customTransformer = transformer(context); 114 return typeof customTransformer === "function" 115 ? handleDefault(context, customTransformer) 116 : wrapCustomTransformer(customTransformer); 117 }; 118 } 119 120 function wrapScriptTransformerFactory(transformer: TransformerFactory<SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> { 121 return wrapCustomTransformerFactory(transformer, chainBundle); 122 } 123 124 function wrapDeclarationTransformerFactory(transformer: TransformerFactory<Bundle | SourceFile> | CustomTransformerFactory): TransformerFactory<Bundle | SourceFile> { 125 return wrapCustomTransformerFactory(transformer, (_, node) => node); 126 } 127 128 export function noEmitSubstitution(_hint: EmitHint, node: Node) { 129 return node; 130 } 131 132 export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { 133 callback(hint, node); 134 } 135 136 /** 137 * Transforms an array of SourceFiles by passing them through each transformer. 138 * 139 * @param resolver The emit resolver provided by the checker. 140 * @param host The emit host object used to interact with the file system. 141 * @param options Compiler options to surface in the `TransformationContext`. 142 * @param nodes An array of nodes to transform. 143 * @param transforms An array of `TransformerFactory` callbacks. 144 * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. 145 */ 146 export function transformNodes<T extends Node>(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> { 147 const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count); 148 let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; 149 let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; 150 let lexicalEnvironmentStatements: Statement[]; 151 let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; 152 let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; 153 let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; 154 let lexicalEnvironmentStatementsStack: Statement[][] = []; 155 let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; 156 let lexicalEnvironmentStackOffset = 0; 157 let lexicalEnvironmentSuspended = false; 158 let emitHelpers: EmitHelper[] | undefined; 159 let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; 160 let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; 161 let state = TransformationState.Uninitialized; 162 const diagnostics: DiagnosticWithLocation[] = []; 163 164 // The transformation context is provided to each transformer as part of transformer 165 // initialization. 166 const context: TransformationContext = { 167 factory, 168 getCompilerOptions: () => options, 169 getEmitResolver: () => resolver!, // TODO: GH#18217 170 getEmitHost: () => host!, // TODO: GH#18217 171 getEmitHelperFactory: memoize(() => createEmitHelperFactory(context)), 172 startLexicalEnvironment, 173 suspendLexicalEnvironment, 174 resumeLexicalEnvironment, 175 endLexicalEnvironment, 176 setLexicalEnvironmentFlags, 177 getLexicalEnvironmentFlags, 178 hoistVariableDeclaration, 179 hoistFunctionDeclaration, 180 addInitializationStatement, 181 requestEmitHelper, 182 readEmitHelpers, 183 enableSubstitution, 184 enableEmitNotification, 185 isSubstitutionEnabled, 186 isEmitNotificationEnabled, 187 get onSubstituteNode() { return onSubstituteNode; }, 188 set onSubstituteNode(value) { 189 Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); 190 Debug.assert(value !== undefined, "Value must not be 'undefined'"); 191 onSubstituteNode = value; 192 }, 193 get onEmitNode() { return onEmitNode; }, 194 set onEmitNode(value) { 195 Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); 196 Debug.assert(value !== undefined, "Value must not be 'undefined'"); 197 onEmitNode = value; 198 }, 199 addDiagnostic(diag) { 200 diagnostics.push(diag); 201 } 202 }; 203 204 // Ensure the parse tree is clean before applying transformations 205 for (const node of nodes) { 206 disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); 207 } 208 209 performance.mark("beforeTransform"); 210 211 // Chain together and initialize each transformer. 212 const transformersWithContext = transformers.map(t => t(context)); 213 const transformation = (node: T): T => { 214 for (const transform of transformersWithContext) { 215 node = transform(node); 216 } 217 return node; 218 }; 219 220 // prevent modification of transformation hooks. 221 state = TransformationState.Initialized; 222 223 // Transform each node. 224 const transformed: T[] = []; 225 for (const node of nodes) { 226 tracing?.push(tracing.Phase.Emit, "transformNodes", node.kind === SyntaxKind.SourceFile ? { path: (node as any as SourceFile).path } : { kind: node.kind, pos: node.pos, end: node.end }); 227 transformed.push((allowDtsFiles ? transformation : transformRoot)(node)); 228 tracing?.pop(); 229 } 230 231 // prevent modification of the lexical environment. 232 state = TransformationState.Completed; 233 234 performance.mark("afterTransform"); 235 performance.measure("transformTime", "beforeTransform", "afterTransform"); 236 237 return { 238 transformed, 239 substituteNode, 240 emitNodeWithNotification, 241 isEmitNotificationEnabled, 242 dispose, 243 diagnostics 244 }; 245 246 function transformRoot(node: T) { 247 return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; 248 } 249 250 /** 251 * Enables expression substitutions in the pretty printer for the provided SyntaxKind. 252 */ 253 function enableSubstitution(kind: SyntaxKind) { 254 Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); 255 enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; 256 } 257 258 /** 259 * Determines whether expression substitutions are enabled for the provided node. 260 */ 261 function isSubstitutionEnabled(node: Node) { 262 return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 263 && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; 264 } 265 266 /** 267 * Emits a node with possible substitution. 268 * 269 * @param hint A hint as to the intended usage of the node. 270 * @param node The node to emit. 271 * @param emitCallback The callback used to emit the node or its substitute. 272 */ 273 function substituteNode(hint: EmitHint, node: Node) { 274 Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); 275 return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; 276 } 277 278 /** 279 * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. 280 */ 281 function enableEmitNotification(kind: SyntaxKind) { 282 Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); 283 enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; 284 } 285 286 /** 287 * Determines whether before/after emit notifications should be raised in the pretty 288 * printer when it emits a node. 289 */ 290 function isEmitNotificationEnabled(node: Node) { 291 return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 292 || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; 293 } 294 295 /** 296 * Emits a node with possible emit notification. 297 * 298 * @param hint A hint as to the intended usage of the node. 299 * @param node The node to emit. 300 * @param emitCallback The callback used to emit the node. 301 */ 302 function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { 303 Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); 304 if (node) { 305 // TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed 306 // (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739) 307 if (isEmitNotificationEnabled(node)) { 308 onEmitNode(hint, node, emitCallback); 309 } 310 else { 311 emitCallback(hint, node); 312 } 313 } 314 } 315 316 /** 317 * Records a hoisted variable declaration for the provided name within a lexical environment. 318 */ 319 function hoistVariableDeclaration(name: Identifier): void { 320 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 321 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 322 const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); 323 if (!lexicalEnvironmentVariableDeclarations) { 324 lexicalEnvironmentVariableDeclarations = [decl]; 325 } 326 else { 327 lexicalEnvironmentVariableDeclarations.push(decl); 328 } 329 if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) { 330 lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters; 331 } 332 } 333 334 /** 335 * Records a hoisted function declaration within a lexical environment. 336 */ 337 function hoistFunctionDeclaration(func: FunctionDeclaration): void { 338 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 339 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 340 setEmitFlags(func, EmitFlags.CustomPrologue); 341 if (!lexicalEnvironmentFunctionDeclarations) { 342 lexicalEnvironmentFunctionDeclarations = [func]; 343 } 344 else { 345 lexicalEnvironmentFunctionDeclarations.push(func); 346 } 347 } 348 349 /** 350 * Adds an initialization statement to the top of the lexical environment. 351 */ 352 function addInitializationStatement(node: Statement): void { 353 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 354 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 355 setEmitFlags(node, EmitFlags.CustomPrologue); 356 if (!lexicalEnvironmentStatements) { 357 lexicalEnvironmentStatements = [node]; 358 } 359 else { 360 lexicalEnvironmentStatements.push(node); 361 } 362 } 363 364 /** 365 * Starts a new lexical environment. Any existing hoisted variable or function declarations 366 * are pushed onto a stack, and the related storage variables are reset. 367 */ 368 function startLexicalEnvironment(): void { 369 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 370 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 371 Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); 372 373 // Save the current lexical environment. Rather than resizing the array we adjust the 374 // stack size variable. This allows us to reuse existing array slots we've 375 // already allocated between transformations to avoid allocation and GC overhead during 376 // transformation. 377 lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; 378 lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; 379 lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements; 380 lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags; 381 lexicalEnvironmentStackOffset++; 382 lexicalEnvironmentVariableDeclarations = undefined!; 383 lexicalEnvironmentFunctionDeclarations = undefined!; 384 lexicalEnvironmentStatements = undefined!; 385 lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; 386 } 387 388 /** Suspends the current lexical environment, usually after visiting a parameter list. */ 389 function suspendLexicalEnvironment(): void { 390 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 391 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 392 Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); 393 lexicalEnvironmentSuspended = true; 394 } 395 396 /** Resumes a suspended lexical environment, usually before visiting a function body. */ 397 function resumeLexicalEnvironment(): void { 398 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 399 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 400 Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); 401 lexicalEnvironmentSuspended = false; 402 } 403 404 /** 405 * Ends a lexical environment. The previous set of hoisted declarations are restored and 406 * any hoisted declarations added in this environment are returned. 407 */ 408 function endLexicalEnvironment(): Statement[] | undefined { 409 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); 410 Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); 411 Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); 412 413 let statements: Statement[] | undefined; 414 if (lexicalEnvironmentVariableDeclarations || 415 lexicalEnvironmentFunctionDeclarations || 416 lexicalEnvironmentStatements) { 417 if (lexicalEnvironmentFunctionDeclarations) { 418 statements = [...lexicalEnvironmentFunctionDeclarations]; 419 } 420 421 if (lexicalEnvironmentVariableDeclarations) { 422 const statement = factory.createVariableStatement( 423 /*modifiers*/ undefined, 424 factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) 425 ); 426 427 setEmitFlags(statement, EmitFlags.CustomPrologue); 428 429 if (!statements) { 430 statements = [statement]; 431 } 432 else { 433 statements.push(statement); 434 } 435 } 436 437 if (lexicalEnvironmentStatements) { 438 if (!statements) { 439 statements = [...lexicalEnvironmentStatements]; 440 } 441 else { 442 statements = [...statements, ...lexicalEnvironmentStatements]; 443 } 444 } 445 } 446 447 // Restore the previous lexical environment. 448 lexicalEnvironmentStackOffset--; 449 lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; 450 lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; 451 lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset]; 452 lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset]; 453 if (lexicalEnvironmentStackOffset === 0) { 454 lexicalEnvironmentVariableDeclarationsStack = []; 455 lexicalEnvironmentFunctionDeclarationsStack = []; 456 lexicalEnvironmentStatementsStack = []; 457 lexicalEnvironmentFlagsStack = []; 458 } 459 return statements; 460 } 461 462 function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void { 463 lexicalEnvironmentFlags = value ? 464 lexicalEnvironmentFlags | flags : 465 lexicalEnvironmentFlags & ~flags; 466 } 467 468 function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags { 469 return lexicalEnvironmentFlags; 470 } 471 472 function requestEmitHelper(helper: EmitHelper): void { 473 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); 474 Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); 475 Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); 476 if (helper.dependencies) { 477 for (const h of helper.dependencies) { 478 requestEmitHelper(h); 479 } 480 } 481 emitHelpers = append(emitHelpers, helper); 482 } 483 484 function readEmitHelpers(): EmitHelper[] | undefined { 485 Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); 486 Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); 487 const helpers = emitHelpers; 488 emitHelpers = undefined; 489 return helpers; 490 } 491 492 function dispose() { 493 if (state < TransformationState.Disposed) { 494 // Clean up emit nodes on parse tree 495 for (const node of nodes) { 496 disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); 497 } 498 499 // Release references to external entries for GC purposes. 500 lexicalEnvironmentVariableDeclarations = undefined!; 501 lexicalEnvironmentVariableDeclarationsStack = undefined!; 502 lexicalEnvironmentFunctionDeclarations = undefined!; 503 lexicalEnvironmentFunctionDeclarationsStack = undefined!; 504 onSubstituteNode = undefined!; 505 onEmitNode = undefined!; 506 emitHelpers = undefined; 507 508 // Prevent further use of the transformation result. 509 state = TransformationState.Disposed; 510 } 511 } 512 } 513 514 export const nullTransformationContext: TransformationContext = { 515 get factory() { return factory; }, 516 enableEmitNotification: noop, 517 enableSubstitution: noop, 518 endLexicalEnvironment: returnUndefined, 519 getCompilerOptions: () => ({}), 520 getEmitHost: notImplemented, 521 getEmitResolver: notImplemented, 522 getEmitHelperFactory: notImplemented, 523 setLexicalEnvironmentFlags: noop, 524 getLexicalEnvironmentFlags: () => 0, 525 hoistFunctionDeclaration: noop, 526 hoistVariableDeclaration: noop, 527 addInitializationStatement: noop, 528 isEmitNotificationEnabled: notImplemented, 529 isSubstitutionEnabled: notImplemented, 530 onEmitNode: noop, 531 onSubstituteNode: notImplemented, 532 readEmitHelpers: notImplemented, 533 requestEmitHelper: noop, 534 resumeLexicalEnvironment: noop, 535 startLexicalEnvironment: noop, 536 suspendLexicalEnvironment: noop, 537 addDiagnostic: noop, 538 }; 539} 540