package estransforms import ( "iter" "slices" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/debug" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/transformers" ) // classFacts tracks various facts about a class being transformed. type classFacts int const ( classFactsNone classFacts = 0 classFactsClassWasDecorated classFacts = 1 << 0 classFactsNeedsClassConstructorReference classFacts = 1 << 1 classFactsNeedsClassSuperReference classFacts = 1 << 2 classFactsNeedsSubstitutionForThisInClassStaticField classFacts = 1 << 3 classFactsWillHoistInitializersToConstructor classFacts = 1 << 4 ) // privateIdentifierKind represents the kind of private identifier declaration. // privateIdentifierInfo stores information about a private identifier during transformation. type privateIdentifierInfo struct { kind printer.PrivateIdentifierKind // brandCheckIdentifier can contain: // - For instance field: The WeakMap that will be the storage for the field. // - For instance methods or accessors: The WeakSet that will be used for brand checking. // - For static members: The constructor that will be used for brand checking. brandCheckIdentifier *ast.IdentifierNode // isStatic stores if the identifier is static or not. isStatic bool // isValid stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor) // or duplicate identifiers are considered invalid. isValid bool // variableName contains the variable that will serve as the storage for a static field. variableName *ast.IdentifierNode // methodName is the identifier for a variable that will contain the private method implementation. methodName *ast.IdentifierNode // getterName is the identifier for a variable that will contain the private get accessor implementation, if any. getterName *ast.IdentifierNode // setterName is the identifier for a variable that will contain the private set accessor implementation, if any. setterName *ast.IdentifierNode } // privateEnvironmentData stores class-scoped environment data for private identifiers. type privateEnvironmentData struct { // className is used for prefixing generated variable names. className *ast.IdentifierNode // weakSetName is used for brand check on private methods. weakSetName *ast.IdentifierNode } // privateEnvironment stores a map of private identifier names to their transform info. // Like Strada, it uses two separate maps: one for non-generated identifiers (keyed by text) // and one for generated identifiers (keyed by original AST node). This prevents collisions // when different auto-accessors produce generated backing field names with the same text. type privateEnvironment struct { data privateEnvironmentData members map[string]*privateIdentifierInfo generatedIdentifiers map[*ast.Node]*privateIdentifierInfo } // classLexicalEnvironment stores information about the lexical environment of a class. type classLexicalEnvironment struct { facts classFacts // classConstructor is used for brand checks on static members, and `this` references in static initializers. classConstructor *ast.IdentifierNode classThis *ast.IdentifierNode // superClassReference is used for `super` references in static initializers. superClassReference *ast.IdentifierNode } // classLexicalEnv is a linked list of class lexical environments. type classLexicalEnv struct { previous *classLexicalEnv data *classLexicalEnvironment privateEnv *privateEnvironment } type classFieldsTransformer struct { transformers.Transformer compilerOptions *core.CompilerOptions resolver binder.ReferenceResolver // Computed configuration flags shouldTransformInitializersUsingSet bool shouldTransformInitializersUsingDefine bool shouldTransformInitializers bool shouldTransformPrivateElementsOrClassStaticBlocks bool shouldTransformAutoAccessors bool shouldTransformThisInStaticInitializers bool shouldTransformSuperInStaticInitializers bool shouldTransformPrivateStaticElementsInFile bool legacyDecorators bool // pendingExpressions tracks what computed name expressions originating from elided names // must be inlined at the next execution site, in document order. pendingExpressions []*ast.Expression // pendingStatements tracks what computed name expression statements and static property // initializers must be emitted at the next execution site, in document order (for decorated classes). pendingStatements []*ast.Statement lexicalEnvironment *classLexicalEnv currentClassContainer *ast.ClassLikeDeclaration currentClassElement *ast.ClassElement // classAliases maps class declarations to alias identifiers for substituting class name // references in static initializers. Replaces Strada's onSubstituteNode/trySubstituteClassAlias. classAliases map[*ast.Node]*ast.IdentifierNode enclosingClassDeclarations collections.Set[*ast.Node] inIterationStatement bool // insideComputedPropertyName replaces Strada's onEmitNode for ComputedPropertyName, which // switches to the outer lexical environment. Used by visitThisExpression() to apply // the outer environment's substitution without requiring currentClassElement to be static. insideComputedPropertyName bool parentNode *ast.Node currentNode *ast.Node // Visitors modifierVisitor *ast.NodeVisitor discardedValueVisitor *ast.NodeVisitor heritageClauseVisitor *ast.NodeVisitor assignmentTargetVisitor *ast.NodeVisitor classElementVisitor *ast.NodeVisitor accessorFieldResultVisitor *ast.NodeVisitor arrayAssignmentElementVisitor *ast.NodeVisitor objectAssignmentElementVisitor *ast.NodeVisitor substitutionVisitor *ast.NodeVisitor // Pre-bound callbacks to avoid repeated closure allocation. isAnonymousClassNeedingAssignedName func(*anonymousFunctionDefinition) bool } func newClassFieldsTransformer(opts *transformers.TransformOptions) *transformers.Transformer { languageVersion := opts.CompilerOptions.GetEmitScriptTarget() useDefineForClassFields := opts.CompilerOptions.GetUseDefineForClassFields() // When targeting ESNext+ with useDefineForClassFields (the default), there are no class // field transformations to perform and no prior transform sets EFTransformPrivateStaticElements, // so every node would be returned unchanged. Skip entirely. if languageVersion >= core.ScriptTargetESNext && useDefineForClassFields { return nil } tx := &classFieldsTransformer{ compilerOptions: opts.CompilerOptions, resolver: opts.Resolver, legacyDecorators: opts.CompilerOptions.ExperimentalDecorators.IsTrue(), } // Always transform field initializers using Set semantics when `useDefineForClassFields: false`. tx.shouldTransformInitializersUsingSet = !useDefineForClassFields // Transform field initializers using Define semantics when `useDefineForClassFields: true` and target < ES2022. tx.shouldTransformInitializersUsingDefine = useDefineForClassFields && languageVersion < core.ScriptTargetES2022 tx.shouldTransformInitializers = tx.shouldTransformInitializersUsingSet || tx.shouldTransformInitializersUsingDefine // We need to transform private members and class static blocks when target < ES2022. tx.shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < core.ScriptTargetES2022 // We need to transform `accessor` fields when target < ESNext. // We may need to transform `accessor` fields when `useDefineForClassFields: false` tx.shouldTransformAutoAccessors = languageVersion < core.ScriptTargetESNext // We need to transform `this` in a static initializer into a reference to the class // when target < ES2022 since the assignment will be moved outside of the class body. tx.shouldTransformThisInStaticInitializers = languageVersion < core.ScriptTargetES2022 // Since target is always >= ES2015, this is always the same as // shouldTransformThisInStaticInitializers. tx.shouldTransformSuperInStaticInitializers = tx.shouldTransformThisInStaticInitializers result := tx.NewTransformer(tx.visit, opts.Context) tx.modifierVisitor = tx.EmitContext().NewNodeVisitor(tx.visitModifier) tx.discardedValueVisitor = tx.EmitContext().NewNodeVisitor(tx.visitDiscardedValue) tx.heritageClauseVisitor = tx.EmitContext().NewNodeVisitor(tx.visitHeritageClause) tx.assignmentTargetVisitor = tx.EmitContext().NewNodeVisitor(tx.visitAssignmentTarget) tx.classElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitClassElement) tx.accessorFieldResultVisitor = tx.EmitContext().NewNodeVisitor(tx.visitAccessorFieldResult) tx.arrayAssignmentElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitArrayAssignmentElement) tx.objectAssignmentElementVisitor = tx.EmitContext().NewNodeVisitor(tx.visitObjectAssignmentElement) tx.substitutionVisitor = tx.EmitContext().NewNodeVisitor(tx.visitForSubstitution) tx.isAnonymousClassNeedingAssignedName = tx.isAnonymousClassNeedingAssignedNameWorker return result } // requiresBlockScopedVar returns true when private field temp variables should be // declared as block-scoped (let) rather than function-scoped (var). This occurs when // a class expression is directly inside a loop body. // Replaces Strada's resolver.hasNodeCheckFlag(node, NodeCheckFlags.BlockScopedBindingInLoop). func (tx *classFieldsTransformer) requiresBlockScopedVar() bool { return tx.inIterationStatement && tx.currentClassContainer != nil && ast.IsClassExpression(tx.currentClassContainer) } // classExpressionNeedsBlockScopedTemp returns true when the class expression's temp variable // must be block-scoped. This is more specific than requiresBlockScopedVar: the class temp only // needs to be block-scoped when the class expression has a non-static property with a computed // property name inside a loop (matching the checker's BlockScopedBindingInLoop on the class node). func (tx *classFieldsTransformer) classExpressionNeedsBlockScopedTemp() bool { if !tx.requiresBlockScopedVar() { return false } for _, member := range tx.currentClassContainer.Members() { if ast.IsPropertyDeclaration(member) && !ast.HasStaticModifier(member) && member.Name() != nil && ast.IsComputedPropertyName(member.Name()) { return true } } return false } func (tx *classFieldsTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node { if node.IsDeclarationFile { return node.AsNode() } tx.lexicalEnvironment = nil tx.shouldTransformPrivateStaticElementsInFile = tx.EmitContext().EmitFlags(node.AsNode())&printer.EFTransformPrivateStaticElements != 0 tx.classAliases = make(map[*ast.Node]*ast.IdentifierNode) tx.enclosingClassDeclarations.Clear() visited := tx.Visitor().VisitEachChild(node.AsNode()) tx.EmitContext().AddEmitHelper(visited, tx.EmitContext().ReadEmitHelpers()...) tx.classAliases = nil tx.enclosingClassDeclarations.Clear() return visited } func (tx *classFieldsTransformer) visitModifier(node *ast.Node) *ast.Node { if node.Kind == ast.KindAccessorKeyword { if tx.shouldTransformAutoAccessorsInCurrentClass() { return nil } return node } if ast.IsModifier(node) { return node } return nil } func (tx *classFieldsTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) { grandparentNode = tx.parentNode tx.parentNode = tx.currentNode tx.currentNode = node return grandparentNode } func (tx *classFieldsTransformer) popNode(grandparentNode *ast.Node) { tx.currentNode = tx.parentNode tx.parentNode = grandparentNode } // visitForSubstitution visits nodes solely for class alias substitution in subtrees // that don't contain class field or lexical this/super transforms. It substitutes // identifiers that reference class declarations with their aliases, while skipping // the .Name() of PropertyAccessExpressions since Strada's onSubstituteNode only // fires for EmitHint.Expression, which excludes property access names. func (tx *classFieldsTransformer) visitForSubstitution(node *ast.Node) *ast.Node { if node.Kind == ast.KindIdentifier { return tx.visitIdentifier(node.AsIdentifier()) } if node.Kind == ast.KindPropertyAccessExpression && ast.IsIdentifier(node.AsPropertyAccessExpression().Name()) { return tx.visitPropertyAccessExpressionForSubstitution(node.AsPropertyAccessExpression()) } return tx.substitutionVisitor.VisitEachChild(node) } // visit is the main visitor. func (tx *classFieldsTransformer) visit(node *ast.Node) *ast.Node { grandparentNode := tx.pushNode(node) defer tx.popNode(grandparentNode) if node.SubtreeFacts()&(ast.SubtreeContainsClassFields|ast.SubtreeContainsLexicalThisOrSuper) == 0 { if tx.currentClassContainer != nil && len(tx.classAliases) > 0 { // Continue visiting for alias substitution even in non-class-field subtrees. return tx.visitForSubstitution(node) } return node } switch node.Kind { case ast.KindSourceFile: return tx.visitSourceFile(node.AsSourceFile()) case ast.KindClassDeclaration: return tx.visitClassDeclaration(node.AsClassDeclaration()) case ast.KindClassExpression: return tx.visitClassExpression(node.AsClassExpression()) case ast.KindClassStaticBlockDeclaration, ast.KindPropertyDeclaration: panic("Use `classElementVisitor` instead.") case ast.KindPropertyAssignment: return tx.visitPropertyAssignment(node.AsPropertyAssignment()) case ast.KindVariableStatement: return tx.visitVariableStatement(node.AsVariableStatement()) case ast.KindVariableDeclaration: return tx.visitVariableDeclaration(node.AsVariableDeclaration()) case ast.KindParameter: return tx.visitParameterDeclaration(node.AsParameterDeclaration()) case ast.KindBindingElement: return tx.visitBindingElement(node.AsBindingElement()) case ast.KindExportAssignment: return tx.visitExportAssignment(node.AsExportAssignment()) case ast.KindPrivateIdentifier: return tx.visitPrivateIdentifier(node) case ast.KindPropertyAccessExpression: return tx.visitPropertyAccessExpression(node.AsPropertyAccessExpression()) case ast.KindElementAccessExpression: return tx.visitElementAccessExpression(node.AsElementAccessExpression()) case ast.KindPrefixUnaryExpression, ast.KindPostfixUnaryExpression: return tx.visitPreOrPostfixUnaryExpression(node, false /*discarded*/) case ast.KindBinaryExpression: return tx.visitBinaryExpression(node.AsBinaryExpression(), false /*discarded*/) case ast.KindParenthesizedExpression: return tx.visitParenthesizedExpression(node.AsParenthesizedExpression(), false /*discarded*/) case ast.KindCallExpression: return tx.visitCallExpression(node.AsCallExpression()) case ast.KindExpressionStatement: return tx.visitExpressionStatement(node.AsExpressionStatement()) case ast.KindTaggedTemplateExpression: return tx.visitTaggedTemplateExpression(node.AsTaggedTemplateExpression()) case ast.KindForStatement: return tx.visitForStatement(node.AsForStatement()) case ast.KindForInStatement, ast.KindForOfStatement, ast.KindDoStatement, ast.KindWhileStatement: return tx.setInIterationStatementAnd(true, (*classFieldsTransformer).visitEachChildOfNode, node) case ast.KindThisKeyword: return tx.visitThisExpression(node) case ast.KindFunctionDeclaration, ast.KindFunctionExpression: return tx.setInIterationStatementAnd(false, (*classFieldsTransformer).visitFunctionExpressionOrDeclaration, node) case ast.KindConstructor, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: return tx.setInIterationStatementAnd(false, (*classFieldsTransformer).setClassElementAndVisitEachChild, node) default: return tx.Visitor().VisitEachChild(node) } } // visitDiscardedValue visits a node in an expression whose result is discarded. func (tx *classFieldsTransformer) visitDiscardedValue(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindPrefixUnaryExpression, ast.KindPostfixUnaryExpression: return tx.visitPreOrPostfixUnaryExpression(node, true /*discarded*/) case ast.KindBinaryExpression: return tx.visitBinaryExpression(node.AsBinaryExpression(), true /*discarded*/) case ast.KindParenthesizedExpression: return tx.visitParenthesizedExpression(node.AsParenthesizedExpression(), true /*discarded*/) default: return tx.visit(node) } } // visitHeritageClause visits a node in a HeritageClause. func (tx *classFieldsTransformer) visitHeritageClause(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindHeritageClause: return tx.heritageClauseVisitor.VisitEachChild(node) case ast.KindExpressionWithTypeArguments: return tx.visitExpressionWithTypeArgumentsInHeritageClause(node.AsExpressionWithTypeArguments()) default: return tx.visit(node) } } // visitAssignmentTarget visits the assignment target of a destructuring assignment. func (tx *classFieldsTransformer) visitAssignmentTarget(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression: return tx.visitAssignmentPattern(node) default: return tx.visit(node) } } func (tx *classFieldsTransformer) visitDestructuringAssignmentTarget(node *ast.Node) *ast.Node { if ast.IsObjectLiteralExpression(node) || ast.IsArrayLiteralExpression(node) { return tx.visitAssignmentPattern(node) } if ast.IsPropertyAccessExpression(node) && ast.IsPrivateIdentifier(node.AsPropertyAccessExpression().Name()) { return tx.wrapPrivateIdentifierForDestructuringTarget(node) } if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { data := tx.lexicalEnvironment.data if data.facts&classFactsClassWasDecorated != 0 { return tx.visitInvalidSuperProperty(node) } if data.classConstructor != nil && data.superClassReference != nil { var name *ast.Expression if ast.IsElementAccessExpression(node) { name = tx.Visitor().VisitNode(node.AsElementAccessExpression().ArgumentExpression) } else if ast.IsPropertyAccessExpression(node) && ast.IsIdentifier(node.AsPropertyAccessExpression().Name()) { name = tx.Factory().NewStringLiteralFromNode(node.AsPropertyAccessExpression().Name()) } if name != nil { temp := tx.Factory().NewTempVariable() setExpr := tx.Factory().NewReflectSetCall( data.superClassReference, name, temp, data.classConstructor, ) return tx.Factory().NewAssignmentTargetWrapper(temp, setExpr) } } } return tx.Visitor().VisitEachChild(node) } // visitClassElement visits a member of a class. func (tx *classFieldsTransformer) visitClassElement(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindConstructor: return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitConstructorDeclaration, node) case ast.KindGetAccessor, ast.KindSetAccessor, ast.KindMethodDeclaration: return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitMethodOrAccessorDeclaration, node) case ast.KindPropertyDeclaration: return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitPropertyDeclaration, node) case ast.KindClassStaticBlockDeclaration: return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitClassStaticBlockDeclaration, node) case ast.KindComputedPropertyName: return tx.visitComputedPropertyName(node.AsComputedPropertyName()) case ast.KindSemicolonClassElement: return node default: if ast.IsModifierLike(node) { return tx.visitModifier(node) } return tx.visit(node) } } // visitPropertyName visits a property name of a class member. func (tx *classFieldsTransformer) visitPropertyName(name *ast.PropertyName) *ast.PropertyName { if ast.IsComputedPropertyName(name) { return tx.visitComputedPropertyName(name.AsComputedPropertyName()) } return tx.Visitor().VisitNode(name) } // visitAccessorFieldResult visits the results of an auto-accessor field transformation in a second pass. func (tx *classFieldsTransformer) visitAccessorFieldResult(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindPropertyDeclaration: return tx.transformFieldInitializer(node.AsPropertyDeclaration()) case ast.KindGetAccessor, ast.KindSetAccessor: return tx.visitClassElement(node) default: debug.FailBadSyntaxKind(node, "Expected node to either be a PropertyDeclaration, GetAccessorDeclaration, or SetAccessorDeclaration") return nil } } // visitIdentifier replaces Strada's onSubstituteNode/trySubstituteClassAlias. Instead of // substituting at emit time using NodeCheckFlags.ConstructorReference, we resolve the // identifier to its declaration and check if that declaration has a registered alias. func (tx *classFieldsTransformer) visitIdentifier(node *ast.Identifier) *ast.Node { declaration := tx.resolver.GetReferencedValueDeclaration(tx.EmitContext().MostOriginal(node.AsNode())) if declaration != nil { if alias, ok := tx.classAliases[declaration]; ok && tx.enclosingClassDeclarations.Has(declaration) { clone := alias.Clone(tx.Factory()) tx.EmitContext().SetSourceMapRange(clone, node.Loc) tx.EmitContext().SetCommentRange(clone, node.Loc) return clone } } return node.AsNode() } // visitPrivateIdentifier handles an undeclared private name. Replace it with an empty // identifier to indicate a problem with the code. // Note: private identifiers in statement position (e.g., `#;`) are intercepted earlier // by visitExpressionStatement, which preserves them so the runtime throws a SyntaxError. func (tx *classFieldsTransformer) visitPrivateIdentifier(node *ast.Node) *ast.Node { if !tx.shouldTransformPrivateElementsOrClassStaticBlocks { return node } if tx.parentNode != nil && ast.IsStatement(tx.parentNode) { return node } result := tx.Factory().NewIdentifier("") tx.EmitContext().SetOriginal(result, node) return result } // transformPrivateIdentifierInInExpression visits `#id in expr`. func (tx *classFieldsTransformer) transformPrivateIdentifierInInExpression(node *ast.BinaryExpression) *ast.Node { info := tx.accessPrivateIdentifier(node.Left) if info != nil { receiver := tx.Visitor().VisitNode(node.Right) result := tx.Factory().NewClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver) tx.EmitContext().SetOriginal(result, node.AsNode()) return result } // Private name has not been declared. Subsequent transformers will handle this error return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitPropertyAssignment(node *ast.PropertyAssignment) *ast.Node { // 13.2.5.5 RS: PropertyDefinitionEvaluation // PropertyAssignment : PropertyName `:` AssignmentExpression // ... // 5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and _isProtoSetter_ is *false*, then // a. Let _popValue_ be ? NamedEvaluation of |AssignmentExpression| with argument _propKey_. // ... if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/).AsPropertyAssignment() } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitVariableStatement(node *ast.VariableStatement) *ast.Node { savedPendingStatements := tx.pendingStatements tx.pendingStatements = nil visitedNode := tx.Visitor().VisitEachChild(node.AsNode()) if len(tx.pendingStatements) > 0 { result := make([]*ast.Node, 0, 1+len(tx.pendingStatements)) result = append(result, visitedNode) result = append(result, tx.pendingStatements...) tx.pendingStatements = savedPendingStatements return tx.Factory().NewSyntaxList(result) } tx.pendingStatements = savedPendingStatements return visitedNode } func (tx *classFieldsTransformer) visitVariableDeclaration(node *ast.VariableDeclaration) *ast.Node { // 14.3.1.2 RS: Evaluation // LexicalBinding : BindingIdentifier Initializer // ... // 3. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // a. Let _value_ be ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... // // 14.3.2.1 RS: Evaluation // VariableDeclaration : BindingIdentifier Initializer // ... // 3. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // a. Let _value_ be ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsVariableDeclaration() } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitParameterDeclaration(node *ast.ParameterDeclaration) *ast.Node { // 8.6.3 RS: IteratorBindingInitialization // SingleNameBinding : BindingIdentifier Initializer? // ... // 5. If |Initializer| is present and _v_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... // // 14.3.3.3 RS: KeyedBindingInitialization // SingleNameBinding : BindingIdentifier Initializer? // ... // 4. If |Initializer| is present and _v_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsParameterDeclaration() } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitBindingElement(node *ast.BindingElement) *ast.Node { // 8.6.3 RS: IteratorBindingInitialization // SingleNameBinding : BindingIdentifier Initializer? // ... // 5. If |Initializer| is present and _v_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... // // 14.3.3.3 RS: KeyedBindingInitialization // SingleNameBinding : BindingIdentifier Initializer? // ... // 4. If |Initializer| is present and _v_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _bindingId_. // ... if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsBindingElement() } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitExportAssignment(node *ast.ExportAssignment) *ast.Node { // 16.2.3.7 RS: Evaluation // ExportDeclaration : `export` `default` AssignmentExpression `;` // 1. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true*, then // a. Let _value_ be ? NamedEvaluation of |AssignmentExpression| with argument `"default"`. // ... // NOTE: Since emit for `export =` translates to `module.exports = ...`, the assigned name of the class // is `""`. if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { assignedName := "" if !node.IsExportEquals { assignedName = "default" } node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), true /*ignoreEmptyStringLiteral*/, assignedName).AsExportAssignment() } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) injectPendingExpressions(expression *ast.Expression) *ast.Expression { if len(tx.pendingExpressions) > 0 { if ast.IsParenthesizedExpression(expression) { tx.pendingExpressions = append(tx.pendingExpressions, expression.Expression()) expression = tx.Factory().UpdateParenthesizedExpression( expression.AsParenthesizedExpression(), tx.Factory().InlineExpressions(tx.pendingExpressions), ) } else { exprs := append(tx.pendingExpressions, expression) expression = tx.Factory().InlineExpressions(exprs) } tx.pendingExpressions = nil } return expression } func (tx *classFieldsTransformer) visitComputedPropertyName(node *ast.ComputedPropertyName) *ast.Node { // Computed property names are evaluated in the enclosing scope, not the current class. // Replaces Strada's onEmitNode for ComputedPropertyName which switches to // lexicalEnvironment?.previous. We do this explicitly during transformation. savedLexicalEnvironment := tx.lexicalEnvironment savedInsideComputedPropertyName := tx.insideComputedPropertyName tx.insideComputedPropertyName = true if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.previous != nil { tx.lexicalEnvironment = tx.lexicalEnvironment.previous } expression := tx.Visitor().VisitNode(node.Expression) tx.lexicalEnvironment = savedLexicalEnvironment tx.insideComputedPropertyName = savedInsideComputedPropertyName return tx.Factory().UpdateComputedPropertyName(node, tx.injectPendingExpressions(expression)) } func (tx *classFieldsTransformer) visitConstructorDeclaration(node *ast.Node) *ast.Node { if tx.currentClassContainer != nil { return tx.transformConstructor(node.AsConstructorDeclaration(), tx.currentClassContainer) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) shouldTransformClassElementToWeakMap(node *ast.Node) bool { if tx.shouldTransformPrivateElementsOrClassStaticBlocks { return true } return tx.shouldAlwaysTransformPrivateStaticElements(node) } func (tx *classFieldsTransformer) shouldAlwaysTransformPrivateStaticElements(node *ast.Node) bool { return ast.HasStaticModifier(node) && tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0 } // nodeHasTransformPrivateStaticElementsFlag checks the emit flag on a class node (not a member). // Unlike shouldAlwaysTransformPrivateStaticElements, this does not check HasStaticModifier, // since class nodes themselves don't have a static modifier. func (tx *classFieldsTransformer) nodeHasTransformPrivateStaticElementsFlag(node *ast.Node) bool { return tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0 } func (tx *classFieldsTransformer) visitMethodOrAccessorDeclaration(node *ast.Node) *ast.Node { debug.Assert(!ast.HasDecorators(node)) if !ast.IsPrivateIdentifierClassElementDeclaration(node) || !tx.shouldTransformClassElementToWeakMap(node) { return tx.classElementVisitor.VisitEachChild(node) } // leave invalid code untransformed info := tx.accessPrivateIdentifier(node.Name()) debug.Assert(info != nil, "Undeclared private name for property declaration.") if !info.isValid { return node } functionName := tx.getHoistedFunctionName(node) if functionName != nil { modifiers := tx.extractNonStaticNonAccessorModifiers(node) tx.EmitContext().StartVariableEnvironment() saved := tx.inIterationStatement tx.inIterationStatement = false body := tx.EmitContext().VisitFunctionBody(node.Body(), tx.Visitor()) params := tx.Visitor().VisitNodes(node.ParameterList()) tx.inIterationStatement = saved funcExpr := tx.Factory().NewFunctionExpression(modifiers, node.BodyData().AsteriskToken, functionName, nil, params, nil, nil, body) assignment := tx.Factory().NewAssignmentExpression(functionName, funcExpr) tx.addPendingExpressions(assignment) } // remove method declaration from class return nil } func (tx *classFieldsTransformer) extractNonStaticNonAccessorModifiers(node *ast.Node) *ast.ModifierList { return transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^(ast.ModifierFlagsStatic | ast.ModifierFlagsAccessor)) } func (tx *classFieldsTransformer) setCurrentClassElementAnd(classElement *ast.ClassElement, visitor func(tx *classFieldsTransformer, node *ast.Node) *ast.Node, node *ast.Node) *ast.Node { if classElement != tx.currentClassElement { saved := tx.currentClassElement tx.currentClassElement = classElement result := visitor(tx, node) tx.currentClassElement = saved return result } return visitor(tx, node) } // visitEachChildOfNode just calls Visitor.VisitEachChild, but is necessary to avoid repeated closure allocations when passing as a callback. func (tx *classFieldsTransformer) visitEachChildOfNode(node *ast.Node) *ast.Node { return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) setInIterationStatementAnd(inIteration bool, visitor func(tx *classFieldsTransformer, node *ast.Node) *ast.Node, node *ast.Node) *ast.Node { if tx.inIterationStatement != inIteration { saved := tx.inIterationStatement tx.inIterationStatement = inIteration result := visitor(tx, node) tx.inIterationStatement = saved return result } return visitor(tx, node) } func (tx *classFieldsTransformer) clearClassElementAndVisitEachChild(node *ast.Node) *ast.Node { return tx.setCurrentClassElementAnd(nil, (*classFieldsTransformer).visitEachChildOfNode, node) } // visitFunctionExpressionOrDeclaration handles lexical environment scoping for function // expressions and declarations, mirroring Strada's onEmitNode behavior. // // In Strada, onEmitNode checks whether a FunctionExpression has been registered in // lexicalEnvironmentMap (via its original node). If found, the lexical environment is // restored; otherwise it is cleared (since regular functions create a new `this` scope). // // Since Corsa performs substitution eagerly (no emit-time hooks), we replicate this by // preserving currentClassElement for function expressions whose original node is a class // member of the current class. This allows visitThisExpression to correctly substitute // `this` -> `_classThis` inside synthesized functions (e.g., ES decorator descriptor // methods for static private auto-accessors). func (tx *classFieldsTransformer) visitFunctionExpressionOrDeclaration(node *ast.Node) *ast.Node { if tx.currentClassElement != nil { original := tx.EmitContext().MostOriginal(node) if original != node && tx.currentClassContainer != nil { for _, member := range tx.currentClassContainer.Members() { if tx.EmitContext().MostOriginal(member) == original && ast.IsStatic(member) { // The function expression originates from a static class member (e.g., a // descriptor method synthesized by the ES decorator transformer for a // static private auto-accessor). Preserve the current class element so // that visitThisExpression can substitute `this` with `_classThis`. // Non-static members must NOT preserve the class element because `this` // inside their descriptor functions should remain dynamic. return tx.visitEachChildOfNode(node) } } } } return tx.setCurrentClassElementAnd(nil, (*classFieldsTransformer).visitEachChildOfNode, node) } func (tx *classFieldsTransformer) setClassElementAndVisitEachChild(node *ast.Node) *ast.Node { return tx.setCurrentClassElementAnd(node, (*classFieldsTransformer).visitEachChildOfNode, node) } func (tx *classFieldsTransformer) getHoistedFunctionName(node *ast.Node) *ast.IdentifierNode { debug.Assert(node.Name() != nil && ast.IsPrivateIdentifier(node.Name())) info := tx.accessPrivateIdentifier(node.Name()) debug.Assert(info != nil, "Undeclared private name for property declaration.") if info.kind == printer.PrivateIdentifierKindMethod { return info.methodName } if info.kind == printer.PrivateIdentifierKindAccessor { if ast.IsGetAccessorDeclaration(node) { return info.getterName } if ast.IsSetAccessorDeclaration(node) { return info.setterName } } return nil } func (tx *classFieldsTransformer) tryGetClassThis() *ast.Expression { if classThis := tx.tryGetClassThisNoContainer(); classThis != nil { return classThis } if tx.currentClassContainer != nil { return tx.currentClassContainer.Name() } return nil } func (tx *classFieldsTransformer) tryGetClassThisNoContainer() *ast.Expression { lex := tx.getClassLexicalEnvironment() if lex.classThis != nil { return lex.classThis } if lex.classConstructor != nil { return lex.classConstructor } return nil } // transformAutoAccessor transforms an auto-accessor property: // // accessor x = 1; // // into: // // #x = 1; // get x() { return this.#x; } // set x(value) { this.#x = value; } func (tx *classFieldsTransformer) transformAutoAccessor(node *ast.PropertyDeclaration) *ast.Node { commentRange := tx.EmitContext().CommentRange(node.AsNode()) sourceMapRange := tx.EmitContext().SourceMapRange(node.AsNode()) // Since we're creating two declarations where there was previously one, cache // the expression for any computed property names. name := node.Name() getterName := name setterName := name if ast.IsComputedPropertyName(name) && !transformers.IsSimpleInlineableExpression(name.Expression()) { cacheAssignment := findComputedPropertyNameCacheAssignment(tx.EmitContext(), name) if cacheAssignment != nil { getterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), tx.Visitor().VisitNode(name.Expression())) setterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), cacheAssignment.Left) } else { temp := tx.Factory().NewTempVariable() tx.EmitContext().SetSourceMapRange(temp, name.Expression().Loc) tx.EmitContext().AddVariableDeclaration(temp) expression := tx.Visitor().VisitNode(name.Expression()) assignment := tx.Factory().NewAssignmentExpression(temp, expression) tx.EmitContext().SetSourceMapRange(assignment, name.Expression().Loc) getterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), assignment) setterName = tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), temp) } } modifiers := tx.modifierVisitor.VisitModifiers(node.Modifiers()) backingField := createAccessorPropertyBackingField(tx.Factory(), node, modifiers, node.Initializer) tx.EmitContext().SetOriginal(backingField, node.AsNode()) tx.EmitContext().AddEmitFlags(backingField, printer.EFNoComments) tx.EmitContext().SetSourceMapRange(backingField, sourceMapRange) var receiver *ast.Expression if ast.IsStatic(node.AsNode()) { receiver = tx.tryGetClassThis() if receiver == nil { receiver = tx.Factory().NewThisExpression() } } else { receiver = tx.Factory().NewThisExpression() } getter := tx.createAccessorPropertyGetRedirector(node, modifiers, getterName, receiver) tx.EmitContext().SetOriginal(getter, node.AsNode()) tx.EmitContext().SetCommentRange(getter, commentRange) tx.EmitContext().SetSourceMapRange(getter, sourceMapRange) // create a fresh copy of the modifiers so that we don't duplicate comments var setterModifiers *ast.ModifierList if modifiers != nil { setterModifiers = tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers.ModifierFlags, tx.Factory().NewModifier)) } setter := tx.createAccessorPropertySetRedirector(node, setterModifiers, setterName, receiver) tx.EmitContext().SetOriginal(setter, node.AsNode()) tx.EmitContext().AddEmitFlags(setter, printer.EFNoComments) tx.EmitContext().SetSourceMapRange(setter, sourceMapRange) // Visit the results in a second pass visited, _ := tx.accessorFieldResultVisitor.VisitSlice([]*ast.Node{backingField, getter, setter}) return tx.Factory().NewSyntaxList(visited) } func (tx *classFieldsTransformer) transformPrivateFieldInitializer(node *ast.PropertyDeclaration) *ast.Node { if tx.shouldTransformClassElementToWeakMap(node.AsNode()) { // If we are transforming private elements into WeakMap/WeakSet, we should elide the node. info := tx.accessPrivateIdentifier(node.Name()) debug.Assert(info != nil, "Undeclared private name for property declaration.") // Leave invalid code untransformed if !info.isValid { return node.AsNode() } // If we encounter a valid private static field and we're not transforming // class static blocks, convert to a static block initializer. if info.isStatic && !tx.shouldTransformPrivateElementsOrClassStaticBlocks { // TODO: fix statement := tx.transformPropertyOrClassStaticBlock(node.AsNode(), tx.Factory().NewThisExpression()) if statement != nil { return tx.Factory().NewClassStaticBlockDeclaration( nil, /*modifiers*/ tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), true /*multiLine*/), ) } } return nil } if tx.shouldTransformInitializersUsingSet && !ast.HasStaticModifier(node.AsNode()) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor != 0 { return tx.Factory().UpdatePropertyDeclaration( node, tx.Visitor().VisitModifiers(node.Modifiers()), node.Name(), nil, /*postfixToken*/ nil, /*typeNode*/ nil, /*initializer*/ ) } if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsPropertyDeclaration() } return tx.Factory().UpdatePropertyDeclaration( node, tx.modifierVisitor.VisitModifiers(node.Modifiers()), tx.visitPropertyName(node.Name()), nil, /*postfixToken*/ nil, /*typeNode*/ tx.Visitor().VisitNode(node.Initializer), ) } func (tx *classFieldsTransformer) transformPublicFieldInitializer(node *ast.PropertyDeclaration) *ast.Node { if tx.shouldTransformInitializers && !ast.IsAutoAccessorPropertyDeclaration(node.AsNode()) { // Elide the property declaration; the initializer will be moved to the constructor. // For computed property names, we still need to emit the expression. expr := tx.getPropertyNameExpressionIfNeeded(node.Name(), node.Initializer != nil || tx.compilerOptions.GetUseDefineForClassFields()) if expr != nil { for e := range flattenCommaList(expr) { tx.addPendingExpressions(e) } } // When target >= ES2022 (i.e., !shouldTransformPrivateElementsOrClassStaticBlocks) and we // still need to transform initializers (useDefineForClassFields: false), static property // initializers must be converted into `static { this.x = ...; }` blocks so that `this` // refers to the class constructor inside the static block. if ast.IsStatic(node.AsNode()) && !tx.shouldTransformPrivateElementsOrClassStaticBlocks { initializerStatement := tx.transformPropertyOrClassStaticBlock(node.AsNode(), tx.Factory().NewThisExpression()) if initializerStatement != nil { staticBlock := tx.Factory().NewClassStaticBlockDeclaration( nil, /*modifiers*/ tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{initializerStatement}), false), ) tx.EmitContext().SetOriginal(staticBlock, node.AsNode()) tx.EmitContext().SetCommentRange(staticBlock, node.Loc) tx.EmitContext().AddEmitFlags(initializerStatement, printer.EFNoComments) return staticBlock } } return nil } return tx.Factory().UpdatePropertyDeclaration( node, tx.modifierVisitor.VisitModifiers(node.Modifiers()), tx.visitPropertyName(node.Name()), nil, /*postfixToken*/ nil, /*typeNode*/ tx.Visitor().VisitNode(node.Initializer), ) } func (tx *classFieldsTransformer) transformFieldInitializer(node *ast.PropertyDeclaration) *ast.Node { debug.Assert(!ast.HasDecorators(node.AsNode()), "Decorators should already have been transformed and elided.") if ast.IsPrivateIdentifierClassElementDeclaration(node.AsNode()) { return tx.transformPrivateFieldInitializer(node) } return tx.transformPublicFieldInitializer(node) } func (tx *classFieldsTransformer) shouldTransformAutoAccessorsInCurrentClass() bool { if tx.shouldTransformAutoAccessors { return true } // When targeting ESNext with useDefineForClassFields: false, auto-accessors are only // transformed if the current class will hoist initializers to the constructor. return tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor != 0 } func (tx *classFieldsTransformer) visitPropertyDeclaration(node *ast.Node) *ast.Node { // If this is an auto-accessor, we defer to `transformAutoAccessor`. That function // will in turn call `transformFieldInitializer` as needed. propDecl := node.AsPropertyDeclaration() if ast.IsAutoAccessorPropertyDeclaration(node) && (tx.shouldTransformAutoAccessorsInCurrentClass() || ast.HasStaticModifier(node) && tx.shouldAlwaysTransformPrivateStaticElements(node)) { return tx.transformAutoAccessor(propDecl) } return tx.transformFieldInitializer(propDecl) } func (tx *classFieldsTransformer) createPrivateIdentifierAccess(info *privateIdentifierInfo, receiver *ast.Expression) *ast.Expression { receiver = tx.Visitor().VisitNode(receiver) return tx.createPrivateIdentifierAccessHelper(info, receiver) } func (tx *classFieldsTransformer) createPrivateIdentifierAccessHelper(info *privateIdentifierInfo, receiver *ast.Expression) *ast.Expression { tx.EmitContext().SetCommentRange(receiver, core.NewTextRange(-1, receiver.End())) switch info.kind { case printer.PrivateIdentifierKindAccessor: return tx.Factory().NewClassPrivateFieldGetHelper( receiver, info.brandCheckIdentifier, info.kind, info.getterName, ) case printer.PrivateIdentifierKindMethod: return tx.Factory().NewClassPrivateFieldGetHelper( receiver, info.brandCheckIdentifier, info.kind, info.methodName, ) case printer.PrivateIdentifierKindField: var f *ast.IdentifierNode if info.isStatic { f = info.variableName } return tx.Factory().NewClassPrivateFieldGetHelper( receiver, info.brandCheckIdentifier, info.kind, f, ) case printer.PrivateIdentifierKindUntransformed: debug.Fail("Access helpers should not be created for untransformed private elements") return nil } debug.AssertNever(info, "Unknown private element type") return nil } func (tx *classFieldsTransformer) visitPropertyAccessExpression(node *ast.PropertyAccessExpression) *ast.Node { if ast.IsPrivateIdentifier(node.Name()) { info := tx.accessPrivateIdentifier(node.Name()) if info != nil { result := tx.createPrivateIdentifierAccess(info, node.Expression) tx.EmitContext().SetOriginal(result, node.AsNode()) result.Loc = node.Loc return result } } if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node.AsNode()) && ast.IsIdentifier(node.Name()) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { data := tx.lexicalEnvironment.data if data.facts&classFactsClassWasDecorated != 0 { return tx.visitInvalidSuperProperty(node.AsNode()) } if data.classConstructor != nil && data.superClassReference != nil { // converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)` superProperty := tx.Factory().NewReflectGetCall( data.superClassReference, tx.Factory().NewStringLiteralFromNode(node.Name()), data.classConstructor, ) tx.EmitContext().SetOriginal(superProperty, node.Expression) superProperty.Loc = node.Expression.Loc return superProperty } } // Visit only the expression, not the name (when it's a regular identifier), to prevent // substitution of property names. Strada's onSubstituteNode only fires for // EmitHint.Expression, which excludes the .name of PropertyAccessExpression. // Private identifier names are still visited through VisitEachChild so they can be // transformed by visitPrivateIdentifier. if ast.IsIdentifier(node.Name()) { return tx.visitPropertyAccessExpressionForSubstitution(node) } return tx.Visitor().VisitEachChild(node.AsNode()) } // visitPropertyAccessExpressionForSubstitution visits only the expression of a PropertyAccessExpression, // leaving the name unchanged. This prevents the name from being treated as a standalone identifier // reference and incorrectly substituted with a class alias. func (tx *classFieldsTransformer) visitPropertyAccessExpressionForSubstitution(node *ast.PropertyAccessExpression) *ast.Node { expression := tx.Visitor().VisitNode(node.Expression) if expression != node.Expression { return tx.Factory().UpdatePropertyAccessExpression(node, expression, node.QuestionDotToken, node.Name(), node.Flags) } return node.AsNode() } func (tx *classFieldsTransformer) visitElementAccessExpression(node *ast.ElementAccessExpression) *ast.Node { if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node.AsNode()) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { data := tx.lexicalEnvironment.data if data.facts&classFactsClassWasDecorated != 0 { return tx.visitInvalidSuperProperty(node.AsNode()) } if data.classConstructor != nil && data.superClassReference != nil { // converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)` superProperty := tx.Factory().NewReflectGetCall( data.superClassReference, tx.Visitor().VisitNode(node.ArgumentExpression), data.classConstructor, ) tx.EmitContext().SetOriginal(superProperty, node.Expression) superProperty.Loc = node.Expression.Loc return superProperty } } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitPreOrPostfixUnaryExpression(node *ast.Node, discarded bool) *ast.Node { var operator ast.Kind var operand *ast.Node if ast.IsPrefixUnaryExpression(node) { operator = node.AsPrefixUnaryExpression().Operator operand = node.AsPrefixUnaryExpression().Operand } else { operator = node.AsPostfixUnaryExpression().Operator operand = node.AsPostfixUnaryExpression().Operand } if operator == ast.KindPlusPlusToken || operator == ast.KindMinusMinusToken { operandSkipped := ast.SkipParentheses(operand) // Private identifier property access if ast.IsPropertyAccessExpression(operandSkipped) && ast.IsPrivateIdentifier(operandSkipped.Name()) { info := tx.accessPrivateIdentifier(operandSkipped.Name()) if info != nil { receiver := tx.Visitor().VisitNode(operandSkipped.Expression()) readExpression, initializeExpression := tx.createCopiableReceiverExpr(receiver) expression := tx.createPrivateIdentifierAccessHelper(info, readExpression) var temp *ast.IdentifierNode if !ast.IsPrefixUnaryExpression(node) && !discarded { temp = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(temp) } expression = expandPreOrPostfixIncrementOrDecrementExpression(tx.Factory(), tx.EmitContext(), node, expression, temp) assignReceiver := readExpression if initializeExpression != nil { assignReceiver = initializeExpression } expression = tx.createPrivateIdentifierAssignment(info, assignReceiver, expression, ast.KindEqualsToken) tx.EmitContext().SetOriginal(expression, node) expression.Loc = node.Loc if temp != nil { expression = tx.Factory().NewCommaExpression(expression, temp) expression.Loc = node.Loc } return expression } } else if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(operandSkipped) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { // converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)` // converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)` // converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)` // converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)` // converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)` // converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)` // converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)` // converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)` data := tx.lexicalEnvironment.data if data.facts&classFactsClassWasDecorated != 0 { visitedExpr := tx.visitInvalidSuperProperty(operandSkipped) if ast.IsPrefixUnaryExpression(node) { return tx.Factory().UpdatePrefixUnaryExpression(node.AsPrefixUnaryExpression(), node.AsPrefixUnaryExpression().Operator, visitedExpr) } return tx.Factory().UpdatePostfixUnaryExpression(node.AsPostfixUnaryExpression(), visitedExpr, node.AsPostfixUnaryExpression().Operator) } if data.classConstructor != nil && data.superClassReference != nil { var setterName *ast.Expression var getterName *ast.Expression if ast.IsPropertyAccessExpression(operandSkipped) { if ast.IsIdentifier(operandSkipped.Name()) { getterName = tx.Factory().NewStringLiteralFromNode(operandSkipped.Name()) setterName = getterName } } else if ast.IsElementAccessExpression(operandSkipped) { if transformers.IsSimpleInlineableExpression(operandSkipped.AsElementAccessExpression().ArgumentExpression) { getterName = operandSkipped.AsElementAccessExpression().ArgumentExpression setterName = getterName } else { getterName = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(getterName) setterName = tx.Factory().NewAssignmentExpression(getterName, tx.Visitor().VisitNode(operandSkipped.AsElementAccessExpression().ArgumentExpression)) } } if setterName != nil && getterName != nil { expression := tx.Factory().NewReflectGetCall(data.superClassReference, getterName, data.classConstructor) expression.Loc = operandSkipped.Loc var temp *ast.IdentifierNode if !discarded { temp = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(temp) } expression = expandPreOrPostfixIncrementOrDecrementExpression(tx.Factory(), tx.EmitContext(), node, expression, temp) expression = tx.Factory().NewReflectSetCall(data.superClassReference, setterName, expression, data.classConstructor) tx.EmitContext().SetOriginal(expression, node) expression.Loc = node.Loc if temp != nil { expression = tx.Factory().NewCommaExpression(expression, temp) expression.Loc = node.Loc } return expression } } } } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitForStatement(node *ast.ForStatement) *ast.Node { initializer := tx.discardedValueVisitor.VisitNode(node.Initializer) condition := tx.Visitor().VisitNode(node.Condition) incrementor := tx.discardedValueVisitor.VisitNode(node.Incrementor) saved := tx.inIterationStatement tx.inIterationStatement = true body := tx.EmitContext().VisitIterationBody(node.Statement, tx.Visitor()) tx.inIterationStatement = saved return tx.Factory().UpdateForStatement(node, initializer, condition, incrementor, body) } func (tx *classFieldsTransformer) visitExpressionStatement(node *ast.ExpressionStatement) *ast.Node { // Preserve private identifiers that appear directly as the expression of an // ExpressionStatement (e.g., `#;`). This is error-recovery output from the parser // for invalid syntax. Keeping it ensures the runtime throws a SyntaxError rather // than silently succeeding with an empty statement. if ast.IsPrivateIdentifier(node.Expression) && tx.shouldTransformPrivateElementsOrClassStaticBlocks { return node.AsNode() } return tx.Factory().UpdateExpressionStatement( node, tx.discardedValueVisitor.VisitNode(node.Expression), ) } func (tx *classFieldsTransformer) createCopiableReceiverExpr(receiver *ast.Expression) (readExpression *ast.Expression, initializeExpression *ast.Expression) { clone := receiver if !ast.NodeIsSynthesized(receiver) { clone = receiver.Clone(tx.Factory()) } if transformers.IsSimpleInlineableExpression(receiver) { return clone, nil } readExpression = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(readExpression) initializeExpression = tx.Factory().NewAssignmentExpression(readExpression, clone) return readExpression, initializeExpression } func (tx *classFieldsTransformer) visitCallExpression(node *ast.CallExpression) *ast.Node { if ast.IsPropertyAccessExpression(node.Expression) && ast.IsPrivateIdentifier(node.Expression.AsPropertyAccessExpression().Name()) && tx.accessPrivateIdentifier(node.Expression.AsPropertyAccessExpression().Name()) != nil { // obj.#x() // Transform call expressions of private names to properly bind the `this` parameter. thisArg, target := tx.createCallBinding(node.Expression) visitedTarget := tx.Visitor().VisitNode(target) visitedThisArg := tx.Visitor().VisitNode(thisArg) visitedArgs := tx.Visitor().VisitNodes(node.Arguments) allArgs := make([]*ast.Node, 0, 1+len(visitedArgs.Nodes)) allArgs = append(allArgs, visitedThisArg) allArgs = append(allArgs, visitedArgs.Nodes...) if node.Flags&ast.NodeFlagsOptionalChain != 0 { return tx.Factory().UpdateCallExpression( node, tx.Factory().NewPropertyAccessExpression(visitedTarget, node.QuestionDotToken, tx.Factory().NewIdentifier("call"), ast.NodeFlagsOptionalChain), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList(allArgs), node.Flags, ) } return tx.Factory().UpdateCallExpression( node, tx.Factory().NewPropertyAccessExpression(visitedTarget, nil, tx.Factory().NewIdentifier("call"), ast.NodeFlagsNone), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList(allArgs), node.Flags, ) } if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node.Expression) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.classConstructor != nil { // super.x() // super[x]() // converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)` invocation := tx.Factory().NewFunctionCallCall( tx.Visitor().VisitNode(node.Expression), tx.lexicalEnvironment.data.classConstructor, tx.Visitor().VisitNodes(node.Arguments).Nodes, ) tx.EmitContext().SetOriginal(invocation, node.AsNode()) invocation.Loc = node.Loc return invocation } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitTaggedTemplateExpression(node *ast.TaggedTemplateExpression) *ast.Node { if ast.IsPropertyAccessExpression(node.Tag) && ast.IsPrivateIdentifier(node.Tag.AsPropertyAccessExpression().Name()) && tx.accessPrivateIdentifier(node.Tag.AsPropertyAccessExpression().Name()) != nil { // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. thisArg, target := tx.createCallBinding(node.Tag) bindExpr := tx.Factory().NewCallExpression( tx.Factory().NewPropertyAccessExpression(tx.Visitor().VisitNode(target), nil, tx.Factory().NewIdentifier("bind"), ast.NodeFlagsNone), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList([]*ast.Node{tx.Visitor().VisitNode(thisArg)}), ast.NodeFlagsNone, ) return tx.Factory().UpdateTaggedTemplateExpression( node, bindExpr, nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Visitor().VisitNode(node.Template), node.Flags, ) } if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node.Tag) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.classConstructor != nil { // converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` `` invocation := tx.Factory().NewFunctionBindCall( tx.Visitor().VisitNode(node.Tag), tx.lexicalEnvironment.data.classConstructor, nil, ) tx.EmitContext().SetOriginal(invocation, node.AsNode()) invocation.Loc = node.Loc return tx.Factory().UpdateTaggedTemplateExpression( node, invocation, nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Visitor().VisitNode(node.Template), node.Flags, ) } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) transformClassStaticBlockDeclaration(node *ast.Node) *ast.Expression { if tx.shouldTransformPrivateElementsOrClassStaticBlocks { if isClassThisAssignmentBlock(tx.EmitContext(), node) { result := tx.Visitor().VisitNode(node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes[0].Expression()) // If the generated `_classThis` assignment is a noop (i.e., `_classThis = _classThis`), we can // eliminate the expression if ast.IsAssignmentExpression(result, true /*excludeCompoundAssignment*/) { binary := result.AsBinaryExpression() if binary.Left == binary.Right { return nil } } return result } if isClassNamedEvaluationHelperBlock(tx.EmitContext(), node) { return tx.Visitor().VisitNode(node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes[0].Expression()) } tx.EmitContext().StartVariableEnvironment() statements := tx.setCurrentClassElementAndVisitStatements(node, node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Nodes) statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements) iife := tx.Factory().NewImmediatelyInvokedArrowFunction(statements) arrowFunction := ast.SkipParentheses(iife.Expression()) tx.EmitContext().SetOriginal(arrowFunction, node) tx.EmitContext().AddEmitFlags(arrowFunction, printer.EFNoLexicalArguments) // Preserve the statement list source range so the printer can emit detached comments // (e.g., `// do` inside an otherwise empty static block) arrowFunction.AsArrowFunction().Body.AsBlock().Statements.Loc = node.AsClassStaticBlockDeclaration().Body.AsBlock().Statements.Loc tx.EmitContext().SetOriginal(iife, node) tx.EmitContext().AssignSourceMapRange(iife, node) tx.EmitContext().AddEmitFlags(arrowFunction, printer.EFNoLexicalThis) return iife } return nil } func (tx *classFieldsTransformer) setCurrentClassElementAndVisitStatements(classElement *ast.Node, statements []*ast.Statement) []*ast.Statement { savedCurrentClassElement := tx.currentClassElement tx.currentClassElement = classElement result, _ := tx.Visitor().VisitSlice(statements) tx.currentClassElement = savedCurrentClassElement return result } func (tx *classFieldsTransformer) isAnonymousClassNeedingAssignedNameWorker(node *anonymousFunctionDefinition) bool { if ast.IsClassExpression(node) && node.Name() == nil { staticPropertiesOrClassStaticBlocks := tx.getStaticPropertiesAndClassStaticBlock(node) if core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool { return isClassNamedEvaluationHelperBlock(tx.EmitContext(), n) }) { return false } hasTransformableStatics := (tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node)) && core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool { return ast.IsClassStaticBlockDeclaration(n) || ast.IsPrivateIdentifierClassElementDeclaration(n) || tx.shouldTransformInitializers && ast.IsInitializedProperty(n) }) return hasTransformableStatics } return false } func (tx *classFieldsTransformer) visitBinaryExpression(node *ast.BinaryExpression, discarded bool) *ast.Node { if ast.IsDestructuringAssignment(node.AsNode()) { // ({ x: obj.#x } = ...) // ({ x: super.x } = ...) // ({ x: super[x] } = ...) savedPendingExpressions := tx.pendingExpressions tx.pendingExpressions = nil updated := tx.Factory().UpdateBinaryExpression( node, nil, tx.assignmentTargetVisitor.VisitNode(node.Left), nil, node.OperatorToken, tx.Visitor().VisitNode(node.Right), ) var result *ast.Expression if len(tx.pendingExpressions) > 0 { exprs := append(tx.pendingExpressions, updated) result = tx.Factory().InlineExpressions(exprs) } else { result = updated } tx.pendingExpressions = savedPendingExpressions return result } if ast.IsAssignmentExpression(node.AsNode(), false /*excludeCompound*/) { // 13.15.2 RS: Evaluation // AssignmentExpression : LeftHandSideExpression `=` AssignmentExpression // 1. If |LeftHandSideExpression| is neither an |ObjectLiteral| nor an |ArrayLiteral|, then // a. Let _lref_ be ? Evaluation of |LeftHandSideExpression|. // b. If IsAnonymousFunctionDefinition(|AssignmentExpression|) and IsIdentifierRef of |LeftHandSideExpression| are both *true*, then // i. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]]. // ... // // AssignmentExpression : LeftHandSideExpression `&&=` AssignmentExpression // ... // 5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then // a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]]. // ... // // AssignmentExpression : LeftHandSideExpression `||=` AssignmentExpression // ... // 5. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then // a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]]. // ... // // AssignmentExpression : LeftHandSideExpression `??=` AssignmentExpression // ... // 4. If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of |LeftHandSideExpression| is *true*, then // a. Let _rval_ be ? NamedEvaluation of |AssignmentExpression| with argument _lref_.[[ReferencedName]]. // ... if isNamedEvaluationAnd(tx.EmitContext(), node.AsNode(), tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node.AsNode(), false, "").AsBinaryExpression() debug.Assert(node.AsNode() != nil && ast.IsAssignmentExpression(node.AsNode(), false)) } left := ast.SkipOuterExpressions(node.Left, ast.OEKPartiallyEmittedExpressions|ast.OEKParentheses) if ast.IsPropertyAccessExpression(left) && ast.IsPrivateIdentifier(left.Name()) { // obj.#x = ... info := tx.accessPrivateIdentifier(left.Name()) if info != nil { result := tx.createPrivateIdentifierAssignment(info, left.Expression(), node.Right, node.OperatorToken.Kind) tx.EmitContext().SetOriginal(result, node.AsNode()) result.Loc = node.Loc return result } } else if tx.shouldTransformSuperInStaticInitializers && tx.currentClassElement != nil && ast.IsSuperProperty(node.Left) && isStaticPropertyDeclarationOrClassStaticBlock(tx.currentClassElement) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { // super.x = ... // super[x] = ... // super.x += ... // super.x -= ... data := tx.lexicalEnvironment.data if data.facts&classFactsClassWasDecorated != 0 { return tx.Factory().UpdateBinaryExpression( node, nil, tx.visitInvalidSuperProperty(node.Left), nil, node.OperatorToken, tx.Visitor().VisitNode(node.Right), ) } if data.classConstructor != nil && data.superClassReference != nil { var setterName *ast.Expression if ast.IsElementAccessExpression(node.Left) { setterName = tx.Visitor().VisitNode(node.Left.AsElementAccessExpression().ArgumentExpression) } else if ast.IsPropertyAccessExpression(node.Left) && ast.IsIdentifier(node.Left.AsPropertyAccessExpression().Name()) { setterName = tx.Factory().NewStringLiteralFromNode(node.Left.AsPropertyAccessExpression().Name()) } if setterName != nil { // converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)` // converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)` // converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)` // converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)` expression := tx.Visitor().VisitNode(node.Right) if ast.IsCompoundAssignment(node.OperatorToken.Kind) { getterName := setterName if !transformers.IsSimpleInlineableExpression(setterName) { getterName = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(getterName) setterName = tx.Factory().NewAssignmentExpression(getterName, setterName) } superPropertyGet := tx.Factory().NewReflectGetCall( data.superClassReference, getterName, data.classConstructor, ) tx.EmitContext().SetOriginal(superPropertyGet, node.Left) superPropertyGet.Loc = node.Left.Loc expression = tx.Factory().NewBinaryExpression( nil, superPropertyGet, nil, tx.Factory().NewToken(transformers.GetNonAssignmentOperatorForCompoundAssignment(node.OperatorToken.Kind)), expression, ) expression.Loc = node.Loc } var temp *ast.IdentifierNode if !discarded { temp = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(temp) } if temp != nil { expression = tx.Factory().NewAssignmentExpression(temp, expression) expression.Loc = node.Loc } expression = tx.Factory().NewReflectSetCall( data.superClassReference, setterName, expression, data.classConstructor, ) tx.EmitContext().SetOriginal(expression, node.AsNode()) expression.Loc = node.Loc if temp != nil { expression = tx.Factory().NewCommaExpression(expression, temp) expression.Loc = node.Loc } return expression } } } } if node.OperatorToken.Kind == ast.KindInKeyword && ast.IsPrivateIdentifier(node.Left) { // #x in obj return tx.transformPrivateIdentifierInInExpression(node) } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, discarded bool) *ast.Node { // 8.4.5 RS: NamedEvaluation // ParenthesizedExpression : `(` Expression `)` // ... // 2. Return ? NamedEvaluation of |Expression| with argument _name_. if discarded { expression := tx.discardedValueVisitor.VisitNode(node.Expression) return tx.Factory().UpdateParenthesizedExpression(node, expression) } expression := tx.Visitor().VisitNode(node.Expression) return tx.Factory().UpdateParenthesizedExpression(node, expression) } func (tx *classFieldsTransformer) createPrivateIdentifierAssignment(info *privateIdentifierInfo, receiver *ast.Expression, right *ast.Expression, operator ast.Kind) *ast.Expression { receiver = tx.Visitor().VisitNode(receiver) right = tx.Visitor().VisitNode(right) if ast.IsCompoundAssignment(operator) { readExpression, initializeExpression := tx.createCopiableReceiverExpr(receiver) if initializeExpression != nil { receiver = initializeExpression } else { receiver = readExpression } right = tx.Factory().NewBinaryExpression( nil, tx.createPrivateIdentifierAccessHelper(info, readExpression), nil, tx.Factory().NewToken(transformers.GetNonAssignmentOperatorForCompoundAssignment(operator)), right, ) } tx.EmitContext().SetCommentRange(receiver, core.NewTextRange(-1, receiver.End())) switch info.kind { case printer.PrivateIdentifierKindAccessor: return tx.Factory().NewClassPrivateFieldSetHelper( receiver, info.brandCheckIdentifier, right, info.kind, info.setterName, ) case printer.PrivateIdentifierKindMethod: return tx.Factory().NewClassPrivateFieldSetHelper( receiver, info.brandCheckIdentifier, right, info.kind, nil, ) case printer.PrivateIdentifierKindField: var f *ast.IdentifierNode if info.isStatic { f = info.variableName } return tx.Factory().NewClassPrivateFieldSetHelper( receiver, info.brandCheckIdentifier, right, info.kind, f, ) case printer.PrivateIdentifierKindUntransformed: debug.Fail("Access helpers should not be created for untransformed private elements") return nil } debug.AssertNever(info, "Unknown private element type") return nil } func (tx *classFieldsTransformer) getPrivateInstanceMethodsAndAccessors(node *ast.Node) []*ast.Node { return core.Filter(node.Members(), isNonStaticMethodOrAccessorWithPrivateName) } // memberContainsConstructorReference checks if a class member's body contains an identifier // that resolves to the class declaration. Replaces Strada's resolver.hasNodeCheckFlag(member, // NodeCheckFlags.ContainsConstructorReference) by walking the AST with the EmitResolver. // Only checks member bodies (not computed property names), since computed property names // are evaluated during class definition when the binding is still correct. func (tx *classFieldsTransformer) memberContainsConstructorReference(member *ast.Node, classDecl *ast.Node) bool { classOriginal := tx.EmitContext().MostOriginal(classDecl) className := ast.GetNameOfDeclaration(classDecl) var check func(n *ast.Node) bool check = func(n *ast.Node) bool { if ast.IsIdentifier(n) && n != className { decl := tx.resolver.GetReferencedValueDeclaration(n) if decl == classOriginal { return true } } // For PropertyAccessExpression, only check the expression, not the name. // The .Name() is a property access name, not a value reference to the class. if ast.IsPropertyAccessExpression(n) { return check(n.Expression()) } return n.ForEachChild(check) } // Check only the body/initializer of the member, not the name (which may be // a computed property name that shouldn't trigger alias substitution). if ast.IsClassStaticBlockDeclaration(member) { body := member.AsClassStaticBlockDeclaration().Body if body != nil && check(body.AsNode()) { return true } } else { body := member.Body() if body != nil && check(body) { return true } } if ast.IsPropertyDeclaration(member) { init := member.Initializer() if init != nil && check(init) { return true } } return false } // classContainsConstructorReference checks if any member of a class contains // references to the class's own constructor. Replaces Strada's // resolver.hasNodeCheckFlag(node, NodeCheckFlags.ContainsConstructorReference). func (tx *classFieldsTransformer) classContainsConstructorReference(node *ast.Node) bool { for _, member := range node.Members() { if tx.memberContainsConstructorReference(member, node) { return true } } return false } func (tx *classFieldsTransformer) getClassFacts(node *ast.Node) classFacts { facts := classFactsNone original := tx.EmitContext().MostOriginal(node) if ast.IsClassLike(original) && ast.ClassOrConstructorParameterIsDecorated(tx.legacyDecorators /*useLegacyDecorators*/, original) { facts |= classFactsClassWasDecorated } if tx.shouldTransformPrivateElementsOrClassStaticBlocks && (classHasClassThisAssignment(tx.EmitContext(), node) || classHasExplicitlyAssignedName(tx.EmitContext(), node)) { facts |= classFactsNeedsClassConstructorReference } var containsPublicInstanceFields bool var containsInitializedPublicInstanceFields bool var containsInstancePrivateElements bool var containsInstanceAutoAccessors bool for _, member := range node.Members() { if ast.IsStatic(member) { if member.Name() != nil && (ast.IsPrivateIdentifier(member.Name()) || ast.IsAutoAccessorPropertyDeclaration(member)) && tx.shouldTransformPrivateElementsOrClassStaticBlocks { facts |= classFactsNeedsClassConstructorReference } else if ast.IsAutoAccessorPropertyDeclaration(member) && tx.shouldTransformAutoAccessors && node.Name() == nil && tx.EmitContext().ClassThis(node) == nil { facts |= classFactsNeedsClassConstructorReference } if ast.IsPropertyDeclaration(member) || ast.IsClassStaticBlockDeclaration(member) { if tx.shouldTransformThisInStaticInitializers && member.SubtreeFacts()&ast.SubtreeContainsLexicalThis != 0 { facts |= classFactsNeedsSubstitutionForThisInClassStaticField if facts&classFactsClassWasDecorated == 0 { facts |= classFactsNeedsClassConstructorReference } } if tx.shouldTransformSuperInStaticInitializers && member.SubtreeFacts()&ast.SubtreeContainsLexicalSuper != 0 { if facts&classFactsClassWasDecorated == 0 { facts |= classFactsNeedsClassConstructorReference | classFactsNeedsClassSuperReference } } } } else if !ast.HasAbstractModifier(tx.EmitContext().MostOriginal(member)) { if ast.IsAutoAccessorPropertyDeclaration(member) { containsInstanceAutoAccessors = true containsInstancePrivateElements = containsInstancePrivateElements || ast.IsPrivateIdentifierClassElementDeclaration(member) } else if ast.IsPrivateIdentifierClassElementDeclaration(member) { containsInstancePrivateElements = true if tx.memberContainsConstructorReference(member, node) { facts |= classFactsNeedsClassConstructorReference } } else if ast.IsPropertyDeclaration(member) { containsPublicInstanceFields = true containsInitializedPublicInstanceFields = containsInitializedPublicInstanceFields || member.Initializer() != nil } } } willHoistInitializersToConstructor := (tx.shouldTransformInitializersUsingDefine && containsPublicInstanceFields) || (tx.shouldTransformInitializersUsingSet && containsInitializedPublicInstanceFields) || (tx.shouldTransformPrivateElementsOrClassStaticBlocks && containsInstancePrivateElements) || (tx.shouldTransformPrivateElementsOrClassStaticBlocks && containsInstanceAutoAccessors && tx.shouldTransformAutoAccessors) if willHoistInitializersToConstructor { facts |= classFactsWillHoistInitializersToConstructor } return facts } func (tx *classFieldsTransformer) visitExpressionWithTypeArgumentsInHeritageClause(node *ast.ExpressionWithTypeArguments) *ast.Node { facts := classFactsNone if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { facts = tx.lexicalEnvironment.data.facts } if facts&classFactsNeedsClassSuperReference != 0 { temp := tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) tx.EmitContext().AddVariableDeclaration(temp) tx.getClassLexicalEnvironment().superClassReference = temp return tx.Factory().UpdateExpressionWithTypeArguments( node, tx.Factory().NewAssignmentExpression(temp, tx.Visitor().VisitNode(node.Expression)), nil, /*typeArguments*/ ) } return tx.heritageClauseVisitor.VisitEachChild(node.AsNode()) } func (tx *classFieldsTransformer) visitInNewClassLexicalEnvironment(node *ast.Node, visitor func(tx *classFieldsTransformer, node *ast.Node, facts classFacts) *ast.Node) *ast.Node { savedCurrentClassContainer := tx.currentClassContainer savedPendingExpressions := tx.pendingExpressions savedLexicalEnvironment := tx.lexicalEnvironment tx.currentClassContainer = node tx.pendingExpressions = nil tx.startClassLexicalEnvironment() original := tx.EmitContext().MostOriginal(node) tx.enclosingClassDeclarations.Add(original) if tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node) { name := ast.GetNameOfDeclaration(node) if name != nil && ast.IsIdentifier(name) { tx.getPrivateIdentifierEnvironment().data.className = name } else if assignedName := tx.EmitContext().AssignedName(node); assignedName != nil { if ast.IsStringLiteral(assignedName) { // If the assigned name has a textSourceNode that is an identifier, use it directly. if textSourceNode := tx.EmitContext().TextSource(assignedName); textSourceNode != nil && ast.IsIdentifier(textSourceNode) { tx.getPrivateIdentifierEnvironment().data.className = textSourceNode } else if scanner.IsIdentifierText(assignedName.Text(), core.LanguageVariantStandard) { // If the text is a valid identifier, create an identifier from it. prefixName := tx.Factory().NewIdentifier(assignedName.Text()) tx.getPrivateIdentifierEnvironment().data.className = prefixName } } } } if tx.shouldTransformPrivateElementsOrClassStaticBlocks { privateInstanceMethodsAndAccessors := tx.getPrivateInstanceMethodsAndAccessors(node) if len(privateInstanceMethodsAndAccessors) > 0 { tx.getPrivateIdentifierEnvironment().data.weakSetName = tx.createHoistedVariableForClass( "instances", privateInstanceMethodsAndAccessors[0].Name(), "", ) } } facts := tx.getClassFacts(node) if facts != classFactsNone { tx.getClassLexicalEnvironment().facts = facts } result := visitor(tx, node, facts) tx.enclosingClassDeclarations.Delete(original) tx.endClassLexicalEnvironment() debug.Assert(tx.lexicalEnvironment == savedLexicalEnvironment) tx.currentClassContainer = savedCurrentClassContainer tx.pendingExpressions = savedPendingExpressions tx.lexicalEnvironment = savedLexicalEnvironment return result } func (tx *classFieldsTransformer) visitClassDeclaration(node *ast.ClassDeclaration) *ast.Node { return tx.visitInNewClassLexicalEnvironment(node.AsNode(), (*classFieldsTransformer).visitClassDeclarationInNewClassLexicalEnvironment) } func (tx *classFieldsTransformer) visitClassDeclarationInNewClassLexicalEnvironment(node *ast.Node, facts classFacts) *ast.Node { classDecl := node.AsClassDeclaration() // If a class has private static fields, or a static field has a `this` or `super` reference, // then we need to allocate a temp variable to hold on to that reference. var pendingClassReferenceAssignment *ast.Expression if facts&classFactsNeedsClassConstructorReference != 0 { // If we aren't transforming class static blocks, then we can't reuse `_classThis` since in // `class C { ... static { _classThis = ... } }; _classThis = C` the outer assignment would occur *after* // class static blocks evaluate and would overwrite the replacement constructor produced by class // decorators. // If we are transforming class static blocks, then we can reuse `_classThis` since the assignment // will be evaluated *before* the transformed static blocks are evaluated and thus won't overwrite // the replacement constructor. if tx.shouldTransformPrivateElementsOrClassStaticBlocks && tx.EmitContext().ClassThis(node) != nil { classThis := tx.EmitContext().ClassThis(node) tx.getClassLexicalEnvironment().classConstructor = classThis pendingClassReferenceAssignment = tx.Factory().NewAssignmentExpression( classThis, tx.Factory().GetLocalName(node), ) } else { temp := tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) tx.EmitContext().AddVariableDeclaration(temp) tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory()) pendingClassReferenceAssignment = tx.Factory().NewAssignmentExpression( temp, tx.Factory().GetLocalName(node), ) } } if tx.EmitContext().ClassThis(node) != nil { tx.getClassLexicalEnvironment().classThis = tx.EmitContext().ClassThis(node) } isClassWithConstructorReference := tx.classContainsConstructorReference(node) // Register class alias BEFORE visiting members (Strada registers after, since its // onSubstituteNode runs at emit time; we substitute eagerly during transformation). alias := tx.getClassLexicalEnvironment().classConstructor if isClassWithConstructorReference && alias != nil { tx.classAliases[tx.EmitContext().MostOriginal(node)] = alias } modifiers := tx.modifierVisitor.VisitModifiers(classDecl.Modifiers()) heritageClauses := tx.heritageClauseVisitor.VisitNodes(classDecl.HeritageClauses) members, membersPrologue := tx.transformClassMembers(node) var statements []*ast.Node if pendingClassReferenceAssignment != nil { tx.pendingExpressions = append([]*ast.Expression{pendingClassReferenceAssignment}, tx.pendingExpressions...) } // Write any pending expressions from elided or moved computed property names if len(tx.pendingExpressions) > 0 { statements = append(statements, tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(tx.pendingExpressions))) } // A class declaration without a name needs a generated name if it has static // initialized properties, since those will be moved outside the class body and // need to reference the class by name. name := classDecl.Name() if tx.shouldTransformInitializersUsingSet || tx.shouldTransformPrivateElementsOrClassStaticBlocks { // Emit static property assignment. Because classDeclaration is lexically evaluated, // it is safe to emit static property assignment after classDeclaration // From ES6 specification: // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. staticProperties := tx.getStaticPropertiesAndClassStaticBlock(node) if len(staticProperties) > 0 { if name == nil { name = tx.Factory().NewGeneratedNameForNode(node) } statements = tx.addPropertyOrClassStaticBlockStatements(statements, staticProperties, tx.Factory().GetLocalName(node)) } } isExport := ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) isDefault := ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) if len(statements) > 0 && isExport && isDefault { modifiers = transformers.ExtractModifiers(tx.EmitContext(), modifiers, ^ast.ModifierFlagsExportDefault) exportAssignment := tx.Factory().NewExportAssignment(nil, false /*isExportEquals*/, nil /*typeNode*/, tx.Factory().GetLocalName(node)) statements = append(statements, exportAssignment) } updatedClass := tx.Factory().UpdateClassDeclaration( classDecl, modifiers, name, nil, /*typeParameters*/ heritageClauses, members, ) result := make([]*ast.Node, 0, 1+len(statements)+1) if membersPrologue != nil { result = append(result, tx.Factory().NewExpressionStatement(membersPrologue)) } result = append(result, updatedClass) result = append(result, statements...) return tx.Factory().NewSyntaxList(result) } func (tx *classFieldsTransformer) visitClassExpression(node *ast.ClassExpression) *ast.Node { return tx.visitInNewClassLexicalEnvironment(node.AsNode(), (*classFieldsTransformer).visitClassExpressionInNewClassLexicalEnvironment) } func (tx *classFieldsTransformer) visitClassExpressionInNewClassLexicalEnvironment(node *ast.Node, facts classFacts) *ast.Node { classExpr := node.AsClassExpression() // If this class expression is a transformation of a decorated class declaration, // then we want to output the pendingExpressions as statements, not as inlined // expressions with the class statement. // // In this case, we use pendingStatements to produce the same output as the // class declaration transformation. The VariableStatement visitor will insert // these statements after the class expression variable statement. isDecoratedClassDeclaration := facts&classFactsClassWasDecorated != 0 if tx.EmitContext().ClassThis(node) != nil { tx.getClassLexicalEnvironment().classThis = tx.EmitContext().ClassThis(node) } var temp *ast.IdentifierNode if facts&classFactsNeedsClassConstructorReference != 0 { if (tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node)) && tx.EmitContext().ClassThis(node) != nil { classThis := tx.EmitContext().ClassThis(node) tx.getClassLexicalEnvironment().classConstructor = classThis temp = classThis } else { temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) if tx.classExpressionNeedsBlockScopedTemp() { tx.EmitContext().AddLexicalDeclaration(temp) } else { tx.EmitContext().AddVariableDeclaration(temp) } tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory()) } } staticPropertiesOrClassStaticBlocks := tx.getStaticPropertiesAndClassStaticBlock(node) // Pre-compute whether the class expression will need a temp variable wrapper. // Strada registers class aliases AFTER transformClassMembers (since onSubstituteNode runs // at emit time), but we must predict this before visiting members since we substitute // eagerly. This requires pre-detecting willHavePrivatePendingExpressions. isClassWithConstructorReference := false hasTransformableStatics := false deferTempDeclaration := false if !isDecoratedClassDeclaration { isClassWithConstructorReference = tx.classContainsConstructorReference(node) hasTransformableStatics = (tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.nodeHasTransformPrivateStaticElementsFlag(node)) && core.Some(staticPropertiesOrClassStaticBlocks, func(n *ast.Node) bool { return ast.IsClassStaticBlockDeclaration(n) || ast.IsPrivateIdentifierClassElementDeclaration(n) || (tx.shouldTransformInitializers && ast.IsInitializedProperty(n)) }) // Private instance elements (fields, methods, accessors) transformed to // WeakMap/WeakSet will add initialization expressions to pendingExpressions // during transformClassMembers. Pre-detect this so we know whether the class // will be wrapped with a temp variable. willHavePrivatePendingExpressions := tx.shouldTransformPrivateElementsOrClassStaticBlocks && core.Some(node.Members(), func(n *ast.Node) bool { return ast.IsPrivateIdentifierClassElementDeclaration(n) && !ast.HasStaticModifier(n) && tx.shouldTransformClassElementToWeakMap(n) }) willNeedTempWrapper := hasTransformableStatics || willHavePrivatePendingExpressions // Register class alias BEFORE visiting members (Strada registers after, since its // onSubstituteNode runs at emit time). Only register when the class will be wrapped // with a temp, matching Strada's conditional registration. if isClassWithConstructorReference && willNeedTempWrapper && tx.getClassLexicalEnvironment().classConstructor == nil { // Create temp early so the alias is available during member visiting, even though in the Strada // reference the temp would be created later in the pendingExpressions branch. temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) // Defer AddVariableDeclaration to preserve Strada's variable declaration ordering. deferTempDeclaration = true tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory()) } if alias := tx.getClassLexicalEnvironment().classConstructor; isClassWithConstructorReference && willNeedTempWrapper && alias != nil { tx.classAliases[tx.EmitContext().MostOriginal(node)] = alias } } modifiers := tx.modifierVisitor.VisitModifiers(classExpr.Modifiers()) heritageClauses := tx.heritageClauseVisitor.VisitNodes(classExpr.HeritageClauses) members, membersPrologue := tx.transformClassMembers(node) if deferTempDeclaration { if tx.classExpressionNeedsBlockScopedTemp() { tx.EmitContext().AddLexicalDeclaration(temp) } else { tx.EmitContext().AddVariableDeclaration(temp) } } classExpression := tx.Factory().UpdateClassExpression( classExpr, modifiers, classExpr.Name(), nil, /*typeParameters*/ heritageClauses, members, ) var expressions []*ast.Expression if membersPrologue != nil { expressions = append(expressions, membersPrologue) } if !isDecoratedClassDeclaration { if hasTransformableStatics || len(tx.pendingExpressions) > 0 { if temp == nil { temp = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) if tx.classExpressionNeedsBlockScopedTemp() { tx.EmitContext().AddLexicalDeclaration(temp) } else { tx.EmitContext().AddVariableDeclaration(temp) } tx.getClassLexicalEnvironment().classConstructor = temp.Clone(tx.Factory()) if isClassWithConstructorReference { tx.classAliases[tx.EmitContext().MostOriginal(node)] = tx.getClassLexicalEnvironment().classConstructor } } expressions = append(expressions, tx.Factory().NewAssignmentExpression(temp, classExpression)) // Add any pending expressions leftover from elided or relocated computed property names expressions = append(expressions, tx.pendingExpressions...) expressions = append(expressions, tx.generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)...) expressions = append(expressions, temp.Clone(tx.Factory())) } else { expressions = append(expressions, classExpression) } } else { // Decorated class declaration path: emit static properties as separate statements // via pendingStatements, matching the class declaration output structure. // Write any pending expressions from elided or moved computed property names if len(tx.pendingExpressions) > 0 { for _, expr := range tx.pendingExpressions { tx.pendingStatements = append(tx.pendingStatements, tx.Factory().NewExpressionStatement(expr)) } } // Emit static properties as statements (via pendingStatements) using the class's // internal name as the receiver, matching the class declaration output structure. if len(staticPropertiesOrClassStaticBlocks) > 0 { classThisOrName := tx.EmitContext().ClassThis(node) if classThisOrName == nil { classThisOrName = tx.Factory().GetLocalName(node) } tx.pendingStatements = tx.addPropertyOrClassStaticBlockStatements(tx.pendingStatements, staticPropertiesOrClassStaticBlocks, classThisOrName) } if temp != nil { expressions = append(expressions, tx.Factory().NewAssignmentExpression(temp, classExpression)) } else if tx.shouldTransformPrivateElementsOrClassStaticBlocks && tx.EmitContext().ClassThis(node) != nil { expressions = append(expressions, tx.Factory().NewAssignmentExpression(tx.EmitContext().ClassThis(node), classExpression)) } else { expressions = append(expressions, classExpression) } } if len(expressions) > 1 { tx.EmitContext().AddEmitFlags(classExpression, printer.EFIndented) for _, expr := range expressions { tx.EmitContext().AddEmitFlags(expr, printer.EFStartOnNewLine) } } return tx.Factory().InlineExpressions(expressions) } func (tx *classFieldsTransformer) visitClassStaticBlockDeclaration(node *ast.Node) *ast.Node { if !tx.shouldTransformPrivateElementsOrClassStaticBlocks { return tx.Visitor().VisitEachChild(node) } // ClassStaticBlockDeclaration for classes are transformed in visitClassDeclaration/visitClassExpression. return nil } // visitThisExpression replaces Strada's substituteThisExpression / onSubstituteNode. // Strada substitutes `this` at emit time; we do it eagerly during transformation. // // The Strada noSubstitution set (ensureDynamicThisIfNeeded) is not needed because // transformAutoAccessor() passes the receiver directly rather than emitting `this`. func (tx *classFieldsTransformer) visitThisExpression(node *ast.Node) *ast.Node { if tx.insideComputedPropertyName && tx.shouldTransformThisInStaticInitializers && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { // Don't replace `this` in computed property names for ES-decorated classes. // The esDecorator transformer wraps them in an arrow IIFE where `this` already // refers to the correct outer scope. if tx.lexicalEnvironment.data.facts&classFactsClassWasDecorated == 0 || tx.legacyDecorators { if classThis := tx.tryGetClassThisNoContainer(); classThis != nil { return classThis } } } if tx.shouldTransformThisInStaticInitializers && tx.currentClassElement != nil && (ast.IsClassStaticBlockDeclaration(tx.currentClassElement) || (ast.IsPropertyDeclaration(tx.currentClassElement) && ast.HasStaticModifier(tx.currentClassElement))) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil { if classThis := tx.tryGetClassThisNoContainer(); classThis != nil { return classThis } // When the class was decorated with legacy decorators and no class constructor // reference is available, the decorator may replace the constructor, so `this` // cannot reliably point to the class. Use `(void 0)` instead. if tx.lexicalEnvironment.data.facts&classFactsClassWasDecorated != 0 && tx.legacyDecorators { return tx.Factory().NewParenthesizedExpression(tx.Factory().NewVoidZeroExpression()) } } return node } func (tx *classFieldsTransformer) transformClassMembers(node *ast.Node) (members *ast.NodeList, prologue *ast.Expression) { shouldTransformPrivateStaticElementsInClass := tx.EmitContext().EmitFlags(node)&printer.EFTransformPrivateStaticElements != 0 // Declare private names if tx.shouldTransformPrivateElementsOrClassStaticBlocks || tx.shouldTransformPrivateStaticElementsInFile { for _, member := range node.Members() { if ast.IsPrivateIdentifierClassElementDeclaration(member) { if tx.shouldTransformClassElementToWeakMap(member) { tx.addPrivateIdentifierToEnvironment(member) } else { env := tx.getPrivateIdentifierEnvironment() tx.setPrivateIdentifier(env, member.Name(), &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindUntransformed, }) } } } if tx.shouldTransformPrivateElementsOrClassStaticBlocks { if len(tx.getPrivateInstanceMethodsAndAccessors(node)) > 0 { tx.createBrandCheckWeakSetForPrivateMethods() } } if tx.shouldTransformAutoAccessorsInCurrentClass() { for _, member := range node.Members() { if ast.IsAutoAccessorPropertyDeclaration(member) { storageName := tx.Factory().NewGeneratedPrivateNameForNodeEx(member.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"}) if tx.shouldTransformPrivateElementsOrClassStaticBlocks || shouldTransformPrivateStaticElementsInClass && ast.HasStaticModifier(member) { tx.addPrivateIdentifierPropertyDeclarationToEnvironment(member, storageName) } else { env := tx.getPrivateIdentifierEnvironment() // Only register as untransformed if it hasn't already been registered // by the first loop (e.g., if esDecorators expanded a private auto-accessor // into a backing field with the same generated name). if _, ok := tx.getPrivateIdentifier(env, storageName); !ok { tx.setPrivateIdentifier(env, storageName, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindUntransformed, }) } } } } } } members = tx.classElementVisitor.VisitNodes(node.MemberList()) // Create a synthetic constructor if necessary var syntheticConstructor *ast.Node if !core.Some(members.Nodes, ast.IsConstructorDeclaration) { syntheticConstructor = tx.transformConstructor(nil, node) } // If there are pending expressions create a class static block in which to evaluate them, but only if // class static blocks are not also being transformed. This block will be injected at the top of the class // to ensure that expressions from computed property names are evaluated before any other static // initializers. var syntheticStaticBlock *ast.Node if !tx.shouldTransformPrivateElementsOrClassStaticBlocks && len(tx.pendingExpressions) > 0 { statement := tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(tx.pendingExpressions)) if statement.SubtreeFacts()&ast.SubtreeContainsLexicalThisOrSuper != 0 { // If there are `this` or `super` references from computed property names, shift the expression // into an arrow function to be evaluated in the outer scope so that `this` and `super` are // properly captured. temp := tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(temp) arrow := tx.Factory().NewArrowFunction( nil, /*modifiers*/ nil, /*typeParameters*/ tx.Factory().NewNodeList(nil), /*parameters*/ nil, /*returnType*/ nil, /*fullSignature*/ tx.Factory().NewToken(ast.KindEqualsGreaterThanToken), /*equalsGreaterThanToken*/ tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), false /*multiline*/), ) prologue = tx.Factory().NewAssignmentExpression(temp, arrow) statement = tx.Factory().NewExpressionStatement( tx.Factory().NewCallExpression(temp, nil /*questionDotToken*/, nil /*typeArguments*/, tx.Factory().NewNodeList(nil), ast.NodeFlagsNone), ) } block := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{statement}), false /*multiline*/) syntheticStaticBlock = tx.Factory().NewClassStaticBlockDeclaration(nil /*modifiers*/, block) tx.pendingExpressions = nil } // If we created a synthetic constructor or class static block, add them to the visited members if syntheticConstructor != nil || syntheticStaticBlock != nil { membersArray := make([]*ast.Node, 0, len(members.Nodes)+2) // Find and preserve classThis assignment block and named evaluation helper block at the top classThisIdx := slices.IndexFunc(members.Nodes, func(n *ast.Node) bool { return isClassThisAssignmentBlock(tx.EmitContext(), n) }) namedEvalIdx := slices.IndexFunc(members.Nodes, func(n *ast.Node) bool { return isClassNamedEvaluationHelperBlock(tx.EmitContext(), n) }) if classThisIdx >= 0 { membersArray = append(membersArray, members.Nodes[classThisIdx]) } if namedEvalIdx >= 0 { membersArray = append(membersArray, members.Nodes[namedEvalIdx]) } if syntheticConstructor != nil { membersArray = append(membersArray, syntheticConstructor) } if syntheticStaticBlock != nil { membersArray = append(membersArray, syntheticStaticBlock) } for i, member := range members.Nodes { if i != classThisIdx && i != namedEvalIdx { membersArray = append(membersArray, member) } } members = tx.Factory().NewNodeList(membersArray) members.Loc = node.MemberList().Loc } return members, prologue } func (tx *classFieldsTransformer) createBrandCheckWeakSetForPrivateMethods() { env := tx.getPrivateIdentifierEnvironment() weakSetName := env.data.weakSetName debug.Assert(weakSetName != nil, "weakSetName should be set in private identifier environment") tx.addPendingExpressions( tx.Factory().NewAssignmentExpression( weakSetName, tx.Factory().NewNewExpression( tx.Factory().NewIdentifier("WeakSet"), nil, /*typeArguments*/ tx.Factory().NewNodeList(nil), ), ), ) } func (tx *classFieldsTransformer) transformConstructor(constructor *ast.ConstructorDeclaration, container *ast.Node) *ast.Node { // NOTE: The Strada reference pre-visits the constructor via `visitNode(constructor, visitor)` before // checking WillHoistInitializersToConstructor. This is not done here because Go's variable environment // (StartVariableEnvironment/EndAndMergeVariableEnvironment) is scoped inside transformConstructorBody. // Pre-visiting would hoist variables outside that scope, causing them to appear after field initializers // instead of before. Instead, we visit parameters and body separately within the correct scopes. if tx.lexicalEnvironment == nil || tx.lexicalEnvironment.data == nil || tx.lexicalEnvironment.data.facts&classFactsWillHoistInitializersToConstructor == 0 { if constructor != nil { return tx.Visitor().VisitEachChild(constructor.AsNode()) } return nil } extendsClauseElement := ast.GetClassExtendsHeritageElement(container) isDerivedClass := extendsClauseElement != nil && ast.SkipOuterExpressions(extendsClauseElement.Expression(), ast.OEKAll).Kind != ast.KindNullKeyword var parameters *ast.NodeList if constructor != nil { parameters = tx.Visitor().VisitNodes(constructor.Parameters) } body := tx.transformConstructorBody(container, constructor, isDerivedClass) if body == nil { if constructor != nil { return tx.Visitor().VisitEachChild(constructor.AsNode()) } return nil } if constructor != nil { debug.Assert(parameters != nil) return tx.Factory().UpdateConstructorDeclaration( constructor, nil, /*modifiers*/ nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ body, ) } if parameters == nil { parameters = tx.Factory().NewNodeList(nil) } result := tx.Factory().NewConstructorDeclaration( nil, /*modifiers*/ nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ body, ) result.Loc = container.Loc return result } func (tx *classFieldsTransformer) transformConstructorBodyWorker( statementsOut []*ast.Statement, statementsIn []*ast.Statement, statementOffset int, superPath []int, superPathDepth int, initializerStatements []*ast.Statement, constructor *ast.ConstructorDeclaration, ) []*ast.Statement { superStatementIndex := superPath[superPathDepth] superStatement := statementsIn[superStatementIndex] // Visit statements before super visited, _ := tx.Visitor().VisitSlice(statementsIn[statementOffset:superStatementIndex]) statementsOut = append(statementsOut, visited...) statementOffset = superStatementIndex + 1 if ast.IsTryStatement(superStatement) { tryBlock := superStatement.AsTryStatement().TryBlock.AsBlock() tryBlockStatements := tx.transformConstructorBodyWorker( nil, tryBlock.Statements.Nodes, 0, /*statementOffset*/ superPath, superPathDepth+1, initializerStatements, constructor, ) tryStatementList := tx.Factory().NewNodeList(tryBlockStatements) tryStatementList.Loc = tryBlock.Statements.Loc catchClause := tx.Visitor().VisitNode(superStatement.AsTryStatement().CatchClause) finallyBlock := tx.Visitor().VisitNode(superStatement.AsTryStatement().FinallyBlock) updated := tx.Factory().UpdateTryStatement( superStatement.AsTryStatement(), tx.Factory().UpdateBlock(tryBlock, tryStatementList, tryBlock.MultiLine), catchClause, finallyBlock, ) statementsOut = append(statementsOut, updated) } else { visited, _ := tx.Visitor().VisitSlice(statementsIn[superStatementIndex : superStatementIndex+1]) statementsOut = append(statementsOut, visited...) // Add the property initializers. Transforms this: // // public x = 1; // // Into this: // // constructor() { // this.x = 1; // } // // If we do useDefineForClassFields, they'll be converted elsewhere. // We instead *remove* them from the transformed output at this stage. // parameter-property assignments should occur immediately after the prologue and `super()`, // so only count the statements that immediately follow. for statementOffset < len(statementsIn) { stmt := statementsIn[statementOffset] orig := tx.EmitContext().MostOriginal(stmt) if ast.IsParameterPropertyDeclaration(orig, constructor.AsNode()) { statementOffset++ } else { break } } statementsOut = append(statementsOut, initializerStatements...) } // Visit remaining statements visited2, _ := tx.Visitor().VisitSlice(statementsIn[statementOffset:]) statementsOut = append(statementsOut, visited2...) return statementsOut } func (tx *classFieldsTransformer) transformConstructorBody(container *ast.Node, constructor *ast.ConstructorDeclaration, isDerivedClass bool) *ast.Node { instanceProperties := tx.getProperties(container, false /*requireInitializer*/, false /*isStatic*/) properties := instanceProperties if !tx.compilerOptions.GetUseDefineForClassFields() { properties = core.Filter(properties, func(prop *ast.Node) bool { return prop.Initializer() != nil || ast.IsPrivateIdentifier(prop.Name()) || ast.HasAccessorModifier(prop) }) } privateMethodsAndAccessors := tx.getPrivateInstanceMethodsAndAccessors(container) needsConstructorBody := len(properties) > 0 || len(privateMethodsAndAccessors) > 0 // Only generate synthetic constructor when there are property initializers to move. if constructor == nil && !needsConstructorBody { return tx.EmitContext().VisitFunctionBody(nil, tx.Visitor()) } tx.EmitContext().StartVariableEnvironment() needsSyntheticConstructor := constructor == nil && isDerivedClass var statements []*ast.Statement // Add the property initializers. Transforms this: // // public x = 1; // // Into this: // // constructor() { // this.x = 1; // } // var initializerStatements []*ast.Statement receiver := tx.Factory().NewThisExpression() // private methods can be called in property initializers, they should execute first initializerStatements = tx.addInstanceMethodStatements(initializerStatements, privateMethodsAndAccessors, receiver) if constructor != nil { parameterProperties := core.Filter(instanceProperties, func(prop *ast.Node) bool { return ast.IsParameterPropertyDeclaration(tx.EmitContext().MostOriginal(prop), constructor.AsNode()) }) nonParameterProperties := core.Filter(properties, func(prop *ast.Node) bool { return !ast.IsParameterPropertyDeclaration(tx.EmitContext().MostOriginal(prop), constructor.AsNode()) }) initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, parameterProperties, receiver) initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, nonParameterProperties, receiver) } else { initializerStatements = tx.addPropertyOrClassStaticBlockStatements(initializerStatements, properties, receiver) } if constructor != nil && constructor.Body != nil { body := constructor.Body.AsBlock() // Copy prologue for _, stmt := range body.Statements.Nodes { if ast.IsPrologueDirective(stmt) { statements = append(statements, stmt) } else { break } } statementOffset := len(statements) superPath := transformers.FindSuperStatementIndexPath(body.Statements.Nodes, statementOffset) if len(superPath) > 0 { statements = tx.transformConstructorBodyWorker(statements, body.Statements.Nodes, statementOffset, superPath, 0, initializerStatements, constructor) } else { // parameter-property assignments should occur immediately after the prologue and `super()`, // so only count the statements that immediately follow. for statementOffset < len(body.Statements.Nodes) { stmt := body.Statements.Nodes[statementOffset] orig := tx.EmitContext().MostOriginal(stmt) if ast.IsParameterPropertyDeclaration(orig, constructor.AsNode()) { statementOffset++ } else { break } } statements = append(statements, initializerStatements...) visited, _ := tx.Visitor().VisitSlice(body.Statements.Nodes[statementOffset:]) statements = append(statements, visited...) } } else { if needsSyntheticConstructor { // Add a synthetic `super` call: // // super(...arguments); // superCall := tx.Factory().NewExpressionStatement( tx.Factory().NewCallExpression( tx.Factory().NewKeywordExpression(ast.KindSuperKeyword), nil, /*typeArguments*/ nil, /*questionDotToken*/ tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewSpreadElement(tx.Factory().NewIdentifier("arguments")), }), ast.NodeFlagsNone, ), ) statements = append(statements, superCall) } statements = append(statements, initializerStatements...) } statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements) if len(statements) == 0 && constructor == nil { return nil } var multiLine bool if constructor != nil && constructor.Body != nil && len(constructor.Body.AsBlock().Statements.Nodes) >= len(statements) { multiLine = constructor.Body.AsBlock().MultiLine } else { multiLine = len(statements) > 0 } statementList := tx.Factory().NewNodeList(statements) if constructor != nil && constructor.Body != nil { statementList.Loc = constructor.Body.AsBlock().Statements.Loc } else { statementList.Loc = core.NewTextRange(container.MemberList().Loc.Pos(), container.MemberList().Loc.End()) } block := tx.Factory().NewBlock(statementList, multiLine) if constructor != nil && constructor.Body != nil { block.Loc = constructor.Body.Loc } return block } // addPropertyOrClassStaticBlockStatements generates assignment statements for property initializers. func (tx *classFieldsTransformer) addPropertyOrClassStaticBlockStatements(statements []*ast.Node, properties []*ast.Node, receiver *ast.Expression) []*ast.Node { for _, property := range properties { if ast.IsStatic(property) && !tx.shouldTransformPrivateElementsOrClassStaticBlocks { continue } statement := tx.transformPropertyOrClassStaticBlock(property, receiver) if statement != nil { statements = append(statements, statement) } } return statements } func (tx *classFieldsTransformer) transformPropertyOrClassStaticBlock(property *ast.Node, receiver *ast.Expression) *ast.Node { var expression *ast.Expression if ast.IsClassStaticBlockDeclaration(property) { expression = tx.setCurrentClassElementAnd(property, (*classFieldsTransformer).transformClassStaticBlockDeclaration, property) } else { expression = tx.transformProperty(property.AsPropertyDeclaration(), receiver) } if expression == nil { return nil } statement := tx.Factory().NewExpressionStatement(expression) tx.EmitContext().SetOriginal(statement, property) tx.EmitContext().AddEmitFlags(statement, tx.EmitContext().EmitFlags(property)&printer.EFNoComments) tx.EmitContext().SetCommentRange(statement, property.Loc) propertyOriginalNode := tx.EmitContext().MostOriginal(property) if ast.IsParameterDeclaration(propertyOriginalNode) { tx.EmitContext().SetSourceMapRange(statement, propertyOriginalNode.Loc) tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments) } else { tx.EmitContext().SetSourceMapRange(statement, transformers.MoveRangePastModifiers(property)) } // `setOriginalNode` *copies* the `emitNode` from `property`, so now both // `statement` and `expression` have a copy of the synthesized comments. // Drop the comments from expression to avoid printing them twice. tx.EmitContext().SetSyntheticLeadingComments(expression, nil) tx.EmitContext().SetSyntheticTrailingComments(expression, nil) // If the property was originally an auto-accessor, don't emit comments here since they will be attached to // the synthesized getter. if ast.HasAccessorModifier(propertyOriginalNode) { tx.EmitContext().AddEmitFlags(statement, printer.EFNoComments) } return statement } // generateInitializedPropertyExpressionsOrClassStaticBlock generates assignment expressions for property initializers. func (tx *classFieldsTransformer) generateInitializedPropertyExpressionsOrClassStaticBlock( propertiesOrClassStaticBlocks []*ast.Node, receiver *ast.Expression, ) []*ast.Expression { var expressions []*ast.Expression for _, property := range propertiesOrClassStaticBlocks { var expression *ast.Expression if ast.IsClassStaticBlockDeclaration(property) { expression = tx.setCurrentClassElementAnd(property, (*classFieldsTransformer).transformClassStaticBlockDeclaration, property) } else { expression = tx.transformProperty(property.AsPropertyDeclaration(), receiver) } if expression == nil { continue } tx.EmitContext().SetOriginalEx(expression, property, true /*allowOverwrite*/) tx.EmitContext().AssignCommentAndSourceMapRanges(expression, property) expressions = append(expressions, expression) } return expressions } // transformProperty transforms a property initializer into an assignment expression. func (tx *classFieldsTransformer) transformProperty(property *ast.PropertyDeclaration, receiver *ast.Expression) *ast.Expression { savedCurrentClassElement := tx.currentClassElement transformed := tx.transformPropertyWorker(property, receiver) if transformed != nil && ast.HasStaticModifier(property.AsNode()) { tx.EmitContext().AddEmitFlags(transformed, printer.EFNoLexicalThis) } if transformed != nil && ast.HasStaticModifier(property.AsNode()) && tx.lexicalEnvironment != nil && tx.lexicalEnvironment.data != nil && tx.lexicalEnvironment.data.facts != 0 { // capture the lexical environment for the member tx.EmitContext().SetOriginal(transformed, property.AsNode()) tx.EmitContext().SetSourceMapRange(transformed, tx.EmitContext().SourceMapRange(property.Name())) } tx.currentClassElement = savedCurrentClassElement return transformed } func (tx *classFieldsTransformer) transformPropertyWorker(property *ast.PropertyDeclaration, receiver *ast.Expression) *ast.Expression { // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) emitAssignment := !tx.compilerOptions.GetUseDefineForClassFields() if isNamedEvaluationAnd(tx.EmitContext(), property.AsNode(), tx.isAnonymousClassNeedingAssignedName) { property = transformNamedEvaluation(tx.EmitContext(), property.AsNode(), false, "").AsPropertyDeclaration() } propertyName := property.Name() if ast.HasAccessorModifier(property.AsNode()) { propertyName = tx.Factory().NewGeneratedPrivateNameForNodeEx(property.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"}) } else if ast.IsComputedPropertyName(propertyName) && !transformers.IsSimpleInlineableExpression(propertyName.Expression()) { propertyName = tx.Factory().UpdateComputedPropertyName( propertyName.AsComputedPropertyName(), tx.Factory().NewGeneratedNameForNode(propertyName), ) } if ast.HasStaticModifier(property.AsNode()) { tx.currentClassElement = property.AsNode() } if ast.IsPrivateIdentifier(propertyName) && tx.shouldTransformClassElementToWeakMap(property.AsNode()) { info := tx.accessPrivateIdentifier(propertyName) if info != nil { if info.kind == printer.PrivateIdentifierKindField { if !info.isStatic { return createPrivateInstanceFieldInitializer( tx.Factory(), receiver, tx.Visitor().VisitNode(property.Initializer), info.brandCheckIdentifier, ) } return createPrivateStaticFieldInitializer( tx.Factory(), info.variableName, tx.Visitor().VisitNode(property.Initializer), ) } return nil } else { debug.Fail("Undeclared private name for property declaration.") } } if (ast.IsPrivateIdentifier(propertyName) || ast.HasStaticModifier(property.AsNode())) && property.Initializer == nil { return nil } // TODO: can we get rid of this original checking and better coordinate with runtimesyntax? if ast.HasAbstractModifier(tx.EmitContext().MostOriginal(property.AsNode())) { return nil } initializer := tx.Visitor().VisitNode(property.Initializer) propertyOriginalNode := tx.EmitContext().MostOriginal(property.AsNode()) if ast.IsParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.Parent) && ast.IsIdentifier(propertyName) { //nolint:customlint // MostOriginal returns parse-tree nodes, and this parent relationship is intentional. // A parameter-property declaration always overrides the initializer. The only time a parameter-property // declaration *should* have an initializer is when decorators have added initializers that need to run before // any other initializer localName := propertyName.Clone(tx.Factory()) if initializer != nil { // unwrap `(__runInitializers(this, _instanceExtraInitializers), void 0)` if ast.IsParenthesizedExpression(initializer) && ast.IsCommaExpression(initializer.Expression()) && tx.EmitContext().IsCallToHelper(initializer.Expression().AsBinaryExpression().Left, "__runInitializers") && ast.IsVoidExpression(initializer.Expression().AsBinaryExpression().Right) && ast.IsNumericLiteral(initializer.Expression().AsBinaryExpression().Right.Expression()) { initializer = initializer.Expression().AsBinaryExpression().Left } initializer = tx.Factory().InlineExpressions([]*ast.Expression{initializer, localName}) } else { initializer = localName } tx.EmitContext().AddEmitFlags(propertyName, printer.EFNoComments|printer.EFNoSourceMap) tx.EmitContext().SetSourceMapRange(localName, propertyOriginalNode.Name().Loc) tx.EmitContext().AddEmitFlags(localName, printer.EFNoComments) } else if initializer == nil { initializer = tx.Factory().NewVoidZeroExpression() } if emitAssignment || ast.IsPrivateIdentifier(propertyName) { memberAccess := createMemberAccessForPropertyName(tx.Factory(), tx.EmitContext(), receiver, propertyName, propertyName) tx.EmitContext().AddEmitFlags(memberAccess, printer.EFNoLeadingComments) return tx.Factory().NewAssignmentExpression(memberAccess, initializer) } // useDefineForClassFields: Object.defineProperty var name *ast.Expression if ast.IsComputedPropertyName(propertyName) { name = propertyName.Expression() } else if ast.IsIdentifier(propertyName) { name = tx.Factory().NewStringLiteral(propertyName.Text(), ast.TokenFlagsNone) } else { name = propertyName } descriptor := tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("enumerable"), nil, nil, tx.Factory().NewTrueExpression()), tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("configurable"), nil, nil, tx.Factory().NewTrueExpression()), tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("writable"), nil, nil, tx.Factory().NewTrueExpression()), tx.Factory().NewPropertyAssignment(nil, tx.Factory().NewIdentifier("value"), nil, nil, initializer), }), true) return tx.Factory().NewObjectDefinePropertyCall(receiver, name, descriptor) } // addInstanceMethodStatements generates brand-check initializer for private methods. func (tx *classFieldsTransformer) addInstanceMethodStatements(statements []*ast.Statement, methods []*ast.Node, receiver *ast.Expression) []*ast.Statement { if !tx.shouldTransformPrivateElementsOrClassStaticBlocks || len(methods) == 0 { return statements } env := tx.getPrivateIdentifierEnvironment() weakSetName := env.data.weakSetName debug.Assert(weakSetName != nil, "weakSetName should be set in private identifier environment") return append( statements, tx.Factory().NewExpressionStatement( createPrivateInstanceMethodInitializer(tx.Factory(), receiver, weakSetName), ), ) } func (tx *classFieldsTransformer) visitInvalidSuperProperty(node *ast.Node) *ast.Node { if ast.IsPropertyAccessExpression(node) { return tx.Factory().UpdatePropertyAccessExpression( node.AsPropertyAccessExpression(), tx.Factory().NewVoidZeroExpression(), nil, node.Name(), node.Flags, ) } return tx.Factory().UpdateElementAccessExpression( node.AsElementAccessExpression(), tx.Factory().NewVoidZeroExpression(), nil, tx.Visitor().VisitNode(node.AsElementAccessExpression().ArgumentExpression), node.Flags, ) } // getPropertyNameExpressionIfNeeded transforms a computed property name, then either returns an expression // which caches the value of the result or the expression itself if the value is either unused or safe to // inline into multiple locations. // shouldHoist indicates whether the expression needs to be reused (i.e., for an initializer or a decorator). func (tx *classFieldsTransformer) getPropertyNameExpressionIfNeeded(name *ast.PropertyName, shouldHoist bool) *ast.Expression { if !ast.IsComputedPropertyName(name) { return nil } cacheAssignment := findComputedPropertyNameCacheAssignment(tx.EmitContext(), name) // Switch to outer lex env for computed property name expressions, matching // Strada reference's onEmitNode behavior for ComputedPropertyName. savedLexicalEnvironment := tx.lexicalEnvironment savedInsideComputedPropertyName := tx.insideComputedPropertyName tx.insideComputedPropertyName = true if tx.lexicalEnvironment != nil && tx.lexicalEnvironment.previous != nil { tx.lexicalEnvironment = tx.lexicalEnvironment.previous } expression := tx.Visitor().VisitNode(name.Expression()) tx.lexicalEnvironment = savedLexicalEnvironment tx.insideComputedPropertyName = savedInsideComputedPropertyName innerExpression := ast.SkipPartiallyEmittedExpressions(expression) inlinable := transformers.IsSimpleInlineableExpression(innerExpression) alreadyTransformed := cacheAssignment != nil || (ast.IsAssignmentExpression(innerExpression, true /*excludeCompoundAssignment*/) && ast.IsIdentifier(innerExpression.AsBinaryExpression().Left) && transformers.IsGeneratedIdentifier(tx.EmitContext(), innerExpression.AsBinaryExpression().Left)) if !alreadyTransformed && !inlinable && shouldHoist { generatedName := tx.Factory().NewGeneratedNameForNode(name) if tx.requiresBlockScopedVar() { tx.EmitContext().AddLexicalDeclaration(generatedName) } else { tx.EmitContext().AddVariableDeclaration(generatedName) } return tx.Factory().NewAssignmentExpression(generatedName, expression) } if inlinable || ast.IsIdentifier(innerExpression) { return nil } return expression } func (tx *classFieldsTransformer) startClassLexicalEnvironment() { tx.lexicalEnvironment = &classLexicalEnv{previous: tx.lexicalEnvironment} } func (tx *classFieldsTransformer) endClassLexicalEnvironment() { tx.lexicalEnvironment = tx.lexicalEnvironment.previous } func (tx *classFieldsTransformer) getClassLexicalEnvironment() *classLexicalEnvironment { debug.Assert(tx.lexicalEnvironment != nil) if tx.lexicalEnvironment.data == nil { tx.lexicalEnvironment.data = &classLexicalEnvironment{} } return tx.lexicalEnvironment.data } func (tx *classFieldsTransformer) getPrivateIdentifierEnvironment() *privateEnvironment { debug.Assert(tx.lexicalEnvironment != nil) if tx.lexicalEnvironment.privateEnv == nil { tx.lexicalEnvironment.privateEnv = &privateEnvironment{ members: make(map[string]*privateIdentifierInfo), } } return tx.lexicalEnvironment.privateEnv } func (tx *classFieldsTransformer) addPendingExpressions(exprs ...*ast.Expression) { tx.pendingExpressions = append(tx.pendingExpressions, exprs...) } func (tx *classFieldsTransformer) addPrivateIdentifierPropertyDeclarationToEnvironment(node *ast.Node, name *ast.Node) { lex := tx.getClassLexicalEnvironment() env := tx.getPrivateIdentifierEnvironment() isStatic := ast.HasStaticModifier(node) previousInfo, _ := tx.getPrivateIdentifier(env, name) isValid := !tx.isReservedPrivateName(name) && previousInfo == nil if isStatic { brandCheckIdentifier := lex.classThis if brandCheckIdentifier == nil { brandCheckIdentifier = lex.classConstructor } variableName := tx.createHoistedVariableForPrivateName(name, "") tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindField, isStatic: true, brandCheckIdentifier: brandCheckIdentifier, variableName: variableName, isValid: isValid, }) } else { weakMapName := tx.createHoistedVariableForPrivateName(name, "") tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindField, isStatic: false, brandCheckIdentifier: weakMapName, isValid: isValid, }) tx.addPendingExpressions( tx.Factory().NewAssignmentExpression( weakMapName, tx.Factory().NewNewExpression( tx.Factory().NewIdentifier("WeakMap"), nil, /*typeArguments*/ tx.Factory().NewNodeList(nil), ), ), ) } } func (tx *classFieldsTransformer) addPrivateIdentifierMethodToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool) { methodName := tx.createHoistedVariableForPrivateName(name, "") var brandCheckIdentifier *ast.IdentifierNode if isStatic { brandCheckIdentifier = lex.classThis if brandCheckIdentifier == nil { brandCheckIdentifier = lex.classConstructor } debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment") } else { brandCheckIdentifier = env.data.weakSetName } tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindMethod, methodName: methodName, brandCheckIdentifier: brandCheckIdentifier, isStatic: isStatic, isValid: isValid, }) } func (tx *classFieldsTransformer) addPrivateIdentifierGetAccessorToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool, previousInfo *privateIdentifierInfo) { getterName := tx.createHoistedVariableForPrivateName(name, "_get") var brandCheckIdentifier *ast.IdentifierNode if isStatic { brandCheckIdentifier = lex.classThis if brandCheckIdentifier == nil { brandCheckIdentifier = lex.classConstructor } debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment") } else { brandCheckIdentifier = env.data.weakSetName debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment") } if previousInfo != nil && previousInfo.kind == printer.PrivateIdentifierKindAccessor && previousInfo.isStatic == isStatic && previousInfo.getterName == nil { previousInfo.getterName = getterName } else { tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindAccessor, getterName: getterName, brandCheckIdentifier: brandCheckIdentifier, isStatic: isStatic, isValid: isValid, }) } } func (tx *classFieldsTransformer) addPrivateIdentifierSetAccessorToEnvironment(name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool, previousInfo *privateIdentifierInfo) { setterName := tx.createHoistedVariableForPrivateName(name, "_set") var brandCheckIdentifier *ast.IdentifierNode if isStatic { brandCheckIdentifier = lex.classThis if brandCheckIdentifier == nil { brandCheckIdentifier = lex.classConstructor } debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment") } else { brandCheckIdentifier = env.data.weakSetName debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment") } if previousInfo != nil && previousInfo.kind == printer.PrivateIdentifierKindAccessor && previousInfo.isStatic == isStatic && previousInfo.setterName == nil { previousInfo.setterName = setterName } else { tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindAccessor, setterName: setterName, brandCheckIdentifier: brandCheckIdentifier, isStatic: isStatic, isValid: isValid, }) } } func (tx *classFieldsTransformer) addPrivateIdentifierAutoAccessorToEnvironment(node *ast.Node, name *ast.Node, lex *classLexicalEnvironment, env *privateEnvironment, isStatic bool, isValid bool) { getterName := tx.createHoistedVariableForPrivateName(name, "_get") setterName := tx.createHoistedVariableForPrivateName(name, "_set") var brandCheckIdentifier *ast.IdentifierNode if isStatic { brandCheckIdentifier = lex.classThis if brandCheckIdentifier == nil { brandCheckIdentifier = lex.classConstructor } debug.Assert(brandCheckIdentifier != nil, "classConstructor should be set in private identifier environment") } else { brandCheckIdentifier = env.data.weakSetName debug.Assert(brandCheckIdentifier != nil, "weakSetName should be set in private identifier environment") } tx.setPrivateIdentifier(env, name, &privateIdentifierInfo{ kind: printer.PrivateIdentifierKindAccessor, getterName: getterName, setterName: setterName, brandCheckIdentifier: brandCheckIdentifier, isStatic: isStatic, isValid: isValid, }) } func (tx *classFieldsTransformer) addPrivateIdentifierToEnvironment(node *ast.Node) { lex := tx.getClassLexicalEnvironment() env := tx.getPrivateIdentifierEnvironment() name := node.Name() isStatic := ast.HasStaticModifier(node) previousInfo, _ := tx.getPrivateIdentifier(env, name) isValid := !tx.isReservedPrivateName(name) && previousInfo == nil if ast.IsAutoAccessorPropertyDeclaration(node) { tx.addPrivateIdentifierAutoAccessorToEnvironment(node, name, lex, env, isStatic, isValid) } else if ast.IsPropertyDeclaration(node) { tx.addPrivateIdentifierPropertyDeclarationToEnvironment(node, name) } else if ast.IsMethodDeclaration(node) { tx.addPrivateIdentifierMethodToEnvironment(name, lex, env, isStatic, isValid) } else if ast.IsGetAccessorDeclaration(node) { tx.addPrivateIdentifierGetAccessorToEnvironment(name, lex, env, isStatic, isValid, previousInfo) } else if ast.IsSetAccessorDeclaration(node) { tx.addPrivateIdentifierSetAccessorToEnvironment(name, lex, env, isStatic, isValid, previousInfo) } } func (tx *classFieldsTransformer) setPrivateIdentifier(env *privateEnvironment, name *ast.Node, info *privateIdentifierInfo) { if tx.EmitContext().HasAutoGenerateInfo(name) { if env.generatedIdentifiers == nil { env.generatedIdentifiers = make(map[*ast.Node]*privateIdentifierInfo) } env.generatedIdentifiers[tx.EmitContext().GetNodeForGeneratedName(name)] = info } else { env.members[name.Text()] = info } } func (tx *classFieldsTransformer) getPrivateIdentifier(env *privateEnvironment, name *ast.Node) (*privateIdentifierInfo, bool) { if tx.EmitContext().HasAutoGenerateInfo(name) { info, ok := env.generatedIdentifiers[tx.EmitContext().GetNodeForGeneratedName(name)] return info, ok } info, ok := env.members[name.Text()] return info, ok } func (tx *classFieldsTransformer) createHoistedVariableForClass(nameText string, node *ast.Node, suffix string) *ast.IdentifierNode { env := tx.getPrivateIdentifierEnvironment() var identifier *ast.IdentifierNode if env.data.className != nil { prefix := "_" + env.data.className.Text() + "_" identifier = tx.Factory().NewUniqueNameEx(prefix+nameText, printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes, Suffix: suffix, }) } else { identifier = tx.Factory().NewUniqueNameEx("_"+nameText, printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes, Suffix: suffix, }) } if tx.requiresBlockScopedVar() { tx.EmitContext().AddLexicalDeclaration(identifier) } else { tx.EmitContext().AddVariableDeclaration(identifier) } return identifier } func (tx *classFieldsTransformer) createHoistedVariableForClassFromNode(name *ast.Node, suffix string) *ast.IdentifierNode { env := tx.getPrivateIdentifierEnvironment() var prefix string if env.data.className != nil { prefix = "_" + env.data.className.Text() + "_" } else { prefix = "_" } identifier := tx.Factory().NewGeneratedNameForNodeEx(name, printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsReservedInNestedScopes, Prefix: prefix, Suffix: suffix, }) if tx.requiresBlockScopedVar() { tx.EmitContext().AddLexicalDeclaration(identifier) } else { tx.EmitContext().AddVariableDeclaration(identifier) } return identifier } func (tx *classFieldsTransformer) createHoistedVariableForPrivateName(name *ast.Node, suffix string) *ast.IdentifierNode { // If the name is a generated identifier (e.g., auto-accessor backing field), // use node-based name generation so the emitter can resolve the name properly. if tx.EmitContext().HasAutoGenerateInfo(name) { return tx.createHoistedVariableForClassFromNode(name, suffix) } text := name.Text() if len(text) >= 1 && text[0] == '#' { text = text[1:] // strip leading '#' } return tx.createHoistedVariableForClass(text, name, suffix) } // accessPrivateIdentifier accesses an already defined PrivateIdentifier in the current // PrivateIdentifierEnvironment. func (tx *classFieldsTransformer) accessPrivateIdentifier(name *ast.Node) *privateIdentifierInfo { for env := tx.lexicalEnvironment; env != nil; env = env.previous { if env.privateEnv != nil { if info, ok := tx.getPrivateIdentifier(env.privateEnv, name); ok { if info.kind == printer.PrivateIdentifierKindUntransformed { return nil } return info } } } return nil } func (tx *classFieldsTransformer) wrapPrivateIdentifierForDestructuringTarget(node *ast.Node) *ast.Node { prop := node.AsPropertyAccessExpression() parameter := tx.Factory().NewGeneratedNameForNode(node) info := tx.accessPrivateIdentifier(prop.Name()) if info == nil { return tx.Visitor().VisitEachChild(node) } receiver := prop.Expression // We cannot copy `this` or `super` into the function because they will be bound // differently inside the function. isThisOrSuperProperty := prop.Expression.Kind == ast.KindThisKeyword || prop.Expression.Kind == ast.KindSuperKeyword if isThisOrSuperProperty || !transformers.IsSimpleCopiableExpression(prop.Expression) { receiver = tx.Factory().NewTempVariableEx(printer.AutoGenerateOptions{ Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes, }) tx.EmitContext().AddVariableDeclaration(receiver) tx.pendingExpressions = append( tx.pendingExpressions, tx.Factory().NewAssignmentExpression(receiver, tx.Visitor().VisitNode(prop.Expression)), ) } assignExpr := tx.createPrivateIdentifierAssignment(info, receiver, parameter, ast.KindEqualsToken) return tx.Factory().NewAssignmentTargetWrapper(parameter, assignExpr) } func (tx *classFieldsTransformer) visitAssignmentElement(node *ast.Node) *ast.Node { // 13.15.5.5 RS: IteratorDestructuringAssignmentEvaluation // AssignmentElement : DestructuringAssignmentTarget Initializer? // ... // 4. If |Initializer| is present and _value_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) and IsIdentifierRef of |DestructuringAssignmentTarget| are both *true*, then // i. Let _v_ be ? NamedEvaluation of |Initializer| with argument _lref_.[[ReferencedName]]. // ... if isNamedEvaluationAnd(tx.EmitContext(), node, tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/) } if ast.IsAssignmentExpression(node, true /*excludeCompoundAssignment*/) { left := tx.visitDestructuringAssignmentTarget(node.AsBinaryExpression().Left) right := tx.Visitor().VisitNode(node.AsBinaryExpression().Right) return tx.Factory().UpdateBinaryExpression( node.AsBinaryExpression(), nil, left, nil, node.AsBinaryExpression().OperatorToken, right, ) } return tx.visitDestructuringAssignmentTarget(node) } func (tx *classFieldsTransformer) visitAssignmentRestElement(node *ast.Node) *ast.Node { spread := node.AsSpreadElement() if ast.IsLeftHandSideExpression(spread.Expression) { expr := tx.visitDestructuringAssignmentTarget(spread.Expression) return tx.Factory().UpdateSpreadElement(spread, expr) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitArrayAssignmentElement(node *ast.Node) *ast.Node { if ast.IsArrayBindingOrAssignmentElement(node) { if ast.IsSpreadElement(node) { return tx.visitAssignmentRestElement(node) } if node.Kind != ast.KindOmittedExpression { return tx.visitAssignmentElement(node) } } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitAssignmentProperty(node *ast.Node) *ast.Node { // AssignmentProperty : PropertyName `:` AssignmentElement // AssignmentElement : DestructuringAssignmentTarget Initializer? // 13.15.5.6 RS: KeyedDestructuringAssignmentEvaluation // AssignmentElement : DestructuringAssignmentTarget Initializer? // ... // 3. If |Initializer| is present and _v_ is *undefined*, then // a. If IsAnonymousfunctionDefinition(|Initializer|) and IsIdentifierRef of |DestructuringAssignmentTarget| are both *true*, then // i. Let _rhsValue_ be ? NamedEvaluation of |Initializer| with argument _lref_.[[ReferencedName]]. // ... prop := node.AsPropertyAssignment() name := tx.Visitor().VisitNode(prop.Name()) init := prop.Initializer if ast.IsAssignmentExpression(init, true /*excludeCompoundAssignment*/) { assignElem := tx.visitAssignmentElement(init) return tx.Factory().UpdatePropertyAssignment(prop, nil, name, nil, nil, assignElem) } if ast.IsLeftHandSideExpression(init) { target := tx.visitDestructuringAssignmentTarget(init) return tx.Factory().UpdatePropertyAssignment(prop, nil, name, nil, nil, target) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitShorthandAssignmentProperty(node *ast.Node) *ast.Node { // AssignmentProperty : IdentifierReference Initializer? // 13.15.5.3 RS: PropertyDestructuringAssignmentEvaluation // AssignmentProperty : IdentifierReference Initializer? // ... // 4. If |Initializer?| is present and _v_ is *undefined*, then // a. If IsAnonymousFunctionDefinition(|Initializer|) is *true*, then // i. Set _v_ to ? NamedEvaluation of |Initializer| with argument _P_. // ... if isNamedEvaluationAnd(tx.EmitContext(), node, tx.isAnonymousClassNeedingAssignedName) { node = transformNamedEvaluation(tx.EmitContext(), node, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitAssignmentRestProperty(node *ast.Node) *ast.Node { spread := node.AsSpreadAssignment() if ast.IsLeftHandSideExpression(spread.Expression) { expr := tx.visitDestructuringAssignmentTarget(spread.Expression) return tx.Factory().UpdateSpreadAssignment(spread, expr) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitObjectAssignmentElement(node *ast.Node) *ast.Node { debug.Assert(node != nil && ast.IsObjectBindingOrAssignmentElement(node)) if ast.IsSpreadAssignment(node) { return tx.visitAssignmentRestProperty(node) } if ast.IsShorthandPropertyAssignment(node) { return tx.visitShorthandAssignmentProperty(node) } if ast.IsPropertyAssignment(node) { return tx.visitAssignmentProperty(node) } return tx.Visitor().VisitEachChild(node) } func (tx *classFieldsTransformer) visitAssignmentPattern(node *ast.Node) *ast.Node { if ast.IsArrayLiteralExpression(node) { // Transforms private names in destructuring assignment array bindings. // Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers. // // Source: // ([ this.#myProp ] = [ "hello" ]); // // Transformation: // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; return tx.Factory().UpdateArrayLiteralExpression( node.AsArrayLiteralExpression(), tx.arrayAssignmentElementVisitor.VisitNodes(node.AsArrayLiteralExpression().Elements), node.AsArrayLiteralExpression().MultiLine, ) } // Transforms private names in destructuring assignment object bindings. // Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers. // // Source: // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); // // Transformation: // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; return tx.Factory().UpdateObjectLiteralExpression( node.AsObjectLiteralExpression(), tx.objectAssignmentElementVisitor.VisitNodes(node.AsObjectLiteralExpression().Properties), node.AsObjectLiteralExpression().MultiLine, ) } func createPrivateStaticFieldInitializer(factory *printer.NodeFactory, variableName *ast.IdentifierNode, initializer *ast.Expression) *ast.Expression { if initializer == nil { initializer = factory.NewVoidZeroExpression() } return factory.NewAssignmentExpression( variableName, factory.NewObjectLiteralExpression( factory.NewNodeList([]*ast.Node{ factory.NewPropertyAssignment(nil, factory.NewIdentifier("value"), nil, nil, initializer), }), false, ), ) } func createPrivateInstanceFieldInitializer(factory *printer.NodeFactory, receiver *ast.Expression, initializer *ast.Expression, weakMapName *ast.IdentifierNode) *ast.Expression { if initializer == nil { initializer = factory.NewVoidZeroExpression() } return factory.NewMethodCall(weakMapName, factory.NewIdentifier("set"), []*ast.Node{receiver, initializer}) } func createPrivateInstanceMethodInitializer(factory *printer.NodeFactory, receiver *ast.Expression, weakSetName *ast.IdentifierNode) *ast.Expression { return factory.NewMethodCall(weakSetName, factory.NewIdentifier("add"), []*ast.Node{receiver}) } func (tx *classFieldsTransformer) isReservedPrivateName(node *ast.Node) bool { return !(ast.IsPrivateIdentifier(node) && tx.EmitContext().HasAutoGenerateInfo(node)) && node.Text() == "#constructor" } func isStaticPropertyDeclarationOrClassStaticBlock(node *ast.Node) bool { return ast.IsClassStaticBlockDeclaration(node) || (ast.IsPropertyDeclaration(node) && ast.HasStaticModifier(node)) } func (tx *classFieldsTransformer) getProperties(node *ast.Node, requireInitializer bool, isStatic bool) []*ast.Node { var result []*ast.Node for _, member := range node.Members() { if ast.IsPropertyDeclaration(member) && (!requireInitializer || member.Initializer() != nil) && ast.HasStaticModifier(member) == isStatic { result = append(result, member) } } return result } func (tx *classFieldsTransformer) getStaticPropertiesAndClassStaticBlock(node *ast.Node) []*ast.Node { var result []*ast.Node for _, member := range node.Members() { if ast.IsClassStaticBlockDeclaration(member) || (ast.IsPropertyDeclaration(member) && ast.HasStaticModifier(member)) { result = append(result, member) } } return result } // classHasClassThisAssignment checks if a class has a static block that is a class-this assignment. func classHasClassThisAssignment(emitContext *printer.EmitContext, node *ast.Node) bool { for _, member := range node.Members() { if isClassThisAssignmentBlock(emitContext, member) { return true } } return false } func isNonStaticMethodOrAccessorWithPrivateName(member *ast.Node) bool { return !ast.IsStatic(member) && (ast.IsMethodOrAccessor(member) || ast.IsAutoAccessorPropertyDeclaration(member)) && ast.IsPrivateIdentifier(member.Name()) } func createMemberAccessForPropertyName(factory *printer.NodeFactory, emitContext *printer.EmitContext, receiver *ast.Expression, name *ast.PropertyName, location *ast.PropertyName) *ast.Expression { if ast.IsComputedPropertyName(name) { expression := factory.NewElementAccessExpression(receiver, nil, name.Expression(), ast.NodeFlagsNone) expression.Loc = location.Loc return expression } var expression *ast.Expression if ast.IsIdentifier(name) || ast.IsPrivateIdentifier(name) { expression = factory.NewPropertyAccessExpression(receiver, nil, name, ast.NodeFlagsNone) } else { // string or numeric literal expression = factory.NewElementAccessExpression(receiver, nil, name, ast.NodeFlagsNone) } emitContext.SetCommentRange(expression, name.Loc) emitContext.SetSourceMapRange(expression, name.Loc) emitContext.AddEmitFlags(expression, printer.EFNoNestedSourceMaps) return expression } func (tx *classFieldsTransformer) createCallBinding(node *ast.Node) (thisArg *ast.Expression, target *ast.Expression) { if ast.IsSuperProperty(node) { return tx.Factory().NewThisExpression(), node } if ast.IsPropertyAccessExpression(node) { expr := node.AsPropertyAccessExpression() if shouldBeCapturedInTempVariable(expr.Expression) { thisArg = tx.Factory().NewTempVariable() tx.EmitContext().AddVariableDeclaration(thisArg) target = tx.Factory().NewPropertyAccessExpression( tx.Factory().NewParenthesizedExpression( // TODO: do we even need these? tx.Factory().NewAssignmentExpression(thisArg, expr.Expression), ), nil, expr.Name(), ast.NodeFlagsNone, ) return thisArg, target } return expr.Expression, node } thisArg = tx.Factory().NewVoidZeroExpression() target = node return thisArg, target } func shouldBeCapturedInTempVariable(node *ast.Node) bool { target := ast.SkipParentheses(node) switch target.Kind { case ast.KindIdentifier, ast.KindThisKeyword, ast.KindNumericLiteral, ast.KindBigIntLiteral, ast.KindStringLiteral: return false default: return true } } func (tx *classFieldsTransformer) createAccessorPropertyGetRedirector(node *ast.PropertyDeclaration, modifiers *ast.ModifierList, name *ast.PropertyName, receiver *ast.Expression) *ast.Node { backingFieldName := tx.Factory().NewGeneratedPrivateNameForNodeEx(node.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"}) returnExpr := tx.Factory().NewPropertyAccessExpression( receiver, nil, backingFieldName, ast.NodeFlagsNone, ) returnStmt := tx.Factory().NewReturnStatement(returnExpr) body := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{returnStmt}), false) return tx.Factory().NewGetAccessorDeclaration( modifiers, name, nil, /*typeParameters*/ tx.Factory().NewNodeList([]*ast.Node{}), nil, /*returnType*/ nil, /*fullSignature*/ body, ) } func (tx *classFieldsTransformer) createAccessorPropertySetRedirector(node *ast.PropertyDeclaration, modifiers *ast.ModifierList, name *ast.PropertyName, receiver *ast.Expression) *ast.Node { backingFieldName := tx.Factory().NewGeneratedPrivateNameForNodeEx(node.Name(), printer.AutoGenerateOptions{Suffix: "_accessor_storage"}) valueParam := tx.Factory().NewParameterDeclaration( nil, /*modifiers*/ nil, /*dotDotDotToken*/ tx.Factory().NewIdentifier("value"), nil, /*questionToken*/ nil, /*typeNode*/ nil, /*initializer*/ ) assignExpr := tx.Factory().NewAssignmentExpression( tx.Factory().NewPropertyAccessExpression( receiver, nil, backingFieldName, ast.NodeFlagsNone, ), tx.Factory().NewIdentifier("value"), ) exprStmt := tx.Factory().NewExpressionStatement(assignExpr) body := tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Node{exprStmt}), false) return tx.Factory().NewSetAccessorDeclaration( modifiers, name, nil, /*typeParameters*/ tx.Factory().NewNodeList([]*ast.Node{valueParam}), nil, /*returnType*/ nil, /*fullSignature*/ body, ) } // flattenCommaList decomposes a comma expression tree into a sequence of expressions. func flattenCommaList(node *ast.Expression) iter.Seq[*ast.Expression] { return func(yield func(*ast.Expression) bool) { flattenCommaListWorker(node, yield) } } func flattenCommaListWorker(node *ast.Expression, yield func(*ast.Expression) bool) bool { if ast.IsParenthesizedExpression(node) && ast.NodeIsSynthesized(node) { return flattenCommaListWorker(node.Expression(), yield) } else if ast.IsCommaExpression(node.AsNode()) { return flattenCommaListWorker(node.AsBinaryExpression().Left, yield) && flattenCommaListWorker(node.AsBinaryExpression().Right, yield) } else { return yield(node) } } func findComputedPropertyNameCacheAssignment(emitContext *printer.EmitContext, name *ast.Node) *ast.BinaryExpression { node := name.Expression() for { node = ast.SkipOuterExpressions(node, 0) if ast.IsBinaryExpression(node) && node.AsBinaryExpression().OperatorToken.Kind == ast.KindCommaToken { node = node.AsBinaryExpression().Right continue } if ast.IsAssignmentExpression(node, true /*excludeCompoundAssignment*/) && ast.IsIdentifier(node.AsBinaryExpression().Left) { return node.AsBinaryExpression() } break } return nil } func expandPreOrPostfixIncrementOrDecrementExpression(factory *printer.NodeFactory, emitContext *printer.EmitContext, node *ast.Node, expression *ast.Expression, resultVariable *ast.IdentifierNode) *ast.Expression { var operator ast.Kind var operand *ast.Node if ast.IsPrefixUnaryExpression(node) { operator = node.AsPrefixUnaryExpression().Operator operand = node.AsPrefixUnaryExpression().Operand } else { operator = node.AsPostfixUnaryExpression().Operator operand = node.AsPostfixUnaryExpression().Operand } temp := factory.NewTempVariable() emitContext.AddVariableDeclaration(temp) expression = factory.NewAssignmentExpression(temp, expression) expression.Loc = operand.Loc var operation *ast.Expression if ast.IsPrefixUnaryExpression(node) { operation = factory.NewPrefixUnaryExpression(operator, temp) } else { operation = factory.NewPostfixUnaryExpression(temp, operator) } operation.Loc = node.Loc if resultVariable != nil { operation = factory.NewAssignmentExpression(resultVariable, operation) operation.Loc = node.Loc } expression = factory.NewCommaExpression(expression, operation) expression.Loc = node.Loc if ast.IsPostfixUnaryExpression(node) { expression = factory.NewCommaExpression(expression, temp) expression.Loc = node.Loc } return expression }