package estransforms import ( "slices" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/transformers" ) type asyncContextFlags int const ( asyncContextNonTopLevel asyncContextFlags = 1 << iota asyncContextHasLexicalThis ) type lexicalArgumentsInfo struct { binding *ast.IdentifierNode used bool } type asyncTransformer struct { transformers.Transformer superAccessState contextFlags asyncContextFlags enclosingFunctionParameterNames *collections.Set[string] lexicalArguments lexicalArgumentsInfo asyncBodyVisitor *ast.NodeVisitor fallbackNodeVisitor *ast.NodeVisitor } func newAsyncTransformer(opts *transformers.TransformOptions) *transformers.Transformer { tx := &asyncTransformer{} result := tx.NewTransformer(tx.visit, opts.Context) tx.initSuperAccessVisitor(tx.EmitContext(), tx.Factory()) tx.asyncBodyVisitor = tx.EmitContext().NewNodeVisitor(tx.visitAsyncBodyNode) tx.fallbackNodeVisitor = tx.EmitContext().NewNodeVisitor(tx.visitFallback) return result } func (tx *asyncTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node { if node.IsDeclarationFile { return node.AsNode() } tx.setContextFlag(asyncContextNonTopLevel, false) tx.setContextFlag(asyncContextHasLexicalThis, false) visited := tx.Visitor().VisitEachChild(node.AsNode()) tx.EmitContext().AddEmitHelper(visited, tx.EmitContext().ReadEmitHelpers()...) return visited } func (tx *asyncTransformer) setContextFlag(flag asyncContextFlags, val bool) { if val { tx.contextFlags |= flag } else { tx.contextFlags &^= flag } } func (tx *asyncTransformer) inContext(flags asyncContextFlags) bool { return tx.contextFlags&flags != 0 } func (tx *asyncTransformer) inTopLevelContext() bool { return !tx.inContext(asyncContextNonTopLevel) } func (tx *asyncTransformer) inHasLexicalThisContext() bool { return tx.inContext(asyncContextHasLexicalThis) } func (tx *asyncTransformer) doWithContext(flags asyncContextFlags, cb func(*asyncTransformer, *ast.Node) *ast.Node, node *ast.Node) *ast.Node { flagsToSet := flags & ^tx.contextFlags if flagsToSet != 0 { tx.setContextFlag(flagsToSet, true) result := cb(tx, node) tx.setContextFlag(flagsToSet, false) return result } return cb(tx, node) } func (tx *asyncTransformer) visitDefault(node *ast.Node) *ast.Node { return tx.Visitor().VisitEachChild(node) } func (tx *asyncTransformer) fallbackVisitor(node *ast.Node) *ast.Node { if tx.capturedSuperProperties == nil && tx.lexicalArguments.binding == nil { return node } tx.trackSuperAccess(node) switch node.Kind { case ast.KindFunctionExpression, ast.KindFunctionDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindConstructor: return node case ast.KindParameter, ast.KindBindingElement, ast.KindVariableDeclaration: // fall through to visitEachChild case ast.KindIdentifier: if tx.lexicalArguments.binding != nil && node.Text() == "arguments" && !ast.IsIdentifierName(node) && !ast.IsLabelName(node) { tx.lexicalArguments.used = true return tx.lexicalArguments.binding } } return tx.fallbackNodeVisitor.VisitEachChild(node) } func (tx *asyncTransformer) visitFallback(node *ast.Node) *ast.Node { return tx.fallbackVisitor(node) } func (tx *asyncTransformer) visit(node *ast.Node) *ast.Node { if tx.EmitContext().EmitFlags(node)&printer.EFNoLexicalThis != 0 && tx.inHasLexicalThisContext() { tx.setContextFlag(asyncContextHasLexicalThis, false) defer tx.setContextFlag(asyncContextHasLexicalThis, true) } if node.SubtreeFacts()&(ast.SubtreeContainsAnyAwait|ast.SubtreeContainsAwait) == 0 { return tx.fallbackVisitor(node) } tx.trackSuperAccess(node) switch node.Kind { case ast.KindAsyncKeyword: // ES2017 async modifier should be elided for targets < ES2017 return nil case ast.KindSourceFile: return tx.visitSourceFile(node.AsSourceFile()) case ast.KindAwaitExpression: return tx.visitAwaitExpression(node.AsAwaitExpression()) case ast.KindMethodDeclaration: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitMethodDeclaration, node) case ast.KindFunctionDeclaration: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitFunctionDeclaration, node) case ast.KindFunctionExpression: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitFunctionExpression, node) case ast.KindArrowFunction: return tx.doWithContext(asyncContextNonTopLevel, (*asyncTransformer).visitArrowFunction, node) case ast.KindGetAccessor: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitGetAccessorDeclaration, node) case ast.KindSetAccessor: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitSetAccessorDeclaration, node) case ast.KindConstructor: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitConstructorDeclaration, node) case ast.KindClassDeclaration, ast.KindClassExpression: return tx.doWithContext(asyncContextNonTopLevel|asyncContextHasLexicalThis, (*asyncTransformer).visitDefault, node) default: return tx.Visitor().VisitEachChild(node) } } func (tx *asyncTransformer) visitAsyncBodyNode(node *ast.Node) *ast.Node { if isNodeWithPossibleHoistedDeclaration(node) { switch node.Kind { case ast.KindVariableStatement: return tx.visitVariableStatementInAsyncBody(node) case ast.KindForStatement: return tx.visitForStatementInAsyncBody(node.AsForStatement()) case ast.KindForInStatement: return tx.visitForInStatementInAsyncBody(node.AsForInOrOfStatement()) case ast.KindForOfStatement: return tx.visitForOfStatementInAsyncBody(node.AsForInOrOfStatement()) case ast.KindCatchClause: return tx.visitCatchClauseInAsyncBody(node.AsCatchClause()) case ast.KindBlock, ast.KindSwitchStatement, ast.KindCaseBlock, ast.KindCaseClause, ast.KindDefaultClause, ast.KindTryStatement, ast.KindDoStatement, ast.KindWhileStatement, ast.KindIfStatement, ast.KindWithStatement, ast.KindLabeledStatement: return tx.asyncBodyVisitor.VisitEachChild(node) } } return tx.visit(node) } func (tx *asyncTransformer) visitCatchClauseInAsyncBody(node *ast.CatchClause) *ast.Node { catchClauseNames := &collections.Set[string]{} if node.VariableDeclaration != nil { tx.recordDeclarationName(node.VariableDeclaration, catchClauseNames) } // names declared in a catch variable are block scoped var catchClauseUnshadowedNames *collections.Set[string] for escapedName := range catchClauseNames.Keys() { if tx.enclosingFunctionParameterNames != nil && tx.enclosingFunctionParameterNames.Has(escapedName) { if catchClauseUnshadowedNames == nil { catchClauseUnshadowedNames = tx.enclosingFunctionParameterNames.Clone() } catchClauseUnshadowedNames.Delete(escapedName) } } if catchClauseUnshadowedNames != nil { savedEnclosingFunctionParameterNames := tx.enclosingFunctionParameterNames tx.enclosingFunctionParameterNames = catchClauseUnshadowedNames result := tx.asyncBodyVisitor.VisitEachChild(node.AsNode()) tx.enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames return result } return tx.asyncBodyVisitor.VisitEachChild(node.AsNode()) } func (tx *asyncTransformer) visitVariableStatementInAsyncBody(node *ast.Node) *ast.Node { declList := node.AsVariableStatement().DeclarationList if tx.isVariableDeclarationListWithCollidingName(declList) { expression := tx.visitVariableDeclarationListWithCollidingNames(declList.AsVariableDeclarationList(), false) if expression != nil { return tx.Factory().NewExpressionStatement(expression) } return nil } return tx.Visitor().VisitEachChild(node) } func (tx *asyncTransformer) visitForInStatementInAsyncBody(node *ast.ForInOrOfStatement) *ast.Node { var visitedInitializer *ast.Node if tx.isVariableDeclarationListWithCollidingName(node.Initializer) { visitedInitializer = tx.visitVariableDeclarationListWithCollidingNames(node.Initializer.AsVariableDeclarationList(), true) } else { visitedInitializer = tx.Visitor().VisitNode(node.Initializer) } return tx.Factory().UpdateForInOrOfStatement( node, nil, /*awaitModifier*/ visitedInitializer, tx.Visitor().VisitNode(node.Expression), tx.asyncBodyVisitor.VisitEmbeddedStatement(node.Statement), ) } func (tx *asyncTransformer) visitForOfStatementInAsyncBody(node *ast.ForInOrOfStatement) *ast.Node { var visitedInitializer *ast.Node if tx.isVariableDeclarationListWithCollidingName(node.Initializer) { visitedInitializer = tx.visitVariableDeclarationListWithCollidingNames(node.Initializer.AsVariableDeclarationList(), true) } else { visitedInitializer = tx.Visitor().VisitNode(node.Initializer) } return tx.Factory().UpdateForInOrOfStatement( node, tx.Visitor().VisitNode(node.AwaitModifier), visitedInitializer, tx.Visitor().VisitNode(node.Expression), tx.asyncBodyVisitor.VisitEmbeddedStatement(node.Statement), ) } func (tx *asyncTransformer) visitForStatementInAsyncBody(node *ast.ForStatement) *ast.Node { initializer := node.Initializer var visitedInitializer *ast.Node if initializer != nil && tx.isVariableDeclarationListWithCollidingName(initializer) { visitedInitializer = tx.visitVariableDeclarationListWithCollidingNames(initializer.AsVariableDeclarationList(), false) } else { visitedInitializer = tx.Visitor().VisitNode(node.Initializer) } return tx.Factory().UpdateForStatement( node, visitedInitializer, tx.Visitor().VisitNode(node.Condition), tx.Visitor().VisitNode(node.Incrementor), tx.asyncBodyVisitor.VisitEmbeddedStatement(node.Statement), ) } // visitAwaitExpression visits an AwaitExpression node. // // This function will be called any time a ES2017 await expression is encountered. func (tx *asyncTransformer) visitAwaitExpression(node *ast.AwaitExpression) *ast.Node { // do not downlevel a top-level await as it is module syntax... if tx.inTopLevelContext() { return tx.Visitor().VisitEachChild(node.AsNode()) } yieldExpr := tx.Factory().NewYieldExpression( nil, /*asteriskToken*/ tx.Visitor().VisitNode(node.Expression), ) yieldExpr.Loc = node.Loc tx.EmitContext().SetOriginal(yieldExpr, node.AsNode()) return yieldExpr } func (tx *asyncTransformer) visitConstructorDeclaration(node *ast.Node) *ast.Node { decl := node.AsConstructorDeclaration() savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} updated := tx.Factory().UpdateConstructorDeclaration( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), nil, /*typeParameters*/ tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()), nil, /*returnType*/ nil, /*fullSignature*/ tx.transformMethodBody(node), ) tx.lexicalArguments = savedLexicalArguments return updated } // visitMethodDeclaration visits a MethodDeclaration node. // // This function will be called when one of the following conditions are met: // - The node is marked as async func (tx *asyncTransformer) visitMethodDeclaration(node *ast.Node) *ast.Node { decl := node.AsMethodDeclaration() functionFlags := ast.GetFunctionFlags(node) savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} var parameters *ast.NodeList var body *ast.Node if functionFlags&ast.FunctionFlagsAsync != 0 { parameters = tx.transformAsyncFunctionParameterList(node) body = tx.transformAsyncFunctionBody(node, parameters) } else { parameters = tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()) body = tx.transformMethodBody(node) } updated := tx.Factory().UpdateMethodDeclaration( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), decl.AsteriskToken, decl.Name(), nil, /*postfixToken*/ nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ body, ) tx.lexicalArguments = savedLexicalArguments return updated } func (tx *asyncTransformer) visitGetAccessorDeclaration(node *ast.Node) *ast.Node { decl := node.AsGetAccessorDeclaration() savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} updated := tx.Factory().UpdateGetAccessorDeclaration( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), decl.Name(), nil, /*typeParameters*/ tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()), nil, /*returnType*/ nil, /*fullSignature*/ tx.transformMethodBody(node), ) tx.lexicalArguments = savedLexicalArguments return updated } func (tx *asyncTransformer) visitSetAccessorDeclaration(node *ast.Node) *ast.Node { decl := node.AsSetAccessorDeclaration() savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} updated := tx.Factory().UpdateSetAccessorDeclaration( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), decl.Name(), nil, /*typeParameters*/ tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()), nil, /*returnType*/ nil, /*fullSignature*/ tx.transformMethodBody(node), ) tx.lexicalArguments = savedLexicalArguments return updated } // visitFunctionDeclaration visits a FunctionDeclaration node. // // This function will be called when one of the following conditions are met: // - The node is marked async func (tx *asyncTransformer) visitFunctionDeclaration(node *ast.Node) *ast.Node { decl := node.AsFunctionDeclaration() functionFlags := ast.GetFunctionFlags(node) savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} var parameters *ast.NodeList var body *ast.Node if functionFlags&ast.FunctionFlagsAsync != 0 { parameters = tx.transformAsyncFunctionParameterList(node) body = tx.transformAsyncFunctionBody(node, parameters) } else { parameters = tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()) body = tx.EmitContext().VisitFunctionBody(decl.Body, tx.Visitor()) } updated := tx.Factory().UpdateFunctionDeclaration( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), decl.AsteriskToken, tx.Visitor().VisitNode(decl.Name()), nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ body, ) tx.lexicalArguments = savedLexicalArguments return updated } // visitFunctionExpression visits a FunctionExpression node. // // This function will be called when one of the following conditions are met: // - The node is marked async func (tx *asyncTransformer) visitFunctionExpression(node *ast.Node) *ast.Node { decl := node.AsFunctionExpression() functionFlags := ast.GetFunctionFlags(node) savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} var parameters *ast.NodeList var body *ast.Node if functionFlags&ast.FunctionFlagsAsync != 0 { parameters = tx.transformAsyncFunctionParameterList(node) body = tx.transformAsyncFunctionBody(node, parameters) } else { parameters = tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()) body = tx.EmitContext().VisitFunctionBody(decl.Body, tx.Visitor()) } updated := tx.Factory().UpdateFunctionExpression( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), decl.AsteriskToken, tx.Visitor().VisitNode(decl.Name()), nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ body, ) tx.lexicalArguments = savedLexicalArguments return updated } // visitArrowFunction visits an ArrowFunction. // // This function will be called when one of the following conditions are met: // - The node is marked async func (tx *asyncTransformer) visitArrowFunction(node *ast.Node) *ast.Node { // `arguments` in class static blocks is always an error, but we preserve Strada's emit // behavior for baseline compatibility. In Strada, checker-based `isArgumentsLocalBinding` // returns false for `arguments` in static blocks (since the binding doesn't exist due to // the error), so the async transform leaves them untouched. if tx.EmitContext().EmitFlags(node)&printer.EFNoLexicalArguments != 0 { savedLexicalArguments := tx.lexicalArguments tx.lexicalArguments = lexicalArgumentsInfo{} defer func() { tx.lexicalArguments = savedLexicalArguments }() } decl := node.AsArrowFunction() functionFlags := ast.GetFunctionFlags(node) var parameters *ast.NodeList var body *ast.Node if functionFlags&ast.FunctionFlagsAsync != 0 { parameters = tx.transformAsyncFunctionParameterList(node) body = tx.transformAsyncFunctionBody(node, parameters) } else { parameters = tx.EmitContext().VisitParameters(decl.Parameters, tx.Visitor()) body = tx.EmitContext().VisitFunctionBody(decl.Body, tx.Visitor()) } return tx.Factory().UpdateArrowFunction( decl, tx.Visitor().VisitModifiers(decl.Modifiers()), nil, /*typeParameters*/ parameters, nil, /*returnType*/ nil, /*fullSignature*/ decl.EqualsGreaterThanToken, body, ) } func (tx *asyncTransformer) recordDeclarationName(node *ast.Node, names *collections.Set[string]) { name := node.Name() if name == nil { return } if ast.IsIdentifier(name) { names.Add(name.Text()) } else if ast.IsBindingPattern(name) { for _, element := range name.AsBindingPattern().Elements.Nodes { if !ast.IsOmittedExpression(element) { tx.recordDeclarationName(element, names) } } } } func (tx *asyncTransformer) isVariableDeclarationListWithCollidingName(node *ast.Node) bool { return node != nil && ast.IsVariableDeclarationList(node) && node.Flags&ast.NodeFlagsBlockScoped == 0 && slices.ContainsFunc(node.AsVariableDeclarationList().Declarations.Nodes, tx.collidesWithParameterName) } func (tx *asyncTransformer) visitVariableDeclarationListWithCollidingNames(node *ast.VariableDeclarationList, hasReceiver bool) *ast.Node { tx.hoistVariableDeclarationList(node) var variables []*ast.Node for _, decl := range node.Declarations.Nodes { if decl.AsVariableDeclaration().Initializer != nil { variables = append(variables, decl) } } if len(variables) == 0 { if hasReceiver { name := node.Declarations.Nodes[0].Name() var target *ast.Node if ast.IsBindingPattern(name) { target = transformers.ConvertBindingPatternToAssignmentPattern(tx.EmitContext(), name.AsBindingPattern()) } else { target = name } return tx.Visitor().VisitNode(target) } return nil } var expressions []*ast.Node for _, variable := range variables { expressions = append(expressions, tx.transformInitializedVariable(variable.AsVariableDeclaration())) } return tx.Factory().InlineExpressions(expressions) } func (tx *asyncTransformer) hoistVariableDeclarationList(node *ast.VariableDeclarationList) { for _, decl := range node.Declarations.Nodes { tx.hoistVariable(decl) } } func (tx *asyncTransformer) hoistVariable(node *ast.Node) { name := node.Name() if name == nil { return } if ast.IsIdentifier(name) { tx.EmitContext().AddVariableDeclaration(name) } else if ast.IsBindingPattern(name) { for _, element := range name.AsBindingPattern().Elements.Nodes { if !ast.IsOmittedExpression(element) { tx.hoistVariable(element) } } } } func (tx *asyncTransformer) transformInitializedVariable(node *ast.VariableDeclaration) *ast.Node { var target *ast.Node if ast.IsBindingPattern(node.Name()) { target = transformers.ConvertBindingPatternToAssignmentPattern(tx.EmitContext(), node.Name().AsBindingPattern()) } else { target = node.Name() } converted := tx.Factory().NewAssignmentExpression(target, node.Initializer) tx.EmitContext().SetSourceMapRange(converted, node.Loc) return tx.Visitor().VisitNode(converted) } func (tx *asyncTransformer) collidesWithParameterName(node *ast.Node) bool { name := node.Name() if name == nil { return false } if ast.IsIdentifier(name) { return tx.enclosingFunctionParameterNames != nil && tx.enclosingFunctionParameterNames.Has(name.Text()) } if ast.IsBindingPattern(name) { for _, element := range name.AsBindingPattern().Elements.Nodes { if !ast.IsOmittedExpression(element) && tx.collidesWithParameterName(element) { return true } } } return false } func (tx *asyncTransformer) transformMethodBody(node *ast.Node) *ast.Node { savedCapturedSuperProperties := tx.capturedSuperProperties savedHasSuperElementAccess := tx.hasSuperElementAccess savedHasSuperPropertyAssignment := tx.hasSuperPropertyAssignment savedSuperBinding := tx.superBinding savedSuperIndexBinding := tx.superIndexBinding tx.capturedSuperProperties = &collections.OrderedSet[string]{} tx.hasSuperElementAccess = false tx.hasSuperPropertyAssignment = false tx.superBinding = tx.Factory().NewUniqueNameEx("_super", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) tx.superIndexBinding = tx.Factory().NewUniqueNameEx("_superIndex", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) tx.EmitContext().StartVariableEnvironment() updated := tx.EmitContext().VisitFunctionBody(node.Body(), tx.Visitor()) // Minor optimization, emit `_super` helper to capture `super` access in an arrow. emitSuperHelpers := (tx.capturedSuperProperties.Size() > 0 || tx.hasSuperElementAccess) && (ast.GetFunctionFlags(tx.getOriginalIfFunctionLike(node))&ast.FunctionFlagsAsyncGenerator) != ast.FunctionFlagsAsyncGenerator if emitSuperHelpers { if tx.capturedSuperProperties.Size() > 0 { tx.EmitContext().AddInitializationStatement(tx.createSuperAccessVariableStatement()) } } mergedStatements := tx.EmitContext().EndAndMergeVariableEnvironmentList(updated.StatementList()) if emitSuperHelpers && tx.hasSuperElementAccess && !updated.AsBlock().MultiLine { newBlock := tx.Factory().NewBlock(mergedStatements, true) newBlock.Loc = updated.Loc updated = newBlock } else { updated = tx.Factory().UpdateBlock(updated.AsBlock(), mergedStatements, updated.AsBlock().MultiLine) } if emitSuperHelpers && tx.hasSuperElementAccess { if tx.hasSuperPropertyAssignment { tx.EmitContext().AddEmitHelper(updated, printer.AdvancedAsyncSuperHelper) } else { tx.EmitContext().AddEmitHelper(updated, printer.AsyncSuperHelper) } } tx.capturedSuperProperties = savedCapturedSuperProperties tx.hasSuperElementAccess = savedHasSuperElementAccess tx.hasSuperPropertyAssignment = savedHasSuperPropertyAssignment tx.superBinding = savedSuperBinding tx.superIndexBinding = savedSuperIndexBinding return updated } func (tx *asyncTransformer) createCaptureArgumentsStatement() *ast.Node { variable := tx.Factory().NewVariableDeclaration( tx.lexicalArguments.binding, nil, nil, tx.Factory().NewIdentifier("arguments"), ) declList := tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{variable}), ast.NodeFlagsNone) statement := tx.Factory().NewVariableStatement(nil, declList) tx.EmitContext().AddEmitFlags(statement, printer.EFStartOnNewLine|printer.EFCustomPrologue) return statement } func (tx *asyncTransformer) transformAsyncFunctionParameterList(node *ast.Node) *ast.NodeList { if isSimpleParameterList(node.Parameters()) { return tx.EmitContext().VisitParameters(node.ParameterList(), tx.Visitor()) } var newParameters []*ast.Node for _, parameter := range node.Parameters() { param := parameter.AsParameterDeclaration() if param.Initializer != nil || param.DotDotDotToken != nil { // for an arrow function, capture the remaining arguments in a rest parameter. // for any other function/method this isn't necessary as we can just use `arguments`. if node.Kind == ast.KindArrowFunction { restParameter := tx.Factory().NewParameterDeclaration( nil, tx.Factory().NewToken(ast.KindDotDotDotToken), tx.Factory().NewUniqueNameEx("args", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes}), nil, nil, nil, ) newParameters = append(newParameters, restParameter) } break } // for arrow functions we capture fixed parameters to forward to `__awaiter`. For all other functions // we add fixed parameters to preserve the function's `length` property. newParameter := tx.Factory().NewParameterDeclaration( nil, nil, tx.Factory().NewGeneratedNameForNodeEx(param.Name(), printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes}), nil, nil, nil, ) newParameters = append(newParameters, newParameter) } newParametersArray := tx.Factory().NewNodeList(newParameters) newParametersArray.Loc = node.ParameterList().Loc return newParametersArray } func (tx *asyncTransformer) transformAsyncFunctionBody(node *ast.Node, outerParameters *ast.NodeList) *ast.Node { isArrow := node.Kind == ast.KindArrowFunction savedCapturedSuperProperties := tx.capturedSuperProperties savedHasSuperElementAccess := tx.hasSuperElementAccess savedHasSuperPropertyAssignment := tx.hasSuperPropertyAssignment savedSuperBinding := tx.superBinding savedSuperIndexBinding := tx.superIndexBinding if !isArrow { tx.capturedSuperProperties = &collections.OrderedSet[string]{} tx.hasSuperElementAccess = false tx.hasSuperPropertyAssignment = false tx.superBinding = tx.Factory().NewUniqueNameEx("_super", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) tx.superIndexBinding = tx.Factory().NewUniqueNameEx("_superIndex", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) } innerParameters := (*ast.NodeList)(nil) if !isSimpleParameterList(node.Parameters()) { innerParameters = tx.EmitContext().VisitParameters(node.ParameterList(), tx.Visitor()) } savedLexicalArguments := tx.lexicalArguments captureLexicalArguments := tx.lexicalArguments.binding == nil if captureLexicalArguments { tx.lexicalArguments = lexicalArgumentsInfo{ binding: tx.Factory().NewUniqueName("arguments"), } } var argumentsExpression *ast.Expression if innerParameters != nil { if isArrow { // `node` does not have a simple parameter list, so `outerParameters` refers to placeholders that are // forwarded to `innerParameters`, matching how they are introduced in `transformAsyncFunctionParameterList`. var parameterBindings []*ast.Node outerLen := len(outerParameters.Nodes) for i, param := range node.Parameters() { if i >= outerLen { break } originalParameter := param.AsParameterDeclaration() outerParameter := outerParameters.Nodes[i].AsParameterDeclaration() if originalParameter.Initializer != nil || originalParameter.DotDotDotToken != nil { parameterBindings = append(parameterBindings, tx.Factory().NewSpreadElement(outerParameter.Name())) break } parameterBindings = append(parameterBindings, outerParameter.Name()) } argumentsExpression = tx.Factory().NewArrayLiteralExpression(tx.Factory().NewNodeList(parameterBindings), false) } else { argumentsExpression = tx.Factory().NewIdentifier("arguments") } } // An async function is emit as an outer function that calls an inner // generator function. To preserve lexical bindings, we pass the current // `this` and `arguments` objects to `__awaiter`. The generator function // passed to `__awaiter` is executed inside of the callback to the // promise constructor. savedEnclosingFunctionParameterNames := tx.enclosingFunctionParameterNames tx.enclosingFunctionParameterNames = &collections.Set[string]{} for _, parameter := range node.Parameters() { tx.recordDeclarationName(parameter, tx.enclosingFunctionParameterNames) } hasLexicalThis := tx.inHasLexicalThisContext() asyncBody := tx.transformAsyncFunctionBodyWorker(node.Body()) asyncBody = tx.Factory().UpdateBlock( asyncBody.AsBlock(), tx.EmitContext().EndAndMergeVariableEnvironmentList(asyncBody.StatementList()), asyncBody.AsBlock().MultiLine, ) // Substitute super property accesses with _super/_superIndex helpers emitSuperHelpers := tx.capturedSuperProperties != nil && (tx.capturedSuperProperties.Size() > 0 || tx.hasSuperElementAccess) if emitSuperHelpers { innerParameters = tx.superAccessVisitor.VisitNodes(innerParameters) asyncBody = tx.substituteSuperAccessesInBody(asyncBody) } var result *ast.Node if !isArrow { tx.EmitContext().StartVariableEnvironment() // Minor optimization, emit `_super` helper to capture `super` access in an arrow. if emitSuperHelpers { if tx.capturedSuperProperties.Size() > 0 { tx.EmitContext().AddInitializationStatement(tx.createSuperAccessVariableStatement()) } } if captureLexicalArguments && tx.lexicalArguments.used { tx.EmitContext().AddInitializationStatement(tx.createCaptureArgumentsStatement()) } statements := []*ast.Node{ tx.Factory().NewReturnStatement( tx.Factory().NewAwaiterHelper( hasLexicalThis, argumentsExpression, innerParameters, asyncBody, ), ), } block := tx.Factory().NewBlock( tx.EmitContext().EndAndMergeVariableEnvironmentList(tx.Factory().NewNodeList(statements)), true, ) block.Loc = node.Body().Loc if emitSuperHelpers && tx.hasSuperElementAccess { if tx.hasSuperPropertyAssignment { tx.EmitContext().AddEmitHelper(block, printer.AdvancedAsyncSuperHelper) } else { tx.EmitContext().AddEmitHelper(block, printer.AsyncSuperHelper) } } result = block } else { result = tx.Factory().NewAwaiterHelper( hasLexicalThis, argumentsExpression, innerParameters, asyncBody, ) if captureLexicalArguments && tx.lexicalArguments.used { block := tx.convertToFunctionBlock(result) result = tx.Factory().UpdateBlock( block.AsBlock(), tx.EmitContext().MergeEnvironmentList(block.StatementList(), []*ast.Node{tx.createCaptureArgumentsStatement()}), block.AsBlock().MultiLine, ) } } tx.enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames if !isArrow { tx.capturedSuperProperties = savedCapturedSuperProperties tx.hasSuperElementAccess = savedHasSuperElementAccess tx.hasSuperPropertyAssignment = savedHasSuperPropertyAssignment tx.superBinding = savedSuperBinding tx.superIndexBinding = savedSuperIndexBinding tx.lexicalArguments = savedLexicalArguments } else if captureLexicalArguments && !tx.lexicalArguments.used { // If we created a new binding but it wasn't used, restore the previous state. // If it was used, keep the binding alive so sibling arrows can reuse it // (the `var` declaration hoists to the enclosing function scope). tx.lexicalArguments = savedLexicalArguments } else if captureLexicalArguments { // Keep the binding but clear the used flag so siblings don't re-emit the capture statement. tx.lexicalArguments.used = false } return result } func (tx *asyncTransformer) transformAsyncFunctionBodyWorker(body *ast.Node) *ast.Node { if ast.IsBlock(body) { return tx.Factory().UpdateBlock( body.AsBlock(), tx.asyncBodyVisitor.VisitNodes(body.StatementList()), body.AsBlock().MultiLine, ) } // Convert expression body to block body with return statement visited := tx.asyncBodyVisitor.VisitNode(body) ret := tx.Factory().NewReturnStatement(visited) ret.Loc = body.Loc list := tx.Factory().NewNodeList([]*ast.Node{ret}) list.Loc = body.Loc block := tx.Factory().NewBlock(list, false /*multiLine*/) block.Loc = body.Loc return block } func (tx *asyncTransformer) convertToFunctionBlock(node *ast.Node) *ast.Node { if ast.IsBlock(node) { return node } ret := tx.Factory().NewReturnStatement(node) ret.Loc = node.Loc tx.EmitContext().SetOriginal(ret, node) list := tx.Factory().NewNodeList([]*ast.Node{ret}) list.Loc = node.Loc block := tx.Factory().NewBlock(list, true) block.Loc = node.Loc return block } // assignmentTargetContainsSuperProperty checks top-down whether an assignment target // expression contains a super property or element access (super.x or super[x]). // This avoids relying on parent pointers (IsAssignmentTarget) which may not be set // on synthesized AST nodes from prior transforms. func assignmentTargetContainsSuperProperty(node *ast.Node) bool { switch node.Kind { case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression: return node.Expression().Kind == ast.KindSuperKeyword case ast.KindParenthesizedExpression: return assignmentTargetContainsSuperProperty(node.AsParenthesizedExpression().Expression) case ast.KindArrayLiteralExpression: return slices.ContainsFunc(node.AsArrayLiteralExpression().Elements.Nodes, assignmentTargetContainsSuperProperty) case ast.KindObjectLiteralExpression: for _, prop := range node.AsObjectLiteralExpression().Properties.Nodes { switch prop.Kind { case ast.KindPropertyAssignment: if assignmentTargetContainsSuperProperty(prop.AsPropertyAssignment().Initializer) { return true } case ast.KindShorthandPropertyAssignment: if assignmentTargetContainsSuperProperty(prop.AsShorthandPropertyAssignment().Name()) { return true } case ast.KindSpreadAssignment: if assignmentTargetContainsSuperProperty(prop.AsSpreadAssignment().Expression) { return true } } } case ast.KindSpreadElement: return assignmentTargetContainsSuperProperty(node.AsSpreadElement().Expression) } return false } // isUpdateExpression checks if a prefix/postfix unary expression is ++ or --. func isUpdateExpression(node *ast.Node) bool { if ast.IsPrefixUnaryExpression(node) { op := node.AsPrefixUnaryExpression().Operator return op == ast.KindPlusPlusToken || op == ast.KindMinusMinusToken } if ast.IsPostfixUnaryExpression(node) { op := node.AsPostfixUnaryExpression().Operator return op == ast.KindPlusPlusToken || op == ast.KindMinusMinusToken } return false } func (tx *asyncTransformer) getOriginalIfFunctionLike(node *ast.Node) *ast.Node { original := tx.EmitContext().MostOriginal(node) if original != nil && ast.IsFunctionLikeDeclaration(original) { return original } return node } // isSimpleParameterList checks if every parameter has no initializer and an Identifier name. func isSimpleParameterList(params []*ast.Node) bool { for _, param := range params { p := param.AsParameterDeclaration() if p.Initializer != nil || !ast.IsIdentifier(p.Name()) { return false } } return true } // isNodeWithPossibleHoistedDeclaration checks if a node could contain hoisted declarations. func isNodeWithPossibleHoistedDeclaration(node *ast.Node) bool { switch node.Kind { case ast.KindBlock, ast.KindVariableStatement, ast.KindWithStatement, ast.KindIfStatement, ast.KindSwitchStatement, ast.KindCaseBlock, ast.KindCaseClause, ast.KindDefaultClause, ast.KindLabeledStatement, ast.KindForStatement, ast.KindForInStatement, ast.KindForOfStatement, ast.KindDoStatement, ast.KindWhileStatement, ast.KindTryStatement, ast.KindCatchClause: return true } return false }