• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    const enum ClassPropertySubstitutionFlags {
4        /**
5         * Enables substitutions for class expressions with static fields
6         * which have initializers that reference the class name.
7         */
8        ClassAliases = 1 << 0,
9    }
10
11    const enum PrivateIdentifierPlacement {
12        InstanceField
13    }
14
15    type PrivateIdentifierInfo = PrivateIdentifierInstanceField;
16
17    interface PrivateIdentifierInstanceField {
18        placement: PrivateIdentifierPlacement.InstanceField;
19        weakMapName: Identifier;
20    }
21
22    /**
23     * A mapping of private names to information needed for transformation.
24     */
25    type PrivateIdentifierEnvironment = UnderscoreEscapedMap<PrivateIdentifierInfo>;
26
27    /**
28     * Transforms ECMAScript Class Syntax.
29     * TypeScript parameter property syntax is transformed in the TypeScript transformer.
30     * For now, this transforms public field declarations using TypeScript class semantics,
31     * where declarations are elided and initializers are transformed as assignments in the constructor.
32     * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty.
33     */
34    export function transformClassFields(context: TransformationContext) {
35        const {
36            factory,
37            hoistVariableDeclaration,
38            endLexicalEnvironment,
39            resumeLexicalEnvironment
40        } = context;
41        const resolver = context.getEmitResolver();
42        const compilerOptions = context.getCompilerOptions();
43        const languageVersion = getEmitScriptTarget(compilerOptions);
44
45        const shouldTransformPrivateFields = languageVersion < ScriptTarget.ESNext;
46
47        const previousOnSubstituteNode = context.onSubstituteNode;
48        context.onSubstituteNode = onSubstituteNode;
49
50        let enabledSubstitutions: ClassPropertySubstitutionFlags;
51
52        let classAliases: Identifier[];
53
54        /**
55         * Tracks what computed name expressions originating from elided names must be inlined
56         * at the next execution site, in document order
57         */
58        let pendingExpressions: Expression[] | undefined;
59
60        /**
61         * Tracks what computed name expression statements and static property initializers must be
62         * emitted at the next execution site, in document order (for decorated classes).
63         */
64        let pendingStatements: Statement[] | undefined;
65
66        const privateIdentifierEnvironmentStack: (PrivateIdentifierEnvironment | undefined)[] = [];
67        let currentPrivateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined;
68
69        return chainBundle(context, transformSourceFile);
70
71        function transformSourceFile(node: SourceFile) {
72            const options = context.getCompilerOptions();
73            if (node.isDeclarationFile
74                || options.useDefineForClassFields && options.target === ScriptTarget.ESNext) {
75                return node;
76            }
77            const visited = visitEachChild(node, visitor, context);
78            addEmitHelpers(visited, context.readEmitHelpers());
79            return visited;
80        }
81
82        function visitor(node: Node): VisitResult<Node> {
83            if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node;
84
85            switch (node.kind) {
86                case SyntaxKind.ClassExpression:
87                case SyntaxKind.ClassDeclaration:
88                    return visitClassLike(node as ClassLikeDeclaration);
89                case SyntaxKind.PropertyDeclaration:
90                    return visitPropertyDeclaration(node as PropertyDeclaration);
91                case SyntaxKind.VariableStatement:
92                    return visitVariableStatement(node as VariableStatement);
93                case SyntaxKind.PropertyAccessExpression:
94                    return visitPropertyAccessExpression(node as PropertyAccessExpression);
95                case SyntaxKind.PrefixUnaryExpression:
96                    return visitPrefixUnaryExpression(node as PrefixUnaryExpression);
97                case SyntaxKind.PostfixUnaryExpression:
98                    return visitPostfixUnaryExpression(node as PostfixUnaryExpression, /*valueIsDiscarded*/ false);
99                case SyntaxKind.CallExpression:
100                    return visitCallExpression(node as CallExpression);
101                case SyntaxKind.BinaryExpression:
102                    return visitBinaryExpression(node as BinaryExpression);
103                case SyntaxKind.PrivateIdentifier:
104                    return visitPrivateIdentifier(node as PrivateIdentifier);
105                case SyntaxKind.ExpressionStatement:
106                    return visitExpressionStatement(node as ExpressionStatement);
107                case SyntaxKind.ForStatement:
108                    return visitForStatement(node as ForStatement);
109                case SyntaxKind.TaggedTemplateExpression:
110                    return visitTaggedTemplateExpression(node as TaggedTemplateExpression);
111            }
112            return visitEachChild(node, visitor, context);
113        }
114
115        function visitorDestructuringTarget(node: Node): VisitResult<Node> {
116            switch (node.kind) {
117                case SyntaxKind.ObjectLiteralExpression:
118                case SyntaxKind.ArrayLiteralExpression:
119                    return visitAssignmentPattern(node as AssignmentPattern);
120                default:
121                    return visitor(node);
122            }
123        }
124
125        /**
126         * If we visit a private name, this means it is an undeclared private name.
127         * Replace it with an empty identifier to indicate a problem with the code.
128         */
129        function visitPrivateIdentifier(node: PrivateIdentifier) {
130            if (!shouldTransformPrivateFields) {
131                return node;
132            }
133            return setOriginalNode(factory.createIdentifier(""), node);
134        }
135
136        /**
137         * Visits the members of a class that has fields.
138         *
139         * @param node The node to visit.
140         */
141        function classElementVisitor(node: Node): VisitResult<Node> {
142            switch (node.kind) {
143                case SyntaxKind.Constructor:
144                    // Constructors for classes using class fields are transformed in
145                    // `visitClassDeclaration` or `visitClassExpression`.
146                    return undefined;
147
148                case SyntaxKind.GetAccessor:
149                case SyntaxKind.SetAccessor:
150                case SyntaxKind.MethodDeclaration:
151                    // Visit the name of the member (if it's a computed property name).
152                    return visitEachChild(node, classElementVisitor, context);
153
154                case SyntaxKind.PropertyDeclaration:
155                    return visitPropertyDeclaration(node as PropertyDeclaration);
156
157                case SyntaxKind.ComputedPropertyName:
158                    return visitComputedPropertyName(node as ComputedPropertyName);
159
160                case SyntaxKind.SemicolonClassElement:
161                    return node;
162
163                default:
164                    return visitor(node);
165            }
166        }
167
168        function visitVariableStatement(node: VariableStatement) {
169            const savedPendingStatements = pendingStatements;
170            pendingStatements = [];
171
172            const visitedNode = visitEachChild(node, visitor, context);
173            const statement = some(pendingStatements) ?
174                [visitedNode, ...pendingStatements] :
175                visitedNode;
176
177            pendingStatements = savedPendingStatements;
178            return statement;
179        }
180
181        function visitComputedPropertyName(name: ComputedPropertyName) {
182            let node = visitEachChild(name, visitor, context);
183            if (some(pendingExpressions)) {
184                const expressions = pendingExpressions;
185                expressions.push(node.expression);
186                pendingExpressions = [];
187                node = factory.updateComputedPropertyName(
188                    node,
189                    factory.inlineExpressions(expressions)
190                );
191            }
192            return node;
193        }
194
195        function visitPropertyDeclaration(node: PropertyDeclaration) {
196            Debug.assert(!some(node.decorators));
197            if (!shouldTransformPrivateFields && isPrivateIdentifier(node.name)) {
198                // Initializer is elided as the field is initialized in transformConstructor.
199                return factory.updatePropertyDeclaration(
200                    node,
201                    /*decorators*/ undefined,
202                    visitNodes(node.modifiers, visitor, isModifier),
203                    node.name,
204                    /*questionOrExclamationToken*/ undefined,
205                    /*type*/ undefined,
206                    /*initializer*/ undefined
207                );
208            }
209            // Create a temporary variable to store a computed property name (if necessary).
210            // If it's not inlineable, then we emit an expression after the class which assigns
211            // the property name to the temporary variable.
212            const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || !!context.getCompilerOptions().useDefineForClassFields);
213            if (expr && !isSimpleInlineableExpression(expr)) {
214                getPendingExpressions().push(expr);
215            }
216            return undefined;
217        }
218
219        function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression {
220            receiver = visitNode(receiver, visitor, isExpression);
221            switch (info.placement) {
222                case PrivateIdentifierPlacement.InstanceField:
223                    return context.getEmitHelperFactory().createClassPrivateFieldGetHelper(
224                        nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver),
225                        info.weakMapName
226                    );
227                default: return Debug.fail("Unexpected private identifier placement");
228            }
229        }
230
231        function visitPropertyAccessExpression(node: PropertyAccessExpression) {
232            if (shouldTransformPrivateFields && isPrivateIdentifier(node.name)) {
233                const privateIdentifierInfo = accessPrivateIdentifier(node.name);
234                if (privateIdentifierInfo) {
235                    return setOriginalNode(
236                        createPrivateIdentifierAccess(privateIdentifierInfo, node.expression),
237                        node
238                    );
239                }
240            }
241            return visitEachChild(node, visitor, context);
242        }
243
244        function visitPrefixUnaryExpression(node: PrefixUnaryExpression) {
245            if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.operand)) {
246                const operator = node.operator === SyntaxKind.PlusPlusToken ?
247                    SyntaxKind.PlusToken : node.operator === SyntaxKind.MinusMinusToken ?
248                        SyntaxKind.MinusToken : undefined;
249                let info: PrivateIdentifierInfo | undefined;
250                if (operator && (info = accessPrivateIdentifier(node.operand.name))) {
251                    const receiver = visitNode(node.operand.expression, visitor, isExpression);
252                    const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver);
253
254                    const existingValue = factory.createPrefixUnaryExpression(SyntaxKind.PlusToken, createPrivateIdentifierAccess(info, readExpression));
255
256                    return setOriginalNode(
257                        createPrivateIdentifierAssignment(
258                            info,
259                            initializeExpression || readExpression,
260                            factory.createBinaryExpression(existingValue, operator, factory.createNumericLiteral(1)),
261                            SyntaxKind.EqualsToken
262                        ),
263                        node
264                    );
265                }
266            }
267            return visitEachChild(node, visitor, context);
268        }
269
270        function visitPostfixUnaryExpression(node: PostfixUnaryExpression, valueIsDiscarded: boolean) {
271            if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.operand)) {
272                const operator = node.operator === SyntaxKind.PlusPlusToken ?
273                    SyntaxKind.PlusToken : node.operator === SyntaxKind.MinusMinusToken ?
274                        SyntaxKind.MinusToken : undefined;
275                let info: PrivateIdentifierInfo | undefined;
276                if (operator && (info = accessPrivateIdentifier(node.operand.name))) {
277                    const receiver = visitNode(node.operand.expression, visitor, isExpression);
278                    const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver);
279
280                    const existingValue = factory.createPrefixUnaryExpression(SyntaxKind.PlusToken, createPrivateIdentifierAccess(info, readExpression));
281
282                    // Create a temporary variable to store the value returned by the expression.
283                    const returnValue = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration);
284
285                    return setOriginalNode(
286                        factory.inlineExpressions(compact<Expression>([
287                            createPrivateIdentifierAssignment(
288                                info,
289                                initializeExpression || readExpression,
290                                factory.createBinaryExpression(
291                                    returnValue ? factory.createAssignment(returnValue, existingValue) : existingValue,
292                                    operator,
293                                    factory.createNumericLiteral(1)
294                                ),
295                                SyntaxKind.EqualsToken
296                            ),
297                            returnValue
298                        ])),
299                        node
300                    );
301                }
302            }
303            return visitEachChild(node, visitor, context);
304        }
305
306        function visitForStatement(node: ForStatement) {
307            if (node.incrementor && isPostfixUnaryExpression(node.incrementor)) {
308                return factory.updateForStatement(
309                    node,
310                    visitNode(node.initializer, visitor, isForInitializer),
311                    visitNode(node.condition, visitor, isExpression),
312                    visitPostfixUnaryExpression(node.incrementor, /*valueIsDiscarded*/ true),
313                    visitNode(node.statement, visitor, isStatement)
314                );
315            }
316            return visitEachChild(node, visitor, context);
317        }
318
319        function visitExpressionStatement(node: ExpressionStatement) {
320            if (isPostfixUnaryExpression(node.expression)) {
321                return factory.updateExpressionStatement(node, visitPostfixUnaryExpression(node.expression, /*valueIsDiscarded*/ true));
322            }
323            return visitEachChild(node, visitor, context);
324        }
325
326        function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } {
327            const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver);
328            if (isSimpleInlineableExpression(receiver)) {
329                return { readExpression: clone, initializeExpression: undefined };
330            }
331            const readExpression = factory.createTempVariable(hoistVariableDeclaration);
332            const initializeExpression = factory.createAssignment(readExpression, clone);
333            return { readExpression, initializeExpression };
334        }
335
336        function visitCallExpression(node: CallExpression) {
337            if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.expression)) {
338                // Transform call expressions of private names to properly bind the `this` parameter.
339                const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion);
340                if (isCallChain(node)) {
341                    return factory.updateCallChain(
342                        node,
343                        factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"),
344                        /*questionDotToken*/ undefined,
345                        /*typeArguments*/ undefined,
346                        [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)]
347                    );
348                }
349                return factory.updateCallExpression(
350                    node,
351                    factory.createPropertyAccessExpression(visitNode(target, visitor), "call"),
352                    /*typeArguments*/ undefined,
353                    [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)]
354                );
355            }
356            return visitEachChild(node, visitor, context);
357        }
358
359        function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
360            if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.tag)) {
361                // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access.
362                const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion);
363                return factory.updateTaggedTemplateExpression(
364                    node,
365                    factory.createCallExpression(
366                        factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"),
367                        /*typeArguments*/ undefined,
368                        [visitNode(thisArg, visitor, isExpression)]
369                    ),
370                    /*typeArguments*/ undefined,
371                    visitNode(node.template, visitor, isTemplateLiteral)
372                );
373            }
374            return visitEachChild(node, visitor, context);
375        }
376
377        function visitBinaryExpression(node: BinaryExpression) {
378            if (shouldTransformPrivateFields) {
379                if (isDestructuringAssignment(node)) {
380                    const savedPendingExpressions = pendingExpressions;
381                    pendingExpressions = undefined!;
382                    node = factory.updateBinaryExpression(
383                        node,
384                        visitNode(node.left, visitorDestructuringTarget),
385                        node.operatorToken,
386                        visitNode(node.right, visitor)
387                    );
388                    const expr = some(pendingExpressions) ?
389                        factory.inlineExpressions(compact([...pendingExpressions!, node])) :
390                        node;
391                    pendingExpressions = savedPendingExpressions;
392                    return expr;
393                }
394                if (isAssignmentExpression(node) && isPrivateIdentifierPropertyAccessExpression(node.left)) {
395                    const info = accessPrivateIdentifier(node.left.name);
396                    if (info) {
397                        return setOriginalNode(
398                            createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind),
399                            node
400                        );
401                    }
402                }
403            }
404            return visitEachChild(node, visitor, context);
405        }
406
407        function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator) {
408            switch (info.placement) {
409                case PrivateIdentifierPlacement.InstanceField: {
410                    return createPrivateIdentifierInstanceFieldAssignment(info, receiver, right, operator);
411                }
412                default: return Debug.fail("Unexpected private identifier placement");
413            }
414        }
415
416        function createPrivateIdentifierInstanceFieldAssignment(info: PrivateIdentifierInstanceField, receiver: Expression, right: Expression, operator: AssignmentOperator) {
417            receiver = visitNode(receiver, visitor, isExpression);
418            right = visitNode(right, visitor, isExpression);
419            if (isCompoundAssignment(operator)) {
420                const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver);
421                return context.getEmitHelperFactory().createClassPrivateFieldSetHelper(
422                    initializeExpression || readExpression,
423                    info.weakMapName,
424                    factory.createBinaryExpression(
425                        context.getEmitHelperFactory().createClassPrivateFieldGetHelper(readExpression, info.weakMapName),
426                        getNonAssignmentOperatorForCompoundAssignment(operator),
427                        right
428                    )
429                );
430            }
431            else {
432                return context.getEmitHelperFactory().createClassPrivateFieldSetHelper(receiver, info.weakMapName, right);
433            }
434        }
435
436        /**
437         * Set up the environment for a class.
438         */
439        function visitClassLike(node: ClassLikeDeclaration) {
440            const savedPendingExpressions = pendingExpressions;
441            pendingExpressions = undefined;
442            if (shouldTransformPrivateFields) {
443                startPrivateIdentifierEnvironment();
444            }
445
446            //TODO StructDeclaration 类型暂未处理
447            const result = isClassDeclaration(node)
448                ? visitClassDeclaration(node)
449                : isClassExpression(node)
450                ? visitClassExpression(node)
451                : [];
452
453            if (shouldTransformPrivateFields) {
454                endPrivateIdentifierEnvironment();
455            }
456            pendingExpressions = savedPendingExpressions;
457            return result;
458        }
459
460        function doesClassElementNeedTransform(node: ClassElement) {
461            return isPropertyDeclaration(node) || (shouldTransformPrivateFields && node.name && isPrivateIdentifier(node.name));
462        }
463
464        function visitClassDeclaration(node: ClassDeclaration) {
465            if (!forEach(node.members, doesClassElementNeedTransform)) {
466                return visitEachChild(node, visitor, context);
467            }
468
469            const extendsClauseElement = getEffectiveBaseTypeNode(node);
470            const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword);
471
472            const statements: Statement[] = [
473                factory.updateClassDeclaration(
474                    node,
475                    /*decorators*/ undefined,
476                    node.modifiers,
477                    node.name,
478                    /*typeParameters*/ undefined,
479                    visitNodes(node.heritageClauses, visitor, isHeritageClause),
480                    transformClassMembers(node, isDerivedClass)
481                )
482            ];
483
484            // Write any pending expressions from elided or moved computed property names
485            if (some(pendingExpressions)) {
486                statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)));
487            }
488
489            // Emit static property assignment. Because classDeclaration is lexically evaluated,
490            // it is safe to emit static property assignment after classDeclaration
491            // From ES6 specification:
492            //      HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using
493            //                                  a lexical declaration such as a LexicalDeclaration or a ClassDeclaration.
494            const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true);
495            if (some(staticProperties)) {
496                addPropertyStatements(statements, staticProperties, factory.getInternalName(node));
497            }
498
499            return statements;
500        }
501
502        function visitClassExpression(node: ClassExpression): Expression {
503            if (!forEach(node.members, doesClassElementNeedTransform)) {
504                return visitEachChild(node, visitor, context);
505            }
506
507            // If this class expression is a transformation of a decorated class declaration,
508            // then we want to output the pendingExpressions as statements, not as inlined
509            // expressions with the class statement.
510            //
511            // In this case, we use pendingStatements to produce the same output as the
512            // class declaration transformation. The VariableStatement visitor will insert
513            // these statements after the class expression variable statement.
514            const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node));
515
516            const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true);
517            const extendsClauseElement = getEffectiveBaseTypeNode(node);
518            const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword);
519
520            const classExpression = factory.updateClassExpression(
521                node,
522                visitNodes(node.decorators, visitor, isDecorator),
523                node.modifiers,
524                node.name,
525                /*typeParameters*/ undefined,
526                visitNodes(node.heritageClauses, visitor, isHeritageClause),
527                transformClassMembers(node, isDerivedClass)
528            );
529
530            if (some(staticProperties) || some(pendingExpressions)) {
531                if (isDecoratedClassDeclaration) {
532                    Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration.");
533
534                    // Write any pending expressions from elided or moved computed property names
535                    if (pendingStatements && pendingExpressions && some(pendingExpressions)) {
536                        pendingStatements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)));
537                    }
538
539                    if (pendingStatements && some(staticProperties)) {
540                        addPropertyStatements(pendingStatements, staticProperties, factory.getInternalName(node));
541                    }
542                    return classExpression;
543                }
544                else {
545                    const expressions: Expression[] = [];
546                    const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
547                    const temp = factory.createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
548                    if (isClassWithConstructorReference) {
549                        // record an alias as the class name is not in scope for statics.
550                        enableSubstitutionForClassAliases();
551                        const alias = factory.cloneNode(temp) as GeneratedIdentifier;
552                        alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes;
553                        classAliases[getOriginalNodeId(node)] = alias;
554                    }
555
556                    // To preserve the behavior of the old emitter, we explicitly indent
557                    // the body of a class with static initializers.
558                    setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression));
559                    expressions.push(startOnNewLine(factory.createAssignment(temp, classExpression)));
560                    // Add any pending expressions leftover from elided or relocated computed property names
561                    addRange(expressions, map(pendingExpressions, startOnNewLine));
562                    addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp));
563                    expressions.push(startOnNewLine(temp));
564
565                    return factory.inlineExpressions(expressions);
566                }
567            }
568
569            return classExpression;
570        }
571
572        function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
573            if (shouldTransformPrivateFields) {
574                // Declare private names.
575                for (const member of node.members) {
576                    if (isPrivateIdentifierPropertyDeclaration(member)) {
577                        addPrivateIdentifierToEnvironment(member.name);
578                    }
579                }
580            }
581
582            const members: ClassElement[] = [];
583            const constructor = transformConstructor(node, isDerivedClass);
584            if (constructor) {
585                members.push(constructor);
586            }
587            addRange(members, visitNodes(node.members, classElementVisitor, isClassElement));
588            return setTextRange(factory.createNodeArray(members), /*location*/ node.members);
589        }
590
591        function isPropertyDeclarationThatRequiresConstructorStatement(member: ClassElement): member is PropertyDeclaration {
592            if (!isPropertyDeclaration(member) || hasStaticModifier(member) || hasSyntacticModifier(getOriginalNode(member), ModifierFlags.Abstract)) {
593                return false;
594            }
595            if (context.getCompilerOptions().useDefineForClassFields) {
596                // If we are using define semantics and targeting ESNext or higher,
597                // then we don't need to transform any class properties.
598                return languageVersion < ScriptTarget.ESNext;
599            }
600            return isInitializedProperty(member) || shouldTransformPrivateFields && isPrivateIdentifierPropertyDeclaration(member);
601        }
602
603        function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
604            const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration);
605            const properties = node.members.filter(isPropertyDeclarationThatRequiresConstructorStatement);
606            if (!some(properties)) {
607                return constructor;
608            }
609            const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context);
610            const body = transformConstructorBody(node, constructor, isDerivedClass);
611            if (!body) {
612                return undefined;
613            }
614            return startOnNewLine(
615                setOriginalNode(
616                    setTextRange(
617                        factory.createConstructorDeclaration(
618                            /*decorators*/ undefined,
619                            /*modifiers*/ undefined,
620                            parameters ?? [],
621                            body
622                        ),
623                        constructor || node
624                    ),
625                    constructor
626                )
627            );
628        }
629
630        function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
631            const useDefineForClassFields = context.getCompilerOptions().useDefineForClassFields;
632            let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false);
633            if (!useDefineForClassFields) {
634                properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name));
635            }
636
637
638            // Only generate synthetic constructor when there are property initializers to move.
639            if (!constructor && !some(properties)) {
640                return visitFunctionBody(/*node*/ undefined, visitor, context);
641            }
642
643            resumeLexicalEnvironment();
644
645            let indexOfFirstStatement = 0;
646            let statements: Statement[] = [];
647
648            if (!constructor && isDerivedClass) {
649                // Add a synthetic `super` call:
650                //
651                //  super(...arguments);
652                //
653                statements.push(
654                    factory.createExpressionStatement(
655                        factory.createCallExpression(
656                            factory.createSuper(),
657                            /*typeArguments*/ undefined,
658                            [factory.createSpreadElement(factory.createIdentifier("arguments"))]
659                        )
660                    )
661                );
662            }
663
664            if (constructor) {
665                indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor);
666            }
667            // Add the property initializers. Transforms this:
668            //
669            //  public x = 1;
670            //
671            // Into this:
672            //
673            //  constructor() {
674            //      this.x = 1;
675            //  }
676            //
677            if (constructor?.body) {
678                let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement);
679                if (afterParameterProperties === -1) {
680                    afterParameterProperties = constructor.body.statements.length;
681                }
682                if (afterParameterProperties > indexOfFirstStatement) {
683                    if (!useDefineForClassFields) {
684                        addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement));
685                    }
686                    indexOfFirstStatement = afterParameterProperties;
687                }
688            }
689            addPropertyStatements(statements, properties, factory.createThis());
690
691            // Add existing statements, skipping the initial super call.
692            if (constructor) {
693                addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement));
694            }
695
696            statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment());
697
698            return setTextRange(
699                factory.createBlock(
700                    setTextRange(
701                        factory.createNodeArray(statements),
702                        /*location*/ constructor ? constructor.body!.statements : node.members
703                    ),
704                    /*multiLine*/ true
705                ),
706                /*location*/ constructor ? constructor.body : undefined
707            );
708        }
709
710        /**
711         * Generates assignment statements for property initializers.
712         *
713         * @param properties An array of property declarations to transform.
714         * @param receiver The receiver on which each property should be assigned.
715         */
716        function addPropertyStatements(statements: Statement[], properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) {
717            for (const property of properties) {
718                const expression = transformProperty(property, receiver);
719                if (!expression) {
720                    continue;
721                }
722                const statement = factory.createExpressionStatement(expression);
723                setSourceMapRange(statement, moveRangePastModifiers(property));
724                setCommentRange(statement, property);
725                setOriginalNode(statement, property);
726                statements.push(statement);
727            }
728        }
729
730        /**
731         * Generates assignment expressions for property initializers.
732         *
733         * @param properties An array of property declarations to transform.
734         * @param receiver The receiver on which each property should be assigned.
735         */
736        function generateInitializedPropertyExpressions(properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) {
737            const expressions: Expression[] = [];
738            for (const property of properties) {
739                const expression = transformProperty(property, receiver);
740                if (!expression) {
741                    continue;
742                }
743                startOnNewLine(expression);
744                setSourceMapRange(expression, moveRangePastModifiers(property));
745                setCommentRange(expression, property);
746                setOriginalNode(expression, property);
747                expressions.push(expression);
748            }
749
750            return expressions;
751        }
752
753        /**
754         * Transforms a property initializer into an assignment statement.
755         *
756         * @param property The property declaration.
757         * @param receiver The object receiving the property assignment.
758         */
759        function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) {
760            // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
761            const emitAssignment = !context.getCompilerOptions().useDefineForClassFields;
762            const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression)
763                ? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name))
764                : property.name;
765
766            if (shouldTransformPrivateFields && isPrivateIdentifier(propertyName)) {
767                const privateIdentifierInfo = accessPrivateIdentifier(propertyName);
768                if (privateIdentifierInfo) {
769                    switch (privateIdentifierInfo.placement) {
770                        case PrivateIdentifierPlacement.InstanceField: {
771                            return createPrivateInstanceFieldInitializer(
772                                receiver,
773                                visitNode(property.initializer, visitor, isExpression),
774                                privateIdentifierInfo.weakMapName
775                            );
776                        }
777                    }
778                }
779                else {
780                    Debug.fail("Undeclared private name for property declaration.");
781                }
782            }
783            if (isPrivateIdentifier(propertyName) && !property.initializer) {
784                return undefined;
785            }
786
787            if (isPrivateIdentifier(propertyName) && !property.initializer) {
788                return undefined;
789            }
790
791            const propertyOriginalNode = getOriginalNode(property);
792            if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) {
793                return undefined;
794            }
795            const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero()
796                : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName
797                : factory.createVoidZero();
798
799            if (emitAssignment || isPrivateIdentifier(propertyName)) {
800                const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName);
801                return factory.createAssignment(memberAccess, initializer);
802            }
803            else {
804                const name = isComputedPropertyName(propertyName) ? propertyName.expression
805                    : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText))
806                    : propertyName;
807                const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true });
808                return factory.createObjectDefinePropertyCall(receiver, name, descriptor);
809            }
810        }
811
812        function enableSubstitutionForClassAliases() {
813            if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) {
814                enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases;
815
816                // We need to enable substitutions for identifiers. This allows us to
817                // substitute class names inside of a class declaration.
818                context.enableSubstitution(SyntaxKind.Identifier);
819
820                // Keep track of class aliases.
821                classAliases = [];
822            }
823        }
824
825        /**
826         * Hooks node substitutions.
827         *
828         * @param hint The context for the emitter.
829         * @param node The node to substitute.
830         */
831        function onSubstituteNode(hint: EmitHint, node: Node) {
832            node = previousOnSubstituteNode(hint, node);
833            if (hint === EmitHint.Expression) {
834                return substituteExpression(node as Expression);
835            }
836            return node;
837        }
838
839        function substituteExpression(node: Expression) {
840            switch (node.kind) {
841                case SyntaxKind.Identifier:
842                    return substituteExpressionIdentifier(node as Identifier);
843            }
844            return node;
845        }
846
847        function substituteExpressionIdentifier(node: Identifier): Expression {
848            return trySubstituteClassAlias(node) || node;
849        }
850
851        function trySubstituteClassAlias(node: Identifier): Expression | undefined {
852            if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) {
853                if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
854                    // Due to the emit for class decorators, any reference to the class from inside of the class body
855                    // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
856                    // behavior of class names in ES6.
857                    // Also, when emitting statics for class expressions, we must substitute a class alias for
858                    // constructor references in static property initializers.
859                    const declaration = resolver.getReferencedValueDeclaration(node);
860                    if (declaration) {
861                        const classAlias = classAliases[declaration.id!]; // TODO: GH#18217
862                        if (classAlias) {
863                            const clone = factory.cloneNode(classAlias);
864                            setSourceMapRange(clone, node);
865                            setCommentRange(clone, node);
866                            return clone;
867                        }
868                    }
869                }
870            }
871
872            return undefined;
873        }
874
875
876        /**
877         * If the name is a computed property, this function transforms it, then either returns an expression which caches the
878         * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations
879         * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator)
880         */
881        function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined {
882            if (isComputedPropertyName(name)) {
883                const expression = visitNode(name.expression, visitor, isExpression);
884                const innerExpression = skipPartiallyEmittedExpressions(expression);
885                const inlinable = isSimpleInlineableExpression(innerExpression);
886                const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left);
887                if (!alreadyTransformed && !inlinable && shouldHoist) {
888                    const generatedName = factory.getGeneratedNameForNode(name);
889                    hoistVariableDeclaration(generatedName);
890                    return factory.createAssignment(generatedName, expression);
891                }
892                return (inlinable || isIdentifier(innerExpression)) ? undefined : expression;
893            }
894        }
895
896        function startPrivateIdentifierEnvironment() {
897            privateIdentifierEnvironmentStack.push(currentPrivateIdentifierEnvironment);
898            currentPrivateIdentifierEnvironment = undefined;
899        }
900
901        function endPrivateIdentifierEnvironment() {
902            currentPrivateIdentifierEnvironment = privateIdentifierEnvironmentStack.pop();
903        }
904
905        function getPrivateIdentifierEnvironment() {
906            return currentPrivateIdentifierEnvironment || (currentPrivateIdentifierEnvironment = new Map());
907        }
908
909        function getPendingExpressions() {
910            return pendingExpressions || (pendingExpressions = []);
911        }
912
913        function addPrivateIdentifierToEnvironment(name: PrivateIdentifier) {
914            const text = getTextOfPropertyName(name) as string;
915            const weakMapName = factory.createUniqueName("_" + text.substring(1), GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes);
916            hoistVariableDeclaration(weakMapName);
917            getPrivateIdentifierEnvironment().set(name.escapedText, { placement: PrivateIdentifierPlacement.InstanceField, weakMapName });
918            getPendingExpressions().push(
919                factory.createAssignment(
920                    weakMapName,
921                    factory.createNewExpression(
922                        factory.createIdentifier("WeakMap"),
923                        /*typeArguments*/ undefined,
924                        []
925                    )
926                )
927            );
928        }
929
930        function accessPrivateIdentifier(name: PrivateIdentifier) {
931            if (currentPrivateIdentifierEnvironment) {
932                const info = currentPrivateIdentifierEnvironment.get(name.escapedText);
933                if (info) {
934                    return info;
935                }
936            }
937            for (let i = privateIdentifierEnvironmentStack.length - 1; i >= 0; --i) {
938                const env = privateIdentifierEnvironmentStack[i];
939                if (!env) {
940                    continue;
941                }
942                const info = env.get(name.escapedText);
943                if (info) {
944                    return info;
945                }
946            }
947            return undefined;
948        }
949
950
951        function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) {
952            const parameter = factory.getGeneratedNameForNode(node);
953            const info = accessPrivateIdentifier(node.name);
954            if (!info) {
955                return visitEachChild(node, visitor, context);
956            }
957            let receiver = node.expression;
958            // We cannot copy `this` or `super` into the function because they will be bound
959            // differently inside the function.
960            if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) {
961                receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true);
962                getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, node.expression));
963            }
964            return factory.createPropertyAccessExpression(
965                // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560)
966                factory.createParenthesizedExpression(
967                    factory.createObjectLiteralExpression([
968                        factory.createSetAccessorDeclaration(
969                            /*decorators*/ undefined,
970                            /*modifiers*/ undefined,
971                            "value",
972                            [factory.createParameterDeclaration(
973                                /*decorators*/ undefined,
974                                /*modifiers*/ undefined,
975                                /*dotDotDotToken*/ undefined,
976                                parameter,
977                                /*questionToken*/ undefined,
978                                /*type*/ undefined,
979                                /*initializer*/ undefined
980                            )],
981                            factory.createBlock(
982                                [factory.createExpressionStatement(
983                                    createPrivateIdentifierAssignment(
984                                        info,
985                                        receiver,
986                                        parameter,
987                                        SyntaxKind.EqualsToken
988                                    )
989                                )]
990                            )
991                        )
992                    ])
993                ),
994                "value"
995            );
996        }
997
998        function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) {
999            const target = getTargetOfBindingOrAssignmentElement(node);
1000            if (target && isPrivateIdentifierPropertyAccessExpression(target)) {
1001                const wrapped = wrapPrivateIdentifierForDestructuringTarget(target);
1002                if (isAssignmentExpression(node)) {
1003                    return factory.updateBinaryExpression(
1004                        node,
1005                        wrapped,
1006                        node.operatorToken,
1007                        visitNode(node.right, visitor, isExpression)
1008                    );
1009                }
1010                else if (isSpreadElement(node)) {
1011                    return factory.updateSpreadElement(node, wrapped);
1012                }
1013                else {
1014                    return wrapped;
1015                }
1016            }
1017            return visitNode(node, visitorDestructuringTarget);
1018        }
1019
1020        function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) {
1021            if (isPropertyAssignment(node)) {
1022                const target = getTargetOfBindingOrAssignmentElement(node);
1023                if (target && isPrivateIdentifierPropertyAccessExpression(target)) {
1024                    const initializer = getInitializerOfBindingOrAssignmentElement(node);
1025                    const wrapped = wrapPrivateIdentifierForDestructuringTarget(target);
1026                    return factory.updatePropertyAssignment(
1027                        node,
1028                        visitNode(node.name, visitor),
1029                        initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped,
1030                    );
1031                }
1032                return factory.updatePropertyAssignment(
1033                    node,
1034                    visitNode(node.name, visitor),
1035                    visitNode(node.initializer, visitorDestructuringTarget)
1036                );
1037            }
1038            return visitNode(node, visitor);
1039        }
1040
1041
1042        function visitAssignmentPattern(node: AssignmentPattern) {
1043            if (isArrayLiteralExpression(node)) {
1044                // Transforms private names in destructuring assignment array bindings.
1045                //
1046                // Source:
1047                // ([ this.#myProp ] = [ "hello" ]);
1048                //
1049                // Transformation:
1050                // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ];
1051                return factory.updateArrayLiteralExpression(
1052                    node,
1053                    visitNodes(node.elements, visitArrayAssignmentTarget, isExpression)
1054                );
1055            }
1056            else {
1057                // Transforms private names in destructuring assignment object bindings.
1058                //
1059                // Source:
1060                // ({ stringProperty: this.#myProp } = { stringProperty: "hello" });
1061                //
1062                // Transformation:
1063                // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" };
1064                return factory.updateObjectLiteralExpression(
1065                    node,
1066                    visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike)
1067                );
1068            }
1069        }
1070    }
1071
1072    function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) {
1073        return factory.createCallExpression(
1074            factory.createPropertyAccessExpression(weakMapName, "set"),
1075            /*typeArguments*/ undefined,
1076            [receiver, initializer || factory.createVoidZero()]
1077        );
1078    }
1079}
1080