• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export function transformLegacyDecorators(context: TransformationContext) {
4        const {
5            factory,
6            getEmitHelperFactory: emitHelpers,
7            hoistVariableDeclaration,
8        } = context;
9
10        const resolver = context.getEmitResolver();
11        const compilerOptions = context.getCompilerOptions();
12        const languageVersion = getEmitScriptTarget(compilerOptions);
13
14        // Save the previous transformation hooks.
15        const previousOnSubstituteNode = context.onSubstituteNode;
16
17        // Set new transformation hooks.
18        context.onSubstituteNode = onSubstituteNode;
19
20        /**
21         * A map that keeps track of aliases created for classes with decorators to avoid issues
22         * with the double-binding behavior of classes.
23         */
24        let classAliases: Identifier[];
25
26        return chainBundle(context, transformSourceFile);
27
28        function transformSourceFile(node: SourceFile) {
29            const visited = visitEachChild(node, visitor, context);
30            addEmitHelpers(visited, context.readEmitHelpers());
31            return visited;
32        }
33
34        function modifierVisitor(node: Node): VisitResult<Node> {
35            return isDecorator(node) || isAnnotation(node) ? undefined : node;
36        }
37
38        function visitor(node: Node): VisitResult<Node> {
39            if (!(node.transformFlags & TransformFlags.ContainsDecorators)) {
40                return node;
41            }
42
43            switch (node.kind) {
44                case SyntaxKind.Decorator:
45                    // Decorators are elided. They will be emitted as part of `visitClassDeclaration`.
46                    return undefined;
47                case SyntaxKind.ClassDeclaration:
48                    return visitClassDeclaration(node as ClassDeclaration);
49                case SyntaxKind.ClassExpression:
50                    return visitClassExpression(node as ClassExpression);
51                case SyntaxKind.Constructor:
52                    return visitConstructorDeclaration(node as ConstructorDeclaration);
53                case SyntaxKind.MethodDeclaration:
54                    return visitMethodDeclaration(node as MethodDeclaration);
55                case SyntaxKind.SetAccessor:
56                    return visitSetAccessorDeclaration(node as SetAccessorDeclaration);
57                case SyntaxKind.GetAccessor:
58                    return visitGetAccessorDeclaration(node as GetAccessorDeclaration);
59                case SyntaxKind.PropertyDeclaration:
60                    return visitPropertyDeclaration(node as PropertyDeclaration);
61                case SyntaxKind.Parameter:
62                    return visitParameterDeclaration(node as ParameterDeclaration);
63                default:
64                    return visitEachChild(node, visitor, context);
65            }
66        }
67
68        function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> {
69            if (!(classOrConstructorParameterIsDecorated(node) || childIsDecorated(node))) return visitEachChild(node, visitor, context);
70
71            const statements = hasDecorators(node) ?
72                transformClassDeclarationWithClassDecorators(node, node.name) :
73                transformClassDeclarationWithoutClassDecorators(node, node.name);
74
75            if (statements.length > 1) {
76                // Add a DeclarationMarker as a marker for the end of the declaration
77                statements.push(factory.createEndOfDeclarationMarker(node));
78                setEmitFlags(statements[0], getEmitFlags(statements[0]) | EmitFlags.HasEndOfDeclarationMarker);
79            }
80
81            return singleOrMany(statements);
82        }
83
84        function decoratorContainsPrivateIdentifierInExpression(decorator: Decorator) {
85            return !!(decorator.transformFlags & TransformFlags.ContainsPrivateIdentifierInExpression);
86        }
87
88        function parameterDecoratorsContainPrivateIdentifierInExpression(parameterDecorators: readonly Decorator[] | undefined) {
89            return some(parameterDecorators, decoratorContainsPrivateIdentifierInExpression);
90        }
91
92        function hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node: ClassDeclaration) {
93            for (const member of node.members) {
94                if (!canHaveDecorators(member)) continue;
95                const allDecorators = getAllDecoratorsOfClassElement(member, node);
96                if (some(allDecorators?.decorators, decoratorContainsPrivateIdentifierInExpression)) return true;
97                if (some(allDecorators?.parameters, parameterDecoratorsContainPrivateIdentifierInExpression)) return true;
98            }
99            return false;
100        }
101
102        function transformDecoratorsOfClassElements(node: ClassDeclaration, members: NodeArray<ClassElement>) {
103            let decorationStatements: Statement[] | undefined = [];
104            addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ false);
105            addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ true);
106            if (hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node)) {
107                members = setTextRange(factory.createNodeArray([
108                    ...members,
109                    factory.createClassStaticBlockDeclaration(
110                        factory.createBlock(decorationStatements, /*multiLine*/ true)
111                    )
112                ]), members);
113                decorationStatements = undefined;
114            }
115            return { decorationStatements, members };
116        }
117
118        /**
119         * Transforms a non-decorated class declaration.
120         *
121         * @param node A ClassDeclaration node.
122         * @param name The name of the class.
123         */
124        function transformClassDeclarationWithoutClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
125            //  ${modifiers} class ${name} ${heritageClauses} {
126            //      ${members}
127            //  }
128
129            const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
130            const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
131            let members = visitNodes(node.members, visitor, isClassElement);
132
133            let decorationStatements: Statement[] | undefined = [];
134            ({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));
135
136            const updated = factory.updateClassDeclaration(
137                node,
138                modifiers,
139                name,
140                /*typeParameters*/ undefined,
141                heritageClauses,
142                members
143            );
144
145            return addRange([updated], decorationStatements);
146        }
147
148        /**
149         * Transforms a decorated class declaration and appends the resulting statements. If
150         * the class requires an alias to avoid issues with double-binding, the alias is returned.
151         */
152        function transformClassDeclarationWithClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
153            // When we emit an ES6 class that has a class decorator, we must tailor the
154            // emit to certain specific cases.
155            //
156            // In the simplest case, we emit the class declaration as a let declaration, and
157            // evaluate decorators after the close of the class body:
158            //
159            //  [Example 1]
160            //  ---------------------------------------------------------------------
161            //  TypeScript                      | Javascript
162            //  ---------------------------------------------------------------------
163            //  @dec                            | let C = class C {
164            //  class C {                       | }
165            //  }                               | C = __decorate([dec], C);
166            //  ---------------------------------------------------------------------
167            //  @dec                            | let C = class C {
168            //  export class C {                | }
169            //  }                               | C = __decorate([dec], C);
170            //                                  | export { C };
171            //  ---------------------------------------------------------------------
172            //
173            // If a class declaration contains a reference to itself *inside* of the class body,
174            // this introduces two bindings to the class: One outside of the class body, and one
175            // inside of the class body. If we apply decorators as in [Example 1] above, there
176            // is the possibility that the decorator `dec` will return a new value for the
177            // constructor, which would result in the binding inside of the class no longer
178            // pointing to the same reference as the binding outside of the class.
179            //
180            // As a result, we must instead rewrite all references to the class *inside* of the
181            // class body to instead point to a local temporary alias for the class:
182            //
183            //  [Example 2]
184            //  ---------------------------------------------------------------------
185            //  TypeScript                      | Javascript
186            //  ---------------------------------------------------------------------
187            //  @dec                            | let C = C_1 = class C {
188            //  class C {                       |   static x() { return C_1.y; }
189            //    static x() { return C.y; }    | }
190            //    static y = 1;                 | C.y = 1;
191            //  }                               | C = C_1 = __decorate([dec], C);
192            //                                  | var C_1;
193            //  ---------------------------------------------------------------------
194            //  @dec                            | let C = class C {
195            //  export class C {                |   static x() { return C_1.y; }
196            //    static x() { return C.y; }    | }
197            //    static y = 1;                 | C.y = 1;
198            //  }                               | C = C_1 = __decorate([dec], C);
199            //                                  | export { C };
200            //                                  | var C_1;
201            //  ---------------------------------------------------------------------
202            //
203            // If a class declaration is the default export of a module, we instead emit
204            // the export after the decorated declaration:
205            //
206            //  [Example 3]
207            //  ---------------------------------------------------------------------
208            //  TypeScript                      | Javascript
209            //  ---------------------------------------------------------------------
210            //  @dec                            | let default_1 = class {
211            //  export default class {          | }
212            //  }                               | default_1 = __decorate([dec], default_1);
213            //                                  | export default default_1;
214            //  ---------------------------------------------------------------------
215            //  @dec                            | let C = class C {
216            //  export default class C {        | }
217            //  }                               | C = __decorate([dec], C);
218            //                                  | export default C;
219            //  ---------------------------------------------------------------------
220            //
221            // If the class declaration is the default export and a reference to itself
222            // inside of the class body, we must emit both an alias for the class *and*
223            // move the export after the declaration:
224            //
225            //  [Example 4]
226            //  ---------------------------------------------------------------------
227            //  TypeScript                      | Javascript
228            //  ---------------------------------------------------------------------
229            //  @dec                            | let C = class C {
230            //  export default class C {        |   static x() { return C_1.y; }
231            //    static x() { return C.y; }    | }
232            //    static y = 1;                 | C.y = 1;
233            //  }                               | C = C_1 = __decorate([dec], C);
234            //                                  | export default C;
235            //                                  | var C_1;
236            //  ---------------------------------------------------------------------
237            //
238
239            const location = moveRangePastModifiers(node);
240            const classAlias = getClassAliasIfNeeded(node);
241
242            // When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
243            // without any block-scoped variable collision handling
244            const declName = languageVersion <= ScriptTarget.ES2015 ?
245                factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
246                factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
247
248            //  ... = class ${name} ${heritageClauses} {
249            //      ${members}
250            //  }
251            const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
252            let members = visitNodes(node.members, visitor, isClassElement);
253
254            let decorationStatements: Statement[] | undefined = [];
255            ({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));
256
257            const classExpression = factory.createClassExpression(
258                /*modifiers*/ undefined,
259                name,
260                /*typeParameters*/ undefined,
261                heritageClauses,
262                members);
263
264            setOriginalNode(classExpression, node);
265            setTextRange(classExpression, location);
266
267            //  let ${name} = ${classExpression} where name is either declaredName if the class doesn't contain self-reference
268            //                                         or decoratedClassAlias if the class contain self-reference.
269            const statement = factory.createVariableStatement(
270                /*modifiers*/ undefined,
271                factory.createVariableDeclarationList([
272                    factory.createVariableDeclaration(
273                        declName,
274                        /*exclamationToken*/ undefined,
275                        /*type*/ undefined,
276                        classAlias ? factory.createAssignment(classAlias, classExpression) : classExpression
277                    )
278                ], NodeFlags.Let)
279            );
280            setOriginalNode(statement, node);
281            setTextRange(statement, location);
282            setCommentRange(statement, node);
283
284            const statements: Statement[] = [statement];
285            addRange(statements, decorationStatements);
286            addConstructorDecorationStatement(statements, node);
287            return statements;
288        }
289
290        function visitClassExpression(node: ClassExpression) {
291            // Legacy decorators were not supported on class expressions
292            return factory.updateClassExpression(
293                node,
294                visitNodes(node.modifiers, modifierVisitor, isModifier),
295                node.name,
296                /*typeParameters*/ undefined,
297                visitNodes(node.heritageClauses, visitor, isHeritageClause),
298                visitNodes(node.members, visitor, isClassElement)
299            );
300        }
301
302        function visitConstructorDeclaration(node: ConstructorDeclaration) {
303            return factory.updateConstructorDeclaration(
304                node,
305                visitNodes(node.modifiers, modifierVisitor, isModifier),
306                visitNodes(node.parameters, visitor, isParameterDeclaration),
307                visitNode(node.body, visitor, isBlock));
308        }
309
310        function finishClassElement(updated: ClassElement, original: ClassElement) {
311            if (updated !== original) {
312                // While we emit the source map for the node after skipping decorators and modifiers,
313                // we need to emit the comments for the original range.
314                setCommentRange(updated, original);
315                setSourceMapRange(updated, moveRangePastModifiers(original));
316            }
317            return updated;
318        }
319
320        function visitMethodDeclaration(node: MethodDeclaration) {
321            return finishClassElement(factory.updateMethodDeclaration(
322                node,
323                visitNodes(node.modifiers, modifierVisitor, isModifier),
324                node.asteriskToken,
325                visitNode(node.name, visitor, isPropertyName),
326                /*questionToken*/ undefined,
327                /*typeParameters*/ undefined,
328                visitNodes(node.parameters, visitor, isParameterDeclaration),
329                /*type*/ undefined,
330                visitNode(node.body, visitor, isBlock)
331            ), node);
332        }
333
334        function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
335            return finishClassElement(factory.updateGetAccessorDeclaration(
336                node,
337                visitNodes(node.modifiers, modifierVisitor, isModifier),
338                visitNode(node.name, visitor, isPropertyName),
339                visitNodes(node.parameters, visitor, isParameterDeclaration),
340                /*type*/ undefined,
341                visitNode(node.body, visitor, isBlock)
342            ), node);
343        }
344
345        function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
346            return finishClassElement(factory.updateSetAccessorDeclaration(
347                node,
348                visitNodes(node.modifiers, modifierVisitor, isModifier),
349                visitNode(node.name, visitor, isPropertyName),
350                visitNodes(node.parameters, visitor, isParameterDeclaration),
351                visitNode(node.body, visitor, isBlock)
352            ), node);
353        }
354
355        function visitPropertyDeclaration(node: PropertyDeclaration) {
356            if (node.flags & NodeFlags.Ambient || hasSyntacticModifier(node, ModifierFlags.Ambient)) {
357                return undefined;
358            }
359
360            return finishClassElement(factory.updatePropertyDeclaration(
361                node,
362                visitNodes(node.modifiers, modifierVisitor, isModifier),
363                visitNode(node.name, visitor, isPropertyName),
364                /*questionOrExclamationToken*/ undefined,
365                /*type*/ undefined,
366                visitNode(node.initializer, visitor, isExpression)
367            ), node);
368        }
369
370        function visitParameterDeclaration(node: ParameterDeclaration) {
371            const updated = factory.updateParameterDeclaration(
372                node,
373                elideNodes(factory, node.modifiers),
374                node.dotDotDotToken,
375                visitNode(node.name, visitor, isBindingName),
376                /*questionToken*/ undefined,
377                /*type*/ undefined,
378                visitNode(node.initializer, visitor, isExpression)
379            );
380            if (updated !== node) {
381                // While we emit the source map for the node after skipping decorators and modifiers,
382                // we need to emit the comments for the original range.
383                setCommentRange(updated, node);
384                setTextRange(updated, moveRangePastModifiers(node));
385                setSourceMapRange(updated, moveRangePastModifiers(node));
386                setEmitFlags(updated.name, EmitFlags.NoTrailingSourceMap);
387            }
388            return updated;
389        }
390
391        /**
392         * Transforms all of the decorators for a declaration into an array of expressions.
393         *
394         * @param allDecorators An object containing all of the decorators for the declaration.
395         */
396        function transformAllDecoratorsOfDeclaration(allDecorators: AllDecorators | undefined) {
397            if (!allDecorators) {
398                return undefined;
399            }
400
401            const decoratorExpressions: Expression[] = [];
402            addRange(decoratorExpressions, map(allDecorators.decorators, transformDecorator));
403            addRange(decoratorExpressions, flatMap(allDecorators.parameters, transformDecoratorsOfParameter));
404            return decoratorExpressions;
405        }
406
407        /**
408         * Generates statements used to apply decorators to either the static or instance members
409         * of a class.
410         *
411         * @param node The class node.
412         * @param isStatic A value indicating whether to generate statements for static or
413         *                 instance members.
414         */
415        function addClassElementDecorationStatements(statements: Statement[], node: ClassDeclaration, isStatic: boolean) {
416            addRange(statements, map(generateClassElementDecorationExpressions(node, isStatic), expr => factory.createExpressionStatement(expr)));
417        }
418
419        /**
420         * Determines whether a class member is either a static or an instance member of a class
421         * that is decorated, or has parameters that are decorated.
422         *
423         * @param member The class member.
424         */
425        function isDecoratedClassElement(member: ClassElement, isStaticElement: boolean, parent: ClassLikeDeclaration) {
426            return nodeOrChildIsDecorated(member, parent)
427                && isStaticElement === isStatic(member);
428        }
429
430        /**
431         * Gets either the static or instance members of a class that are decorated, or have
432         * parameters that are decorated.
433         *
434         * @param node The class containing the member.
435         * @param isStatic A value indicating whether to retrieve static or instance members of
436         *                 the class.
437         */
438        function getDecoratedClassElements(node: ClassExpression | ClassDeclaration, isStatic: boolean): readonly ClassElement[] {
439            return filter(node.members, m => isDecoratedClassElement(m, isStatic, node));
440        }
441
442        /**
443         * Generates expressions used to apply decorators to either the static or instance members
444         * of a class.
445         *
446         * @param node The class node.
447         * @param isStatic A value indicating whether to generate expressions for static or
448         *                 instance members.
449         */
450        function generateClassElementDecorationExpressions(node: ClassExpression | ClassDeclaration, isStatic: boolean) {
451            const members = getDecoratedClassElements(node, isStatic);
452            let expressions: Expression[] | undefined;
453            for (const member of members) {
454                expressions = append(expressions, generateClassElementDecorationExpression(node, member));
455            }
456            return expressions;
457        }
458
459        /**
460         * Generates an expression used to evaluate class element decorators at runtime.
461         *
462         * @param node The class node that contains the member.
463         * @param member The class member.
464         */
465        function generateClassElementDecorationExpression(node: ClassExpression | ClassDeclaration, member: ClassElement) {
466            const allDecorators = getAllDecoratorsOfClassElement(member, node);
467            const decoratorExpressions = transformAllDecoratorsOfDeclaration(allDecorators);
468            if (!decoratorExpressions) {
469                return undefined;
470            }
471
472            // Emit the call to __decorate. Given the following:
473            //
474            //   class C {
475            //     @dec method(@dec2 x) {}
476            //     @dec get accessor() {}
477            //     @dec prop;
478            //   }
479            //
480            // The emit for a method is:
481            //
482            //   __decorate([
483            //       dec,
484            //       __param(0, dec2),
485            //       __metadata("design:type", Function),
486            //       __metadata("design:paramtypes", [Object]),
487            //       __metadata("design:returntype", void 0)
488            //   ], C.prototype, "method", null);
489            //
490            // The emit for an accessor is:
491            //
492            //   __decorate([
493            //       dec
494            //   ], C.prototype, "accessor", null);
495            //
496            // The emit for a property is:
497            //
498            //   __decorate([
499            //       dec
500            //   ], C.prototype, "prop");
501            //
502
503            const prefix = getClassMemberPrefix(node, member);
504            const memberName = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ !hasSyntacticModifier(member, ModifierFlags.Ambient));
505            const descriptor = languageVersion > ScriptTarget.ES3
506                ? isPropertyDeclaration(member) && !hasAccessorModifier(member)
507                    // We emit `void 0` here to indicate to `__decorate` that it can invoke `Object.defineProperty` directly, but that it
508                    // should not invoke `Object.getOwnPropertyDescriptor`.
509                    ? factory.createVoidZero()
510
511                    // We emit `null` here to indicate to `__decorate` that it can invoke `Object.getOwnPropertyDescriptor` directly.
512                    // We have this extra argument here so that we can inject an explicit property descriptor at a later date.
513                    : factory.createNull()
514                : undefined;
515
516            const helper = emitHelpers().createDecorateHelper(
517                decoratorExpressions,
518                prefix,
519                memberName,
520                descriptor
521            );
522
523            setEmitFlags(helper, EmitFlags.NoComments);
524            setSourceMapRange(helper, moveRangePastModifiers(member));
525            return helper;
526        }
527
528        /**
529         * Generates a __decorate helper call for a class constructor.
530         *
531         * @param node The class node.
532         */
533        function addConstructorDecorationStatement(statements: Statement[], node: ClassDeclaration) {
534            const expression = generateConstructorDecorationExpression(node);
535            if (expression) {
536                statements.push(setOriginalNode(factory.createExpressionStatement(expression), node));
537            }
538        }
539
540        /**
541         * Generates a __decorate helper call for a class constructor.
542         *
543         * @param node The class node.
544         */
545        function generateConstructorDecorationExpression(node: ClassExpression | ClassDeclaration) {
546            const allDecorators = getAllDecoratorsOfClass(node);
547            const decoratorExpressions = transformAllDecoratorsOfDeclaration(allDecorators);
548            if (!decoratorExpressions) {
549                return undefined;
550            }
551
552            const classAlias = classAliases && classAliases[getOriginalNodeId(node)];
553
554            // When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
555            // without any block-scoped variable collision handling
556            const localName = languageVersion <= ScriptTarget.ES2015 ?
557                factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
558                factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
559            const decorate = emitHelpers().createDecorateHelper(decoratorExpressions, localName);
560            const expression = factory.createAssignment(localName, classAlias ? factory.createAssignment(classAlias, decorate) : decorate);
561            setEmitFlags(expression, EmitFlags.NoComments);
562            setSourceMapRange(expression, moveRangePastModifiers(node));
563            return expression;
564        }
565
566        /**
567         * Transforms a decorator into an expression.
568         *
569         * @param decorator The decorator node.
570         */
571        function transformDecorator(decorator: Decorator) {
572            return visitNode(decorator.expression, visitor, isExpression);
573        }
574
575        /**
576         * Transforms the decorators of a parameter.
577         *
578         * @param decorators The decorators for the parameter at the provided offset.
579         * @param parameterOffset The offset of the parameter.
580         */
581        function transformDecoratorsOfParameter(decorators: Decorator[], parameterOffset: number) {
582            let expressions: Expression[] | undefined;
583            if (decorators) {
584                expressions = [];
585                for (const decorator of decorators) {
586                    const helper = emitHelpers().createParamHelper(
587                        transformDecorator(decorator),
588                        parameterOffset);
589                    setTextRange(helper, decorator.expression);
590                    setEmitFlags(helper, EmitFlags.NoComments);
591                    expressions.push(helper);
592                }
593            }
594
595            return expressions;
596        }
597
598        /**
599         * Gets an expression that represents a property name (for decorated properties or enums).
600         * For a computed property, a name is generated for the node.
601         *
602         * @param member The member whose name should be converted into an expression.
603         */
604        function getExpressionForPropertyName(member: ClassElement | EnumMember, generateNameForComputedPropertyName: boolean): Expression {
605            const name = member.name!;
606            if (isPrivateIdentifier(name)) {
607                return factory.createIdentifier("");
608            }
609            else if (isComputedPropertyName(name)) {
610                return generateNameForComputedPropertyName && !isSimpleInlineableExpression(name.expression)
611                    ? factory.getGeneratedNameForNode(name)
612                    : name.expression;
613            }
614            else if (isIdentifier(name)) {
615                return factory.createStringLiteral(idText(name));
616            }
617            else {
618                return factory.cloneNode(name);
619            }
620        }
621
622        function enableSubstitutionForClassAliases() {
623            if (!classAliases) {
624                // We need to enable substitutions for identifiers. This allows us to
625                // substitute class names inside of a class declaration.
626                context.enableSubstitution(SyntaxKind.Identifier);
627
628                // Keep track of class aliases.
629                classAliases = [];
630            }
631        }
632
633        /**
634         * Gets a local alias for a class declaration if it is a decorated class with an internal
635         * reference to the static side of the class. This is necessary to avoid issues with
636         * double-binding semantics for the class name.
637         */
638        function getClassAliasIfNeeded(node: ClassDeclaration) {
639            if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) {
640                enableSubstitutionForClassAliases();
641                const classAlias = factory.createUniqueName(node.name && !isGeneratedIdentifier(node.name) ? idText(node.name) : "default");
642                classAliases[getOriginalNodeId(node)] = classAlias;
643                hoistVariableDeclaration(classAlias);
644                return classAlias;
645            }
646        }
647
648        function getClassPrototype(node: ClassExpression | ClassDeclaration) {
649            return factory.createPropertyAccessExpression(factory.getDeclarationName(node), "prototype");
650        }
651
652        function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
653            return isStatic(member)
654                ? factory.getDeclarationName(node)
655                : getClassPrototype(node);
656        }
657
658        /**
659         * Hooks node substitutions.
660         *
661         * @param hint A hint as to the intended usage of the node.
662         * @param node The node to substitute.
663         */
664        function onSubstituteNode(hint: EmitHint, node: Node) {
665            node = previousOnSubstituteNode(hint, node);
666            if (hint === EmitHint.Expression) {
667                return substituteExpression(node as Expression);
668            }
669            return node;
670        }
671
672        function substituteExpression(node: Expression) {
673            switch (node.kind) {
674                case SyntaxKind.Identifier:
675                    return substituteExpressionIdentifier(node as Identifier);
676            }
677
678            return node;
679        }
680
681        function substituteExpressionIdentifier(node: Identifier): Expression {
682            return trySubstituteClassAlias(node)
683                ?? node;
684        }
685
686        function trySubstituteClassAlias(node: Identifier): Expression | undefined {
687            if (classAliases) {
688                if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
689                    // Due to the emit for class decorators, any reference to the class from inside of the class body
690                    // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
691                    // behavior of class names in ES6.
692                    // Also, when emitting statics for class expressions, we must substitute a class alias for
693                    // constructor references in static property initializers.
694                    const declaration = resolver.getReferencedValueDeclaration(node);
695                    if (declaration) {
696                        const classAlias = classAliases[declaration.id!]; // TODO: GH#18217
697                        if (classAlias) {
698                            const clone = factory.cloneNode(classAlias);
699                            setSourceMapRange(clone, node);
700                            setCommentRange(clone, node);
701                            return clone;
702                        }
703                    }
704                }
705            }
706
707            return undefined;
708        }
709    }
710}