Files
2026-07-09 16:50:43 -04:00

3619 lines
151 KiB
Go

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
}