996 lines
34 KiB
Go
996 lines
34 KiB
Go
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
|
|
}
|