• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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