/* @internal */ namespace ts { function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { switch (moduleKind) { case ModuleKind.ESNext: case ModuleKind.ES2015: return transformES2015Module; case ModuleKind.System: return transformSystemModule; default: return transformModule; } } const enum TransformationState { Uninitialized, Initialized, Completed, Disposed } const enum SyntaxKindFeatureFlags { Substitution = 1 << 0, EmitNotifications = 1 << 1, } export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { return { scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), declarationTransformers: getDeclarationTransformers(customTransformers), }; } function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean) { if (emitOnlyDtsFiles) return emptyArray; const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); const transformers: TransformerFactory[] = []; addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); transformers.push(transformTypeScript); transformers.push(transformClassFields); if (jsx === JsxEmit.React) { transformers.push(transformJsx); } if (languageVersion < ScriptTarget.ESNext) { transformers.push(transformESNext); } if (languageVersion < ScriptTarget.ES2019) { transformers.push(transformES2019); } if (languageVersion < ScriptTarget.ES2018) { transformers.push(transformES2018); } if (languageVersion < ScriptTarget.ES2017) { transformers.push(transformES2017); } if (languageVersion < ScriptTarget.ES2016) { transformers.push(transformES2016); } if (languageVersion < ScriptTarget.ES2015) { transformers.push(transformES2015); transformers.push(transformGenerators); } transformers.push(getModuleTransformer(moduleKind)); // The ES5 transformer is last so that it can substitute expressions like `exports.default` // for ES3. if (languageVersion < ScriptTarget.ES5) { transformers.push(transformES5); } addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); return transformers; } function getDeclarationTransformers(customTransformers?: CustomTransformers) { const transformers: TransformerFactory[] = []; transformers.push(transformDeclarations); addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); return transformers; } /** * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. */ function wrapCustomTransformer(transformer: CustomTransformer): Transformer { return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); } /** * Wrap a transformer factory that may return a custom script or declaration transformer object. */ function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (node: Transformer) => Transformer): TransformerFactory { return context => { const customTransformer = transformer(context); return typeof customTransformer === "function" ? handleDefault(customTransformer) : wrapCustomTransformer(customTransformer); }; } function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { return wrapCustomTransformerFactory(transformer, chainBundle); } function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { return wrapCustomTransformerFactory(transformer, identity); } export function noEmitSubstitution(_hint: EmitHint, node: Node) { return node; } export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { callback(hint, node); } /** * Transforms an array of SourceFiles by passing them through each transformer. * * @param resolver The emit resolver provided by the checker. * @param host The emit host object used to interact with the file system. * @param options Compiler options to surface in the `TransformationContext`. * @param nodes An array of nodes to transform. * @param transforms An array of `TransformerFactory` callbacks. * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. */ export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[] | undefined; let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; let state = TransformationState.Uninitialized; const diagnostics: DiagnosticWithLocation[] = []; // The transformation context is provided to each transformer as part of transformer // initialization. const context: TransformationContext = { getCompilerOptions: () => options, getEmitResolver: () => resolver!, // TODO: GH#18217 getEmitHost: () => host!, // TODO: GH#18217 startLexicalEnvironment, suspendLexicalEnvironment, resumeLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, hoistFunctionDeclaration, requestEmitHelper, readEmitHelpers, enableSubstitution, enableEmitNotification, isSubstitutionEnabled, isEmitNotificationEnabled, get onSubstituteNode() { return onSubstituteNode; }, set onSubstituteNode(value) { Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); Debug.assert(value !== undefined, "Value must not be 'undefined'"); onSubstituteNode = value; }, get onEmitNode() { return onEmitNode; }, set onEmitNode(value) { Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); Debug.assert(value !== undefined, "Value must not be 'undefined'"); onEmitNode = value; }, addDiagnostic(diag) { diagnostics.push(diag); } }; // Ensure the parse tree is clean before applying transformations for (const node of nodes) { disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } performance.mark("beforeTransform"); // Chain together and initialize each transformer. const transformersWithContext = transformers.map(t => t(context)); const transformation = (node: T): T => { for (const transform of transformersWithContext) { node = transform(node); } return node; }; // prevent modification of transformation hooks. state = TransformationState.Initialized; // Transform each node. const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot); // prevent modification of the lexical environment. state = TransformationState.Completed; performance.mark("afterTransform"); performance.measure("transformTime", "beforeTransform", "afterTransform"); return { transformed, substituteNode, emitNodeWithNotification, dispose, diagnostics }; function transformRoot(node: T) { return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; } /** * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ function enableSubstitution(kind: SyntaxKind) { Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } /** * Determines whether expression substitutions are enabled for the provided node. */ function isSubstitutionEnabled(node: Node) { return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } /** * Emits a node with possible substitution. * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. * @param emitCallback The callback used to emit the node or its substitute. */ function substituteNode(hint: EmitHint, node: Node) { Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; } /** * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. */ function enableEmitNotification(kind: SyntaxKind) { Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } /** * Determines whether before/after emit notifications should be raised in the pretty * printer when it emits a node. */ function isEmitNotificationEnabled(node: Node) { return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; } /** * Emits a node with possible emit notification. * * @param hint A hint as to the intended usage of the node. * @param node The node to emit. * @param emitCallback The callback used to emit the node. */ function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); if (node) { if (isEmitNotificationEnabled(node)) { onEmitNode(hint, node, emitCallback); } else { emitCallback(hint, node); } } } /** * Records a hoisted variable declaration for the provided name within a lexical environment. */ function hoistVariableDeclaration(name: Identifier): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); const decl = setEmitFlags(createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); if (!lexicalEnvironmentVariableDeclarations) { lexicalEnvironmentVariableDeclarations = [decl]; } else { lexicalEnvironmentVariableDeclarations.push(decl); } } /** * Records a hoisted function declaration within a lexical environment. */ function hoistFunctionDeclaration(func: FunctionDeclaration): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); if (!lexicalEnvironmentFunctionDeclarations) { lexicalEnvironmentFunctionDeclarations = [func]; } else { lexicalEnvironmentFunctionDeclarations.push(func); } } /** * Starts a new lexical environment. Any existing hoisted variable or function declarations * are pushed onto a stack, and the related storage variables are reset. */ function startLexicalEnvironment(): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); // Save the current lexical environment. Rather than resizing the array we adjust the // stack size variable. This allows us to reuse existing array slots we've // already allocated between transformations to avoid allocation and GC overhead during // transformation. lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; lexicalEnvironmentStackOffset++; lexicalEnvironmentVariableDeclarations = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; } /** Suspends the current lexical environment, usually after visiting a parameter list. */ function suspendLexicalEnvironment(): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); lexicalEnvironmentSuspended = true; } /** Resumes a suspended lexical environment, usually before visiting a function body. */ function resumeLexicalEnvironment(): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); lexicalEnvironmentSuspended = false; } /** * Ends a lexical environment. The previous set of hoisted declarations are restored and * any hoisted declarations added in this environment are returned. */ function endLexicalEnvironment(): Statement[] | undefined { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); let statements: Statement[] | undefined; if (lexicalEnvironmentVariableDeclarations || lexicalEnvironmentFunctionDeclarations) { if (lexicalEnvironmentFunctionDeclarations) { statements = [...lexicalEnvironmentFunctionDeclarations]; } if (lexicalEnvironmentVariableDeclarations) { const statement = createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) ); setEmitFlags(statement, EmitFlags.CustomPrologue); if (!statements) { statements = [statement]; } else { statements.push(statement); } } } // Restore the previous lexical environment. lexicalEnvironmentStackOffset--; lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; if (lexicalEnvironmentStackOffset === 0) { lexicalEnvironmentVariableDeclarationsStack = []; lexicalEnvironmentFunctionDeclarationsStack = []; } return statements; } function requestEmitHelper(helper: EmitHelper): void { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); emitHelpers = append(emitHelpers, helper); } function readEmitHelpers(): EmitHelper[] | undefined { Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); const helpers = emitHelpers; emitHelpers = undefined; return helpers; } function dispose() { if (state < TransformationState.Disposed) { // Clean up emit nodes on parse tree for (const node of nodes) { disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } // Release references to external entries for GC purposes. lexicalEnvironmentVariableDeclarations = undefined!; lexicalEnvironmentVariableDeclarationsStack = undefined!; lexicalEnvironmentFunctionDeclarations = undefined!; lexicalEnvironmentFunctionDeclarationsStack = undefined!; onSubstituteNode = undefined!; onEmitNode = undefined!; emitHelpers = undefined; // Prevent further use of the transformation result. state = TransformationState.Disposed; } } } }