/*@internal*/
namespace ts {
    export function transformLegacyDecorators(context: TransformationContext) {
        const {
            factory,
            getEmitHelperFactory: emitHelpers,
            hoistVariableDeclaration,
        } = context;

        const resolver = context.getEmitResolver();
        const compilerOptions = context.getCompilerOptions();
        const languageVersion = getEmitScriptTarget(compilerOptions);

        // Save the previous transformation hooks.
        const previousOnSubstituteNode = context.onSubstituteNode;

        // Set new transformation hooks.
        context.onSubstituteNode = onSubstituteNode;

        /**
         * A map that keeps track of aliases created for classes with decorators to avoid issues
         * with the double-binding behavior of classes.
         */
        let classAliases: Identifier[];

        return chainBundle(context, transformSourceFile);

        function transformSourceFile(node: SourceFile) {
            const visited = visitEachChild(node, visitor, context);
            addEmitHelpers(visited, context.readEmitHelpers());
            return visited;
        }

        function modifierVisitor(node: Node): VisitResult<Node> {
            return isDecorator(node) ? undefined : node;
        }

        function visitor(node: Node): VisitResult<Node> {
            if (!(node.transformFlags & TransformFlags.ContainsDecorators)) {
                return node;
            }

            switch (node.kind) {
                case SyntaxKind.Decorator:
                    // Decorators are elided. They will be emitted as part of `visitClassDeclaration`.
                    return undefined;
                case SyntaxKind.ClassDeclaration:
                    return visitClassDeclaration(node as ClassDeclaration);
                case SyntaxKind.ClassExpression:
                    return visitClassExpression(node as ClassExpression);
                case SyntaxKind.Constructor:
                    return visitConstructorDeclaration(node as ConstructorDeclaration);
                case SyntaxKind.MethodDeclaration:
                    return visitMethodDeclaration(node as MethodDeclaration);
                case SyntaxKind.SetAccessor:
                    return visitSetAccessorDeclaration(node as SetAccessorDeclaration);
                case SyntaxKind.GetAccessor:
                    return visitGetAccessorDeclaration(node as GetAccessorDeclaration);
                case SyntaxKind.PropertyDeclaration:
                    return visitPropertyDeclaration(node as PropertyDeclaration);
                case SyntaxKind.Parameter:
                    return visitParameterDeclaration(node as ParameterDeclaration);
                default:
                    return visitEachChild(node, visitor, context);
            }
        }

        function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> {
            if (!(classOrConstructorParameterIsDecorated(node) || childIsDecorated(node))) return visitEachChild(node, visitor, context);

            const statements = hasDecorators(node) ?
                transformClassDeclarationWithClassDecorators(node, node.name) :
                transformClassDeclarationWithoutClassDecorators(node, node.name);

            if (statements.length > 1) {
                // Add a DeclarationMarker as a marker for the end of the declaration
                statements.push(factory.createEndOfDeclarationMarker(node));
                setEmitFlags(statements[0], getEmitFlags(statements[0]) | EmitFlags.HasEndOfDeclarationMarker);
            }

            return singleOrMany(statements);
        }

        function decoratorContainsPrivateIdentifierInExpression(decorator: Decorator) {
            return !!(decorator.transformFlags & TransformFlags.ContainsPrivateIdentifierInExpression);
        }

        function parameterDecoratorsContainPrivateIdentifierInExpression(parameterDecorators: readonly Decorator[] | undefined) {
            return some(parameterDecorators, decoratorContainsPrivateIdentifierInExpression);
        }

        function hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node: ClassDeclaration) {
            for (const member of node.members) {
                if (!canHaveDecorators(member)) continue;
                const allDecorators = getAllDecoratorsOfClassElement(member, node);
                if (some(allDecorators?.decorators, decoratorContainsPrivateIdentifierInExpression)) return true;
                if (some(allDecorators?.parameters, parameterDecoratorsContainPrivateIdentifierInExpression)) return true;
            }
            return false;
        }

