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