241 lines
9.5 KiB
Go
241 lines
9.5 KiB
Go
package estransforms
|
|
|
|
import (
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers"
|
|
)
|
|
|
|
type optionalChainTransformer struct {
|
|
transformers.Transformer
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visit(node *ast.Node) *ast.Node {
|
|
if node.SubtreeFacts()&ast.SubtreeContainsOptionalChaining == 0 {
|
|
return node
|
|
}
|
|
switch node.Kind {
|
|
case ast.KindCallExpression:
|
|
return ch.visitCallExpression(node.AsCallExpression(), false)
|
|
case ast.KindPropertyAccessExpression,
|
|
ast.KindElementAccessExpression:
|
|
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
|
|
return ch.visitOptionalExpression(node, false, false)
|
|
}
|
|
return ch.Visitor().VisitEachChild(node)
|
|
case ast.KindDeleteExpression:
|
|
return ch.visitDeleteExpression(node.AsDeleteExpression())
|
|
default:
|
|
return ch.Visitor().VisitEachChild(node)
|
|
}
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitCallExpression(node *ast.CallExpression, captureThisArg bool) *ast.Node {
|
|
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
|
|
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
|
|
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, false)
|
|
}
|
|
if ast.IsParenthesizedExpression(node.Expression) {
|
|
unwrapped := ast.SkipParentheses(node.Expression)
|
|
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 {
|
|
// capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()`
|
|
expression := ch.visitParenthesizedExpression(node.Expression.AsParenthesizedExpression(), true, false)
|
|
args := ch.Visitor().VisitNodes(node.Arguments)
|
|
if ast.IsSyntheticReferenceExpression(expression) {
|
|
res := ch.Factory().NewFunctionCallCall(expression.AsSyntheticReferenceExpression().Expression, expression.AsSyntheticReferenceExpression().ThisArg, args.Nodes)
|
|
res.Loc = node.Loc
|
|
ch.EmitContext().SetOriginal(res, node.AsNode())
|
|
return res
|
|
}
|
|
return ch.Factory().UpdateCallExpression(node, expression, nil, nil, args)
|
|
}
|
|
}
|
|
return ch.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitParenthesizedExpression(node *ast.ParenthesizedExpression, captureThisArg bool, isDelete bool) *ast.Node {
|
|
expr := ch.visitNonOptionalExpression(node.Expression, captureThisArg, isDelete)
|
|
if ast.IsSyntheticReferenceExpression(expr) {
|
|
// `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` }
|
|
// `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` }
|
|
synth := expr.AsSyntheticReferenceExpression()
|
|
res := ch.Factory().NewSyntheticReferenceExpression(ch.Factory().UpdateParenthesizedExpression(node, synth.Expression), synth.ThisArg)
|
|
ch.EmitContext().SetOriginal(res, node.AsNode())
|
|
return res
|
|
}
|
|
return ch.Factory().UpdateParenthesizedExpression(node, expr)
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitPropertyOrElementAccessExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression {
|
|
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
|
|
// If `node` is an optional chain, then it is the outermost chain of an optional expression.
|
|
return ch.visitOptionalExpression(node.AsNode(), captureThisArg, isDelete)
|
|
}
|
|
expression := ch.Visitor().VisitNode(node.Expression())
|
|
debug.AssertNotNode(expression, ast.IsSyntheticReferenceExpression)
|
|
|
|
var thisArg *ast.Expression
|
|
if captureThisArg {
|
|
if !transformers.IsSimpleCopiableExpression(expression) {
|
|
thisArg = ch.Factory().NewTempVariable()
|
|
ch.EmitContext().AddVariableDeclaration(thisArg)
|
|
expression = ch.Factory().NewAssignmentExpression(thisArg, expression)
|
|
} else {
|
|
thisArg = expression
|
|
}
|
|
}
|
|
|
|
if node.Kind == ast.KindPropertyAccessExpression {
|
|
p := node.AsPropertyAccessExpression()
|
|
expression = ch.Factory().UpdatePropertyAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.Name()))
|
|
} else {
|
|
p := node.AsElementAccessExpression()
|
|
expression = ch.Factory().UpdateElementAccessExpression(p, expression, nil, ch.Visitor().VisitNode(p.AsElementAccessExpression().ArgumentExpression))
|
|
}
|
|
|
|
if thisArg != nil {
|
|
res := ch.Factory().NewSyntheticReferenceExpression(expression, thisArg)
|
|
ch.EmitContext().SetOriginal(res, node.AsNode())
|
|
return res
|
|
}
|
|
return expression
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitDeleteExpression(node *ast.DeleteExpression) *ast.Node {
|
|
unwrapped := ast.SkipParentheses(node.Expression)
|
|
if unwrapped.Flags&ast.NodeFlagsOptionalChain != 0 {
|
|
return ch.visitNonOptionalExpression(node.Expression, false, true)
|
|
}
|
|
return ch.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitNonOptionalExpression(node *ast.Expression, captureThisArg bool, isDelete bool) *ast.Expression {
|
|
switch node.Kind {
|
|
case ast.KindParenthesizedExpression:
|
|
return ch.visitParenthesizedExpression(node.AsParenthesizedExpression(), captureThisArg, isDelete)
|
|
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression:
|
|
return ch.visitPropertyOrElementAccessExpression(node, captureThisArg, isDelete)
|
|
case ast.KindCallExpression:
|
|
return ch.visitCallExpression(node.AsCallExpression(), captureThisArg)
|
|
default:
|
|
return ch.Visitor().VisitNode(node.AsNode())
|
|
}
|
|
}
|
|
|
|
type flattenResult struct {
|
|
expression *ast.Expression
|
|
chain []*ast.Node
|
|
}
|
|
|
|
func isNonNullChain(node *ast.Node) bool {
|
|
return ast.IsNonNullExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0
|
|
}
|
|
|
|
func flattenChain(chain *ast.Node) flattenResult {
|
|
debug.AssertNotNode(chain, isNonNullChain)
|
|
links := []*ast.Node{chain}
|
|
for !ast.IsTaggedTemplateExpression(chain) && chain.QuestionDotToken() == nil {
|
|
chain = ast.SkipPartiallyEmittedExpressions(chain.Expression())
|
|
debug.AssertNotNode(chain, isNonNullChain)
|
|
links = append([]*ast.Node{chain}, links...)
|
|
}
|
|
return flattenResult{chain.Expression(), links}
|
|
}
|
|
|
|
func isCallChain(node *ast.Node) bool {
|
|
return ast.IsCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0
|
|
}
|
|
|
|
func (ch *optionalChainTransformer) visitOptionalExpression(node *ast.Node, captureThisArg bool, isDelete bool) *ast.Node {
|
|
r := flattenChain(node)
|
|
expression := r.expression
|
|
chain := r.chain
|
|
left := ch.visitNonOptionalExpression(ast.SkipPartiallyEmittedExpressions(expression), isCallChain(chain[0]), false)
|
|
var leftThisArg *ast.Expression
|
|
capturedLeft := left
|
|
if ast.IsSyntheticReferenceExpression(left) {
|
|
leftThisArg = left.AsSyntheticReferenceExpression().ThisArg
|
|
capturedLeft = left.AsSyntheticReferenceExpression().Expression
|
|
}
|
|
leftExpression := ch.Factory().RestoreOuterExpressions(expression, capturedLeft, ast.OEKPartiallyEmittedExpressions)
|
|
if !transformers.IsSimpleCopiableExpression(capturedLeft) {
|
|
capturedLeft = ch.Factory().NewTempVariable()
|
|
ch.EmitContext().AddVariableDeclaration(capturedLeft)
|
|
leftExpression = ch.Factory().NewAssignmentExpression(capturedLeft, leftExpression)
|
|
}
|
|
rightExpression := capturedLeft
|
|
var thisArg *ast.Expression
|
|
|
|
for i, segment := range chain {
|
|
switch segment.Kind {
|
|
case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression:
|
|
if i == len(chain)-1 && captureThisArg {
|
|
if !transformers.IsSimpleCopiableExpression(rightExpression) {
|
|
thisArg = ch.Factory().NewTempVariable()
|
|
ch.EmitContext().AddVariableDeclaration(thisArg)
|
|
rightExpression = ch.Factory().NewAssignmentExpression(thisArg, rightExpression)
|
|
} else {
|
|
thisArg = rightExpression
|
|
}
|
|
}
|
|
if segment.Kind == ast.KindElementAccessExpression {
|
|
rightExpression = ch.Factory().NewElementAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsElementAccessExpression().ArgumentExpression), ast.NodeFlagsNone)
|
|
} else {
|
|
rightExpression = ch.Factory().NewPropertyAccessExpression(rightExpression, nil, ch.Visitor().VisitNode(segment.AsPropertyAccessExpression().Name()), ast.NodeFlagsNone)
|
|
}
|
|
case ast.KindCallExpression:
|
|
if i == 0 && leftThisArg != nil {
|
|
if !ch.EmitContext().HasAutoGenerateInfo(leftThisArg) {
|
|
leftThisArg = leftThisArg.Clone(ch.Factory())
|
|
ch.EmitContext().AddEmitFlags(leftThisArg, printer.EFNoComments)
|
|
}
|
|
callThisArg := leftThisArg
|
|
if leftThisArg.Kind == ast.KindSuperKeyword {
|
|
callThisArg = ch.Factory().NewThisExpression()
|
|
}
|
|
rightExpression = ch.Factory().NewFunctionCallCall(rightExpression, callThisArg, ch.Visitor().VisitNodes(segment.ArgumentList()).Nodes)
|
|
} else {
|
|
rightExpression = ch.Factory().NewCallExpression(
|
|
rightExpression,
|
|
nil,
|
|
nil,
|
|
ch.Visitor().VisitNodes(segment.ArgumentList()),
|
|
ast.NodeFlagsNone,
|
|
)
|
|
}
|
|
}
|
|
ch.EmitContext().SetOriginal(rightExpression, segment)
|
|
}
|
|
|
|
var target *ast.Node
|
|
if isDelete {
|
|
target = ch.Factory().NewConditionalExpression(
|
|
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true),
|
|
ch.Factory().NewToken(ast.KindQuestionToken),
|
|
ch.Factory().NewTrueExpression(),
|
|
ch.Factory().NewToken(ast.KindColonToken),
|
|
ch.Factory().NewDeleteExpression(rightExpression),
|
|
)
|
|
} else {
|
|
target = ch.Factory().NewConditionalExpression(
|
|
createNotNullCondition(ch.EmitContext(), leftExpression, capturedLeft, true),
|
|
ch.Factory().NewToken(ast.KindQuestionToken),
|
|
ch.Factory().NewVoidZeroExpression(),
|
|
ch.Factory().NewToken(ast.KindColonToken),
|
|
rightExpression,
|
|
)
|
|
}
|
|
target.Loc = node.Loc
|
|
if thisArg != nil {
|
|
target = ch.Factory().NewSyntheticReferenceExpression(target, thisArg)
|
|
}
|
|
ch.EmitContext().SetOriginal(target, node.AsNode())
|
|
return target
|
|
}
|
|
|
|
func newOptionalChainTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
|
|
tx := &optionalChainTransformer{}
|
|
return tx.NewTransformer(tx.visit, opts.Context)
|
|
}
|