        function transformDecoratorsOfClassElements(node: ClassDeclaration, members: NodeArray<ClassElement>) {
            let decorationStatements: Statement[] | undefined = [];
            addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ false);
            addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ true);
            if (hasClassElementWithDecoratorContainingPrivateIdentifierInExpression(node)) {
                members = setTextRange(factory.createNodeArray([
                    ...members,
                    factory.createClassStaticBlockDeclaration(
                        factory.createBlock(decorationStatements, /*multiLine*/ true)
                    )
                ]), members);
                decorationStatements = undefined;
            }
            return { decorationStatements, members };
        }

        /**
         * Transforms a non-decorated class declaration.
         *
         * @param node A ClassDeclaration node.
         * @param name The name of the class.
         */
        function transformClassDeclarationWithoutClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
            //  ${modifiers} class ${name} ${heritageClauses} {
            //      ${members}
            //  }

            const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
            const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
            let members = visitNodes(node.members, visitor, isClassElement);

            let decorationStatements: Statement[] | undefined = [];
            ({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));

            const updated = factory.updateClassDeclaration(
                node,
                modifiers,
                name,
                /*typeParameters*/ undefined,
                heritageClauses,
                members
            );

            return addRange([updated], decorationStatements);
        }

        /**
         * Transforms a decorated class declaration and appends the resulting statements. If
         * the class requires an alias to avoid issues with double-binding, the alias is returned.
         */
        function transformClassDeclarationWithClassDecorators(node: ClassDeclaration, name: Identifier | undefined) {
            // When we emit an ES6 class that has a class decorator, we must tailor the
            // emit to certain specific cases.
            //
            // In the simplest case, we emit the class declaration as a let declaration, and
            // evaluate decorators after the close of the class body:
            //
            //  [Example 1]
            //  ---------------------------------------------------------------------
            //  TypeScript                      | Javascript
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = class C {
            //  class C {                       | }
            //  }                               | C = __decorate([dec], C);
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = class C {
            //  export class C {                | }
            //  }                               | C = __decorate([dec], C);
            //                                  | export { C };
            //  ---------------------------------------------------------------------
            //
            // If a class declaration contains a reference to itself *inside* of the class body,
            // this introduces two bindings to the class: One outside of the class body, and one
            // inside of the class body. If we apply decorators as in [Example 1] above, there
            // is the possibility that the decorator `dec` will return a new value for the
            // constructor, which would result in the binding inside of the class no longer
            // pointing to the same reference as the binding outside of the class.
            //
            // As a result, we must instead rewrite all references to the class *inside* of the
            // class body to instead point to a local temporary alias for the class:
            //
            //  [Example 2]
            //  ---------------------------------------------------------------------
            //  TypeScript                      | Javascript
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = C_1 = class C {
            //  class C {                       |   static x() { return C_1.y; }
            //    static x() { return C.y; }    | }
            //    static y = 1;                 | C.y = 1;
            //  }                               | C = C_1 = __decorate([dec], C);
            //                                  | var C_1;
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = class C {
            //  export class C {                |   static x() { return C_1.y; }
            //    static x() { return C.y; }    | }
            //    static y = 1;                 | C.y = 1;
            //  }                               | C = C_1 = __decorate([dec], C);
            //                                  | export { C };
            //                                  | var C_1;
            //  ---------------------------------------------------------------------
            //
            // If a class declaration is the default export of a module, we instead emit
            // the export after the decorated declaration:
            //
            //  [Example 3]
            //  ---------------------------------------------------------------------
            //  TypeScript                      | Javascript
            //  ---------------------------------------------------------------------
            //  @dec                            | let default_1 = class {
            //  export default class {          | }
            //  }                               | default_1 = __decorate([dec], default_1);
            //                                  | export default default_1;
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = class C {
            //  export default class C {        | }
            //  }                               | C = __decorate([dec], C);
            //                                  | export default C;
            //  ---------------------------------------------------------------------
            //
            // If the class declaration is the default export and a reference to itself
            // inside of the class body, we must emit both an alias for the class *and*
            // move the export after the declaration:
            //
            //  [Example 4]
            //  ---------------------------------------------------------------------
            //  TypeScript                      | Javascript
            //  ---------------------------------------------------------------------
            //  @dec                            | let C = class C {
            //  export default class C {        |   static x() { return C_1.y; }
            //    static x() { return C.y; }    | }
            //    static y = 1;                 | C.y = 1;
            //  }                               | C = C_1 = __decorate([dec], C);
            //                                  | export default C;
            //                                  | var C_1;
            //  ---------------------------------------------------------------------
            //

            const location = moveRangePastModifiers(node);
            const classAlias = getClassAliasIfNeeded(node);

            // When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
            // without any block-scoped variable collision handling
            const declName = languageVersion <= ScriptTarget.ES2015 ?
                factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
                factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);

            //  ... = class ${name} ${heritageClauses} {
            //      ${members}
            //  }
            const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
            let members = visitNodes(node.members, visitor, isClassElement);

            let decorationStatements: Statement[] | undefined = [];
            ({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));

            const classExpression = factory.createClassExpression(
                /*modifiers*/ undefined,
                name,
                /*typeParameters*/ undefined,
                heritageClauses,
                members);

            setOriginalNode(classExpression, node);
            setTextRange(classExpression, location);

            //  let ${name} = ${classExpression} where name is either declaredName if the class doesn't contain self-reference
            //                                         or decoratedClassAlias if the class contain self-reference.
            const statement = factory.createVariableStatement(
                /*modifiers*/ undefined,
                factory.createVariableDeclarationList([
                    factory.createVariableDeclaration(
                        declName,
                        /*exclamationToken*/ undefined,
                        /*type*/ undefined,
                        classAlias ? factory.createAssignment(classAlias, classExpression) : classExpression
                    )
                ], NodeFlags.Let)
            );
            setOriginalNode(statement, node);
            setTextRange(statement, location);
            setCommentRange(statement, node);

            const statements: Statement[] = [statement];
            addRange(statements, decorationStatements);
            addConstructorDecorationStatement(statements, node);
            return statements;
        }

        function visitClassExpression(node: ClassExpression) {
            // Legacy decorators were not supported on class expressions
            return factory.updateClassExpression(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                node.name,
                /*typeParameters*/ undefined,
                visitNodes(node.heritageClauses, visitor, isHeritageClause),
                visitNodes(node.members, visitor, isClassElement)
            );
        }

        function visitConstructorDeclaration(node: ConstructorDeclaration) {
            return factory.updateConstructorDeclaration(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                visitNodes(node.parameters, visitor, isParameterDeclaration),
                visitNode(node.body, visitor, isBlock));
        }

        function finishClassElement(updated: ClassElement, original: ClassElement) {
            if (updated !== original) {
                // While we emit the source map for the node after skipping decorators and modifiers,
                // we need to emit the comments for the original range.
                setCommentRange(updated, original);
                setSourceMapRange(updated, moveRangePastModifiers(original));
            }
            return updated;
        }

        function visitMethodDeclaration(node: MethodDeclaration) {
            return finishClassElement(factory.updateMethodDeclaration(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                node.asteriskToken,
                visitNode(node.name, visitor, isPropertyName),
                /*questionToken*/ undefined,
                /*typeParameters*/ undefined,
                visitNodes(node.parameters, visitor, isParameterDeclaration),
                /*type*/ undefined,
                visitNode(node.body, visitor, isBlock)
            ), node);
        }

        function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
            return finishClassElement(factory.updateGetAccessorDeclaration(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                visitNode(node.name, visitor, isPropertyName),
                visitNodes(node.parameters, visitor, isParameterDeclaration),
                /*type*/ undefined,
                visitNode(node.body, visitor, isBlock)
            ), node);
        }

        function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
            return finishClassElement(factory.updateSetAccessorDeclaration(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                visitNode(node.name, visitor, isPropertyName),
                visitNodes(node.parameters, visitor, isParameterDeclaration),
                visitNode(node.body, visitor, isBlock)
            ), node);
        }

        function visitPropertyDeclaration(node: PropertyDeclaration) {
            if (node.flags & NodeFlags.Ambient || hasSyntacticModifier(node, ModifierFlags.Ambient)) {
                return undefined;
            }

            return finishClassElement(factory.updatePropertyDeclaration(
                node,
                visitNodes(node.modifiers, modifierVisitor, isModifier),
                visitNode(node.name, visitor, isPropertyName),
                /*questionOrExclamationToken*/ undefined,
                /*type*/ undefined,
                visitNode(node.initializer, visitor, isExpression)
            ), node);
        }

        function visitParameterDeclaration(node: ParameterDeclaration) {
            const updated = factory.updateParameterDeclaration(
                node,
                elideNodes(factory, node.modifiers),
                node.dotDotDotToken,
                visitNode(node.name, visitor, isBindingName),
                /*questionToken*/ undefined,
                /*type*/ undefined,
                visitNode(node.initializer, visitor, isExpression)
            );
            if (updated !== node) {
                // While we emit the source map for the node after skipping decorators and modifiers,
                // we need to emit the comments for the original range.
                setCommentRange(updated, node);
                setTextRange(updated, moveRangePastModifiers(node));
                setSourceMapRange(updated, moveRangePastModifiers(node));
                setEmitFlags(updated.name, EmitFlags.NoTrailingSourceMap);
            }
            return updated;
        }

        /**
         * Transforms all of the decorators for a declaration into an array of expressions.
         *
         * @param allDecorators An object containing all of the decorators for the declaration.
         */
        function transformAllDecoratorsOfDeclaration(allDecorators: AllDecorators | undefined) {
            if (!allDecorators) {
                return undefined;
            }

            const decoratorExpressions: Expression[] = [];
            addRange(decoratorExpressions, map(allDecorators.decorators, transformDecorator));
            addRange(decoratorExpressions, flatMap(allDecorators.parameters, transformDecoratorsOfParameter));
            return decoratorExpressions;
        }

        /**
         * Generates statements used to apply decorators to either the static or instance members
         * of a class.
         *
         * @param node The class node.
         * @param isStatic A value indicating whether to generate statements for static or
         *                 instance members.
         */
        function addClassElementDecorationStatements(statements: Statement[], node: ClassDeclaration, isStatic: boolean) {
            addRange(statements, map(generateClassElementDecorationExpressions(node, isStatic), expr => factory.createExpressionStatement(expr)));
        }

        /**
         * Determines whether a class member is either a static or an instance member of a class
         * that is decorated, or has parameters that are decorated.
         *
         * @param member The class member.
         */
        function isDecoratedClassElement(member: ClassElement, isStaticElement: boolean, parent: ClassLikeDeclaration) {
            return nodeOrChildIsDecorated(member, parent)
                && isStaticElement === isStatic(member);
        }

        /**
         * Gets either the static or instance members of a class that are decorated, or have
         * parameters that are decorated.
         *
         * @param node The class containing the member.
         * @param isStatic A value indicating whether to retrieve static or instance members of
         *                 the class.
         */
        function getDecoratedClassElements(node: ClassExpression | ClassDeclaration, isStatic: boolean): readonly ClassElement[] {
            return filter(node.members, m => isDecoratedClassElement(m, isStatic, node));
        }

        /**
         * Generates expressions used to apply decorators to either the static or instance members
         * of a class.
         *
         * @param node The class node.
         * @param isStatic A value indicating whether to generate expressions for static or
         *                 instance members.
         */
        function generateClassElementDecorationExpressions(node: ClassExpression | ClassDeclaration, isStatic: boolean) {
            const members = getDecoratedClassElements(node, isStatic);
            let expressions: Expression[] | undefined;
            for (const member of members) {
                expressions = append(expressions, generateClassElementDecorationExpression(node, member));
            }
            return expressions;
        }

        /**
         * Generates an expression used to evaluate class element decorators at runtime.
         *
         * @param node The class node that contains the member.
         * @param member The class member.
         */
        function generateClassElementDecorationExpression(node: ClassExpression | ClassDeclaration, member: ClassElement) {
            const allDecorators = getAllDecoratorsOfClassElement(member, node);
            const decoratorExpressions = transformAllDecoratorsOfDeclaration(allDecorators);
            if (!decoratorExpressions) {
                return undefined;
            }

            // Emit the call to __decorate. Given the following:
            //
            //   class C {
            //     @dec method(@dec2 x) {}
            //     @dec get accessor() {}
            //     @dec prop;
            //   }
            //
            // The emit for a method is:
            //
            //   __decorate([
            //       dec,
            //       __param(0, dec2),
            //       __metadata("design:type", Function),
            //       __metadata("design:paramtypes", [Object]),
            //       __metadata("design:returntype", void 0)
            //   ], C.prototype, "method", null);
            //
            // The emit for an accessor is:
            //
            //   __decorate([
            //       dec
            //   ], C.prototype, "accessor", null);
            //
            // The emit for a property is:
            //
            //   __decorate([
            //       dec
            //   ], C.prototype, "prop");
            //

            const prefix = getClassMemberPrefix(node, member);
            const memberName = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ !hasSyntacticModifier(member, ModifierFlags.Ambient));
            const descriptor = languageVersion > ScriptTarget.ES3
                ? isPropertyDeclaration(member) && !hasAccessorModifier(member)
                    // We emit `void 0` here to indicate to `__decorate` that it can invoke `Object.defineProperty` directly, but that it
                    // should not invoke `Object.getOwnPropertyDescriptor`.
                    ? factory.createVoidZero()

                    // We emit `null` here to indicate to `__decorate` that it can invoke `Object.getOwnPropertyDescriptor` directly.
                    // We have this extra argument here so that we can inject an explicit property descriptor at a later date.
                    : factory.createNull()
                : undefined;

            const helper = emitHelpers().createDecorateHelper(
                decoratorExpressions,
                prefix,
                memberName,
                descriptor
            );

            setEmitFlags(helper, EmitFlags.NoComments);
            setSourceMapRange(helper, moveRangePastModifiers(member));
            return helper;
        }

        /**
         * Generates a __decorate helper call for a class constructor.
         *
         * @param node The class node.
         */
        function addConstructorDecorationStatement(statements: Statement[], node: ClassDeclaration) {
            const expression = generateConstructorDecorationExpression(node);
            if (expression) {
                statements.push(setOriginalNode(factory.createExpressionStatement(expression), node));
            }
        }

        /**
         * Generates a __decorate helper call for a class constructor.
         *
         * @param node The class node.
         */
        function generateConstructorDecorationExpression(node: ClassExpression | ClassDeclaration) {
            const allDecorators = getAllDecoratorsOfClass(node);
            const decoratorExpressions = transformAllDecoratorsOfDeclaration(allDecorators);
            if (!decoratorExpressions) {
                return undefined;
            }

            const classAlias = classAliases && classAliases[getOriginalNodeId(node)];

            // When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
            // without any block-scoped variable collision handling
            const localName = languageVersion <= ScriptTarget.ES2015 ?
                factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
                factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
            const decorate = emitHelpers().createDecorateHelper(decoratorExpressions, localName);
            const expression = factory.createAssignment(localName, classAlias ? factory.createAssignment(classAlias, decorate) : decorate);
            setEmitFlags(expression, EmitFlags.NoComments);
            setSourceMapRange(expression, moveRangePastModifiers(node));
            return expression;
        }

        /**
         * Transforms a decorator into an expression.
         *
         * @param decorator The decorator node.
         */
        function transformDecorator(decorator: Decorator) {
            return visitNode(decorator.expression, visitor, isExpression);
        }

        /**
         * Transforms the decorators of a parameter.
         *
         * @param decorators The decorators for the parameter at the provided offset.
         * @param parameterOffset The offset of the parameter.
         */
        function transformDecoratorsOfParameter(decorators: Decorator[], parameterOffset: number) {
            let expressions: Expression[] | undefined;
            if (decorators) {
                expressions = [];
                for (const decorator of decorators) {
                    const helper = emitHelpers().createParamHelper(
                        transformDecorator(decorator),
                        parameterOffset);
                    setTextRange(helper, decorator.expression);
                    setEmitFlags(helper, EmitFlags.NoComments);
                    expressions.push(helper);
                }
            }

            return expressions;
        }

        /**
         * Gets an expression that represents a property name (for decorated properties or enums).
         * For a computed property, a name is generated for the node.
         *
         * @param member The member whose name should be converted into an expression.
         */
        function getExpressionForPropertyName(member: ClassElement | EnumMember, generateNameForComputedPropertyName: boolean): Expression {
            const name = member.name!;
            if (isPrivateIdentifier(name)) {
                return factory.createIdentifier("");
            }
            else if (isComputedPropertyName(name)) {
                return generateNameForComputedPropertyName && !isSimpleInlineableExpression(name.expression)
                    ? factory.getGeneratedNameForNode(name)
                    : name.expression;
            }
            else if (isIdentifier(name)) {
                return factory.createStringLiteral(idText(name));
            }
            else {
                return factory.cloneNode(name);
            }
        }

        function enableSubstitutionForClassAliases() {
            if (!classAliases) {
                // We need to enable substitutions for identifiers. This allows us to
                // substitute class names inside of a class declaration.
                context.enableSubstitution(SyntaxKind.Identifier);

                // Keep track of class aliases.
                classAliases = [];
            }
        }

        /**
         * Gets a local alias for a class declaration if it is a decorated class with an internal
         * reference to the static side of the class. This is necessary to avoid issues with
         * double-binding semantics for the class name.
         */
        function getClassAliasIfNeeded(node: ClassDeclaration) {
            if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) {
                enableSubstitutionForClassAliases();
                const classAlias = factory.createUniqueName(node.name && !isGeneratedIdentifier(node.name) ? idText(node.name) : "default");
                classAliases[getOriginalNodeId(node)] = classAlias;
                hoistVariableDeclaration(classAlias);
                return classAlias;
            }
        }

        function getClassPrototype(node: ClassExpression | ClassDeclaration) {
            return factory.createPropertyAccessExpression(factory.getDeclarationName(node), "prototype");
        }

        function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
            return isStatic(member)
                ? factory.getDeclarationName(node)
                : getClassPrototype(node);
        }

        /**
         * Hooks node substitutions.
         *
         * @param hint A hint as to the intended usage of the node.
         * @param node The node to substitute.
         */
        function onSubstituteNode(hint: EmitHint, node: Node) {
            node = previousOnSubstituteNode(hint, node);
            if (hint === EmitHint.Expression) {
                return substituteExpression(node as Expression);
            }
            return node;
        }

        function substituteExpression(node: Expression) {
            switch (node.kind) {
                case SyntaxKind.Identifier:
                    return substituteExpressionIdentifier(node as Identifier);
            }

            return node;
        }

        function substituteExpressionIdentifier(node: Identifier): Expression {
            return trySubstituteClassAlias(node)
                ?? node;
        }

        function trySubstituteClassAlias(node: Identifier): Expression | undefined {
            if (classAliases) {
                if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
                    // Due to the emit for class decorators, any reference to the class from inside of the class body
                    // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
                    // behavior of class names in ES6.
                    // Also, when emitting statics for class expressions, we must substitute a class alias for
                    // constructor references in static property initializers.
                    const declaration = resolver.getReferencedValueDeclaration(node);
                    if (declaration) {
                        const classAlias = classAliases[declaration.id!]; // TODO: GH#18217
                        if (classAlias) {
                            const clone = factory.cloneNode(classAlias);
                            setSourceMapRange(clone, node);
                            setCommentRange(clone, node);
                            return clone;
                        }
                    }
                }
            }

            return undefined;
        }
    }
}