package estransforms import ( "strconv" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "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 pendingDecl struct { pendingExpressions []*ast.Node name *ast.Node value *ast.Node location core.TextRange original *ast.Node } type flattenLevel int const ( flattenLevelAll flattenLevel = iota flattenLevelObjectRest ) type flattenContext struct { level flattenLevel currentExpressions []*ast.Node currentDeclarations []pendingDecl hasTransformedPriorElement bool emitBindingOrAssignment func(t *objectRestSpreadTransformer, target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node) createArrayBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node createObjectBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node createArrayBindingOrAssignmentElement func(t *objectRestSpreadTransformer, expr *ast.Node) *ast.Node hoistTempVariables bool } type oldFlattenContext flattenContext type objectRestSpreadTransformer struct { transformers.Transformer compilerOptions *core.CompilerOptions inExportedVariableStatement bool ctx flattenContext parametersWithPrecedingObjectRestOrSpread map[*ast.Node]struct{} } func (ch *objectRestSpreadTransformer) enterFlattenContext( level flattenLevel, emitBindingOrAssignment func(t *objectRestSpreadTransformer, target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node), createArrayBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node, createObjectBindingOrAssignmentPattern func(t *objectRestSpreadTransformer, elements []*ast.Node) *ast.Node, createArrayBindingOrAssignmentElement func(t *objectRestSpreadTransformer, expr *ast.Node) *ast.Node, hoistTempVariables bool, ) oldFlattenContext { old := ch.ctx ch.ctx = flattenContext{ level: level, emitBindingOrAssignment: emitBindingOrAssignment, createArrayBindingOrAssignmentPattern: createArrayBindingOrAssignmentPattern, createObjectBindingOrAssignmentPattern: createObjectBindingOrAssignmentPattern, createArrayBindingOrAssignmentElement: createArrayBindingOrAssignmentElement, hoistTempVariables: hoistTempVariables, } return oldFlattenContext(old) } func (ch *objectRestSpreadTransformer) exitFlattenContext(old oldFlattenContext) { ch.ctx = flattenContext(old) } func (ch *objectRestSpreadTransformer) visit(node *ast.Node) *ast.Node { if node.SubtreeFacts()&ast.SubtreeContainsESObjectRestOrSpread == 0 && ch.parametersWithPrecedingObjectRestOrSpread == nil { return node } switch node.Kind { case ast.KindSourceFile: return ch.visitSourceFile(node.AsSourceFile()) case ast.KindObjectLiteralExpression: return ch.visitObjectLiteralExpression(node.AsObjectLiteralExpression()) case ast.KindBinaryExpression: return ch.visitBinaryExpression(node.AsBinaryExpression()) case ast.KindForOfStatement: return ch.visitForOftatement(node.AsForInOrOfStatement()) case ast.KindVariableStatement: return ch.visitVariableStatement(node.AsVariableStatement()) case ast.KindVariableDeclaration: return ch.visitVariableDeclaration(node.AsVariableDeclaration()) case ast.KindCatchClause: return ch.visitCatchClause(node.AsCatchClause()) case ast.KindParameter: return ch.visitParameter(node.AsParameterDeclaration()) case ast.KindConstructor: return ch.visitContructorDeclaration(node.AsConstructorDeclaration()) case ast.KindGetAccessor: return ch.visitGetAccessorDeclaration(node.AsGetAccessorDeclaration()) case ast.KindSetAccessor: return ch.visitSetAccessorDeclaration(node.AsSetAccessorDeclaration()) case ast.KindMethodDeclaration: return ch.visitMethodDeclaration(node.AsMethodDeclaration()) case ast.KindFunctionDeclaration: return ch.visitFunctionDeclaration(node.AsFunctionDeclaration()) case ast.KindArrowFunction: return ch.visitArrowFunction(node.AsArrowFunction()) case ast.KindFunctionExpression: return ch.visitFunctionExpression(node.AsFunctionExpression()) default: return ch.Visitor().VisitEachChild(node) } } func (ch *objectRestSpreadTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node { visited := ch.Visitor().VisitEachChild(node.AsNode()) ch.EmitContext().AddEmitHelper(visited.AsNode(), ch.EmitContext().ReadEmitHelpers()...) return visited } func (ch *objectRestSpreadTransformer) visitParameter(node *ast.ParameterDeclaration) *ast.Node { if ch.parametersWithPrecedingObjectRestOrSpread != nil { if _, ok := ch.parametersWithPrecedingObjectRestOrSpread[node.AsNode()]; ok { name := node.Name() if ast.IsBindingPattern(name) { name = ch.Factory().NewGeneratedNameForNode(node.AsNode()) } return ch.Factory().UpdateParameterDeclaration( node, nil, node.DotDotDotToken, name, nil, nil, nil, ) } } if node.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 { // Binding patterns are converted into a generated name and are // evaluated inside the function body. return ch.Factory().UpdateParameterDeclaration( node, nil, node.DotDotDotToken, ch.Factory().NewGeneratedNameForNode(node.AsNode()), nil, nil, ch.Visitor().VisitNode(node.Initializer), ) } return ch.Visitor().VisitEachChild(node.AsNode()) } func (ch *objectRestSpreadTransformer) collectParametersWithPrecedingObjectRestOrSpread(node *ast.Node) map[*ast.Node]struct{} { var result map[*ast.Node]struct{} for _, parameter := range node.Parameters() { if result != nil { result[parameter] = struct{}{} } else if parameter.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 { result = make(map[*ast.Node]struct{}) } } return result } type oldParamScope map[*ast.Node]struct{} func (ch *objectRestSpreadTransformer) enterParameterListContext(node *ast.Node) oldParamScope { old := ch.parametersWithPrecedingObjectRestOrSpread ch.parametersWithPrecedingObjectRestOrSpread = ch.collectParametersWithPrecedingObjectRestOrSpread(node) return oldParamScope(old) } func (ch *objectRestSpreadTransformer) exitParameterListContext(scope oldParamScope) { ch.parametersWithPrecedingObjectRestOrSpread = map[*ast.Node]struct{}(scope) } func (ch *objectRestSpreadTransformer) visitContructorDeclaration(node *ast.ConstructorDeclaration) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateConstructorDeclaration( node, node.Modifiers(), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitGetAccessorDeclaration(node *ast.GetAccessorDeclaration) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateGetAccessorDeclaration( node, node.Modifiers(), ch.Visitor().VisitNode(node.Name()), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitSetAccessorDeclaration(node *ast.SetAccessorDeclaration) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateSetAccessorDeclaration( node, node.Modifiers(), ch.Visitor().VisitNode(node.Name()), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitMethodDeclaration(node *ast.MethodDeclaration) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateMethodDeclaration( node, node.Modifiers(), node.AsteriskToken, ch.Visitor().VisitNode(node.Name()), node.PostfixToken, nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitFunctionDeclaration(node *ast.FunctionDeclaration) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateFunctionDeclaration( node, node.Modifiers(), node.AsteriskToken, ch.Visitor().VisitNode(node.Name()), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitArrowFunction(node *ast.ArrowFunction) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateArrowFunction( node, node.Modifiers(), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, node.EqualsGreaterThanToken, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) visitFunctionExpression(node *ast.FunctionExpression) *ast.Node { old := ch.enterParameterListContext(node.AsNode()) defer ch.exitParameterListContext(old) return ch.Factory().UpdateFunctionExpression( node, node.Modifiers(), node.AsteriskToken, ch.Visitor().VisitNode(node.Name()), nil, ch.Visitor().VisitNodes(node.Parameters), nil, nil, ch.transformFunctionBody(node.AsNode()), ) } func (ch *objectRestSpreadTransformer) transformFunctionBody(node *ast.Node) *ast.Node { ch.EmitContext().StartVariableEnvironment() body := ch.Visitor().VisitNode(node.Body()) extras := ch.EmitContext().EndVariableEnvironment() ch.EmitContext().StartVariableEnvironment() newStatements := ch.collectObjectRestAssignments(node) extras = ch.EmitContext().EndAndMergeVariableEnvironment(extras) if len(newStatements) == 0 && len(extras) == 0 { return body } if body == nil { body = ch.Factory().NewBlock(ch.Factory().NewNodeList([]*ast.Node{}), true) } var prefix []*ast.Node var suffix []*ast.Node if ast.IsBlock(body) { custom := false for i, statement := range body.AsBlock().Statements.Nodes { if !custom && ast.IsPrologueDirective(statement) { prefix = append(prefix, statement) } else if ch.EmitContext().EmitFlags(statement)&printer.EFCustomPrologue != 0 { custom = true prefix = append(prefix, statement) } else { suffix = body.AsBlock().Statements.Nodes[i:] break } } } else { ret := ch.Factory().NewReturnStatement(body) ret.Loc = body.Loc list := ch.Factory().NewNodeList([]*ast.Node{}) list.Loc = body.Loc body = ch.Factory().NewBlock(list, true) suffix = append(suffix, ret) } newStatementList := ch.Factory().NewNodeList(append(append(append(prefix, extras...), newStatements...), suffix...)) newStatementList.Loc = body.AsBlock().Statements.Loc return ch.Factory().UpdateBlock(body.AsBlock(), newStatementList) } func (ch *objectRestSpreadTransformer) collectObjectRestAssignments(node *ast.Node) []*ast.Node { containsPrecedingObjectRestOrSpread := false var results []*ast.Node for _, parameter := range node.Parameters() { if containsPrecedingObjectRestOrSpread { if ast.IsBindingPattern(parameter.Name()) { // In cases where a binding pattern is simply '[]' or '{}', // we usually don't want to emit a var declaration; however, in the presence // of an initializer, we must emit that expression to preserve side effects. if len(parameter.Name().AsBindingPattern().Elements.Nodes) > 0 { declarations := ch.flattenDestructuringBinding(flattenLevelAll, parameter, ch.Factory().NewGeneratedNameForNode(parameter), false, false) if declarations != nil { declarationList := ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList([]*ast.Node{})) decls := []*ast.Node{declarations} if declarations.Kind == ast.KindSyntaxList { decls = declarations.AsSyntaxList().Children } declarationList.AsVariableDeclarationList().Declarations.Nodes = append(declarationList.AsVariableDeclarationList().Declarations.Nodes, decls...) statement := ch.Factory().NewVariableStatement(nil, declarationList) ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue) results = append(results, statement) } } else if parameter.Initializer() != nil { name := ch.Factory().NewGeneratedNameForNode(parameter) initializer := ch.Visitor().VisitNode(parameter.Initializer()) assignment := ch.Factory().NewAssignmentExpression(name, initializer) statement := ch.Factory().NewExpressionStatement(assignment) ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue) results = append(results, statement) } } else if parameter.Initializer() != nil { // Converts a parameter initializer into a function body statement, i.e.: // // function f(x = 1) { } // // becomes // // function f(x) { // if (typeof x === "undefined") { x = 1; } // } name := parameter.Name().Clone(ch.Factory()) name.Loc = parameter.Name().Loc ch.EmitContext().AddEmitFlags(name, printer.EFNoSourceMap) initializer := ch.Visitor().VisitNode(parameter.Initializer()) ch.EmitContext().AddEmitFlags(initializer, printer.EFNoSourceMap|printer.EFNoComments) assignment := ch.Factory().NewAssignmentExpression(name, initializer) assignment.Loc = parameter.Loc ch.EmitContext().AddEmitFlags(assignment, printer.EFNoComments) block := ch.Factory().NewBlock(ch.Factory().NewNodeList([]*ast.Node{ch.Factory().NewExpressionStatement(assignment)}), false) block.Loc = parameter.Loc ch.EmitContext().AddEmitFlags(block, printer.EFSingleLine|printer.EFNoTrailingSourceMap|printer.EFNoTokenSourceMaps|printer.EFNoComments) typeCheck := ch.Factory().NewTypeCheck(name.Clone(ch.Factory()), "undefined") statement := ch.Factory().NewIfStatement(typeCheck, block, nil) statement.Loc = parameter.Loc ch.EmitContext().AddEmitFlags(statement, printer.EFNoTokenSourceMaps|printer.EFNoTrailingSourceMap|printer.EFCustomPrologue|printer.EFNoComments|printer.EFStartOnNewLine) results = append(results, statement) } } else if parameter.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 { containsPrecedingObjectRestOrSpread = true declarations := ch.flattenDestructuringBinding(flattenLevelObjectRest, parameter, ch.Factory().NewGeneratedNameForNode(parameter), false, true) if declarations != nil { declarationList := ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList([]*ast.Node{})) decls := []*ast.Node{declarations} if declarations.Kind == ast.KindSyntaxList { decls = declarations.AsSyntaxList().Children } declarationList.AsVariableDeclarationList().Declarations.Nodes = append(declarationList.AsVariableDeclarationList().Declarations.Nodes, decls...) statement := ch.Factory().NewVariableStatement(nil, declarationList) ch.EmitContext().AddEmitFlags(statement, printer.EFCustomPrologue) results = append(results, statement) } } } return results } func (ch *objectRestSpreadTransformer) visitCatchClause(node *ast.CatchClause) *ast.Node { if node.VariableDeclaration != nil && ast.IsBindingPattern(node.VariableDeclaration.Name()) && node.VariableDeclaration.Name().SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 { name := ch.Factory().NewGeneratedNameForNode(node.VariableDeclaration.Name()) updatedDecl := ch.Factory().UpdateVariableDeclaration(node.VariableDeclaration.AsVariableDeclaration(), node.VariableDeclaration.Name(), nil, nil, name) visitedBindings := ch.flattenDestructuringBinding(flattenLevelObjectRest, updatedDecl, nil, false, false) block := ch.Visitor().VisitNode(node.Block) if visitedBindings != nil { var decls []*ast.Node if visitedBindings.Kind&ast.KindSyntaxList != 0 { decls = visitedBindings.AsSyntaxList().Children } else { decls = []*ast.Node{visitedBindings} } newStatement := ch.Factory().NewVariableStatement(nil, ch.Factory().NewVariableDeclarationList(ast.NodeFlagsNone, ch.Factory().NewNodeList(decls))) statements := []*ast.Node{newStatement} if block.AsBlock().Statements != nil && len(block.AsBlock().Statements.Nodes) > 0 { statements = append(statements, block.AsBlock().Statements.Nodes...) } statementList := ch.Factory().NewNodeList(statements) statementList.Loc = block.AsBlock().Statements.Loc block = ch.Factory().UpdateBlock(block.AsBlock(), statementList) } return ch.Factory().UpdateCatchClause( node, ch.Factory().UpdateVariableDeclaration(node.VariableDeclaration.AsVariableDeclaration(), name, nil, nil, nil), block, ) } return ch.Visitor().VisitEachChild(node.AsNode()) } func (ch *objectRestSpreadTransformer) visitVariableStatement(node *ast.VariableStatement) *ast.Node { if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) { oldInExportedVariableStatement := ch.inExportedVariableStatement ch.inExportedVariableStatement = true result := ch.Visitor().VisitEachChild(node.AsNode()) ch.inExportedVariableStatement = oldInExportedVariableStatement return result } return ch.Visitor().VisitEachChild(node.AsNode()) } func (ch *objectRestSpreadTransformer) visitVariableDeclaration(node *ast.VariableDeclaration) *ast.Node { if ch.inExportedVariableStatement { ch.inExportedVariableStatement = false result := ch.visitVariableDeclarationWorker(node, true) ch.inExportedVariableStatement = true return result } return ch.visitVariableDeclarationWorker(node, false) } func (ch *objectRestSpreadTransformer) visitVariableDeclarationWorker(node *ast.VariableDeclaration, exported bool) *ast.Node { // If we are here it is because the name contains a binding pattern with a rest somewhere in it. if ast.IsBindingPattern(node.Name()) && node.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 { return ch.flattenDestructuringBinding( flattenLevelObjectRest, node.AsNode(), nil, exported, false, ) } return ch.Visitor().VisitEachChild(node.AsNode()) } func (ch *objectRestSpreadTransformer) flattenDestructuringBinding(level flattenLevel, node *ast.Node, rvalue *ast.Node, hoist bool, skipInitializer bool) *ast.Node { old := ch.enterFlattenContext(level, (*objectRestSpreadTransformer).emitBinding, (*objectRestSpreadTransformer).createArrayBindingPattern, (*objectRestSpreadTransformer).createObjectBindingPattern, (*objectRestSpreadTransformer).createArrayBindingElement, hoist) defer ch.exitFlattenContext(old) if ast.IsVariableDeclaration(node) { initializer := getInitializerOfBindingOrAssignmentElement(node) if initializer != nil && (ast.IsIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.AsIdentifier().Text) || bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { // If the right-hand value of the assignment is also an assignment target then // we need to cache the right-hand value. initializer = ch.ensureIdentifier(ch.Visitor().VisitNode(initializer), false, initializer.Loc) node = ch.Factory().UpdateVariableDeclaration(node.AsVariableDeclaration(), node.Name(), nil, nil, initializer) } } ch.flattenBindingOrAssignmentElement(node, rvalue, node.Loc, skipInitializer) if len(ch.ctx.currentExpressions) > 0 { temp := ch.Factory().NewTempVariable() ch.EmitContext().AddVariableDeclaration(temp) last := &ch.ctx.currentDeclarations[len(ch.ctx.currentDeclarations)-1] last.pendingExpressions = append(last.pendingExpressions, ch.Factory().NewAssignmentExpression(temp, last.value)) last.pendingExpressions = append(last.pendingExpressions, ch.ctx.currentExpressions...) last.value = temp } decls := make([]*ast.Node, 0, len(ch.ctx.currentDeclarations)) for _, pending := range ch.ctx.currentDeclarations { expr := pending.value if len(pending.pendingExpressions) > 0 { expr = ch.Factory().InlineExpressions(append(pending.pendingExpressions, pending.value)) } decl := ch.Factory().NewVariableDeclaration( pending.name, nil, nil, expr, ) decl.Loc = pending.location if pending.original != nil { ch.EmitContext().SetOriginal(decl, pending.original) } decls = append(decls, decl) } if len(decls) == 1 { return decls[0] } if len(decls) == 0 { return nil } return ch.Factory().NewSyntaxList(decls) } func (ch *objectRestSpreadTransformer) visitForOftatement(node *ast.ForInOrOfStatement) *ast.Node { if node.Initializer.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 || (ast.IsAssignmentPattern(node.Initializer) && ast.ContainsObjectRestOrSpread(node.Initializer)) { initializerWithoutParens := ast.SkipParentheses(node.Initializer) if ast.IsVariableDeclarationList(initializerWithoutParens) || ast.IsAssignmentPattern(initializerWithoutParens) { var bodyLocation core.TextRange var statementsLocation core.TextRange temp := ch.Factory().NewTempVariable() res := ch.Visitor().VisitNode(ch.createForOfBindingStatement(initializerWithoutParens, temp)) statements := make([]*ast.Node, 0, 1) if res != nil { statements = append(statements, res) } if ast.IsBlock(node.Statement) { for _, statement := range node.Statement.AsBlock().Statements.Nodes { visited := ch.Visitor().VisitEachChild(statement) if visited != nil { statements = append(statements, visited) } } bodyLocation = node.Statement.Loc statementsLocation = node.Statement.AsBlock().Statements.Loc } else if node.Statement != nil { statements = append(statements, ch.Visitor().VisitEachChild(node.Statement)) bodyLocation = node.Statement.Loc statementsLocation = node.Statement.Loc } list := ch.Factory().NewVariableDeclarationList( ast.NodeFlagsLet, ch.Factory().NewNodeList([]*ast.Node{ch.Factory().NewVariableDeclaration(temp, nil, nil, nil)}), ) list.Loc = node.Initializer.Loc expr := ch.Visitor().VisitEachChild(node.Expression) statementsList := ch.Factory().NewNodeList(statements) statementsList.Loc = statementsLocation block := ch.Factory().NewBlock(statementsList, true) block.Loc = bodyLocation return ch.Factory().UpdateForInOrOfStatement( node, node.AwaitModifier, list, expr, block, ) } } return ch.Visitor().VisitEachChild(node.AsNode()) } func (ch *objectRestSpreadTransformer) createForOfBindingStatement(node *ast.Node, boundValue *ast.Node) *ast.Node { if ast.IsVariableDeclarationList(node) { firstDeclaration := node.AsVariableDeclarationList().Declarations.Nodes[0] updatedDeclaration := ch.Factory().UpdateVariableDeclaration( firstDeclaration.AsVariableDeclaration(), firstDeclaration.Name(), nil, nil, boundValue, ) statement := ch.Factory().NewVariableStatement( nil, ch.Factory().UpdateVariableDeclarationList( node.AsVariableDeclarationList(), ch.Factory().NewNodeList([]*ast.Node{updatedDeclaration}), ), ) statement.Loc = node.Loc return statement } else { updatedExpression := ch.Factory().NewAssignmentExpression(node, boundValue) updatedExpression.Loc = node.Loc statement := ch.Factory().NewExpressionStatement(updatedExpression) statement.Loc = node.Loc return statement } } func (ch *objectRestSpreadTransformer) visitBinaryExpression(node *ast.BinaryExpression) *ast.Node { if !(ast.IsDestructuringAssignment(node.AsNode()) && ast.ContainsObjectRestOrSpread(node.Left)) { return ch.Visitor().VisitEachChild(node.AsNode()) } return ch.flattenDestructuringAssignment( node, ) } func (ch *objectRestSpreadTransformer) flattenDestructuringAssignment(node *ast.BinaryExpression) *ast.Node { location := node.Loc var value *ast.Node if ast.IsDestructuringAssignment(node.AsNode()) { value = node.Right for ast.IsEmptyArrayLiteral(node.Left) || ast.IsEmptyObjectLiteral(node.Left) { if ast.IsDestructuringAssignment(value) { node = value.AsBinaryExpression() location = node.Loc value = node.Right } else { return ch.Visitor().VisitNode(value) } } } old := ch.enterFlattenContext(flattenLevelObjectRest, (*objectRestSpreadTransformer).emitAssignment, (*objectRestSpreadTransformer).createArrayAssignmentPattern, (*objectRestSpreadTransformer).createObjectAssignmentPattern, (*objectRestSpreadTransformer).createArrayAssignmentElement, true) defer ch.exitFlattenContext(old) if value != nil { value = ch.Visitor().VisitNode(value) if ast.IsIdentifier(value) && bindingOrAssignmentElementAssignsToName(node.AsNode(), value.AsIdentifier().Text) || bindingOrAssignmentElementContainsNonLiteralComputedName(node.AsNode()) { // If the right-hand value of the assignment is also an assignment target then // we need to cache the right-hand value. value = ch.ensureIdentifier(value, false, location) } else { value = ch.ensureIdentifier(value, true, location) } if ast.NodeIsSynthesized(node.AsNode()) { // Generally, the source map location for a destructuring assignment is the root // expression. // // However, if the root expression is synthesized (as in the case // of the initializer when transforming a ForOfStatement), then the source map // location should point to the right-hand value of the expression. location = value.Loc } } ch.flattenBindingOrAssignmentElement(node.AsNode(), value, location, ast.IsDestructuringAssignment(node.AsNode())) res := ch.Factory().InlineExpressions(ch.ctx.currentExpressions) if res != nil { return res } return ch.Factory().NewOmittedExpression() } func (ch *objectRestSpreadTransformer) flattenBindingOrAssignmentElement(element *ast.Node, value *ast.Node, location core.TextRange, skipInitializer bool) { bindingTarget := ast.GetTargetOfBindingOrAssignmentElement(element) if !skipInitializer { initializer := ch.Visitor().VisitNode(getInitializerOfBindingOrAssignmentElement(element)) if initializer != nil { // Combine value and initializer if value != nil { value = ch.createDefaultValueCheck(value, initializer, location) // If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern. if !transformers.IsSimpleCopiableExpression(initializer) && (ast.IsBindingPattern(bindingTarget) || ast.IsAssignmentPattern(bindingTarget)) { value = ch.ensureIdentifier(value, true, location) } } else { value = initializer } } else if value == nil { // Use 'void 0' in absence of value and initializer value = ch.Factory().NewVoidZeroExpression() } } if isObjectBindingOrAssignmentPattern(bindingTarget) { ch.flattenObjectBindingOrAssignmentPattern(element, bindingTarget, value, location) } else if isArrayBindingOrAssignmentPattern(bindingTarget) { ch.flattenArrayBindingOrAssignmentPattern(element, bindingTarget, value, location) } else { ch.ctx.emitBindingOrAssignment(ch, bindingTarget, value, location, element) } } func (ch *objectRestSpreadTransformer) flattenObjectBindingOrAssignmentPattern(parent *ast.Node, pattern *ast.Node, value *ast.Node, location core.TextRange) { elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern) numElements := len(elements) if numElements != 1 { // For anything other than a single-element destructuring we need to generate a temporary // to ensure value is evaluated exactly once. Additionally, if we have zero elements // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, // so in that case, we'll intentionally create that temporary. reuseIdentifierExpressions := !ast.IsDeclarationBindingElement(parent) || numElements != 0 value = ch.ensureIdentifier(value, reuseIdentifierExpressions, location) } var bindingElements []*ast.Node var computedTempVariables []*ast.Node for i, element := range elements { if ast.GetRestIndicatorOfBindingOrAssignmentElement(element) == nil { propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element) if ch.ctx.level >= flattenLevelObjectRest && element.SubtreeFacts()&(ast.SubtreeContainsRestOrSpread|ast.SubtreeContainsObjectRestOrSpread) == 0 && ast.GetTargetOfBindingOrAssignmentElement(element).SubtreeFacts()&(ast.SubtreeContainsRestOrSpread|ast.SubtreeContainsObjectRestOrSpread) == 0 && !ast.IsComputedPropertyName(propertyName) { bindingElements = append(bindingElements, ch.Visitor().VisitNode(element)) } else { if len(bindingElements) > 0 { ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern) bindingElements = nil } rhsValue := ch.createDestructuringPropertyAccess(value, propertyName) if ast.IsComputedPropertyName(propertyName) { computedTempVariables = append(computedTempVariables, rhsValue.AsElementAccessExpression().ArgumentExpression) } ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false) } } else if i == numElements-1 { if len(bindingElements) > 0 { ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern) bindingElements = nil } rhsValue := ch.Factory().NewRestHelper(value, elements, computedTempVariables, pattern.Loc) ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false) } } if len(bindingElements) > 0 { ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createObjectBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern) } } type restIdElemPair struct { id *ast.Node element *ast.Node } func (ch *objectRestSpreadTransformer) flattenArrayBindingOrAssignmentPattern(parent *ast.Node, pattern *ast.Node, value *ast.Node, location core.TextRange) { elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern) numElements := len(elements) if numElements != 1 && (ch.ctx.level < flattenLevelObjectRest || numElements == 0) || core.Every(elements, ast.IsOmittedExpression) { // For anything other than a single-element destructuring we need to generate a temporary // to ensure value is evaluated exactly once. Additionally, if we have zero elements // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, // so in that case, we'll intentionally create that temporary. // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", // then we will create temporary variable. reuseIdentifierExpressions := !ast.IsDeclarationBindingElement(parent) || numElements != 0 value = ch.ensureIdentifier(value, reuseIdentifierExpressions, location) } var bindingElements []*ast.Node var restContainingElements []restIdElemPair for i, element := range elements { if ch.ctx.level >= flattenLevelObjectRest { // If an array pattern contains an ObjectRest, we must cache the result so that we // can perform the ObjectRest destructuring in a different declaration if element.SubtreeFacts()&ast.SubtreeContainsObjectRestOrSpread != 0 || ch.ctx.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element) { ch.ctx.hasTransformedPriorElement = true temp := ch.Factory().NewTempVariable() if ch.ctx.hoistTempVariables { ch.EmitContext().AddVariableDeclaration(temp) } restContainingElements = append(restContainingElements, restIdElemPair{temp, element}) bindingElements = append(bindingElements, ch.ctx.createArrayBindingOrAssignmentElement(ch, temp)) } else { bindingElements = append(bindingElements, element) } } else if ast.IsOmittedExpression(element) { continue } else if ast.GetRestIndicatorOfBindingOrAssignmentElement(element) == nil { rhsValue := ch.Factory().NewElementAccessExpression(value, nil, ch.Factory().NewNumericLiteral(strconv.Itoa(i)), ast.NodeFlagsNone) ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false) } else if i == numElements-1 { rhsValue := ch.Factory().NewArraySliceCall(value, i) ch.flattenBindingOrAssignmentElement(element, rhsValue, element.Loc, false) } } if len(bindingElements) > 0 { ch.ctx.emitBindingOrAssignment(ch, ch.ctx.createArrayBindingOrAssignmentPattern(ch, bindingElements), value, location, pattern) } if len(restContainingElements) > 0 { for _, pair := range restContainingElements { ch.flattenBindingOrAssignmentElement(pair.element, pair.id, pair.element.Loc, false) } } } /** * Creates either a PropertyAccessExpression or an ElementAccessExpression for the * right-hand side of a transformed destructuring assignment. * * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation * * @param flattenContext Options used to control flattening. * @param value The RHS value that is the source of the property. * @param propertyName The destructuring property name. */ func (ch *objectRestSpreadTransformer) createDestructuringPropertyAccess(value *ast.Node, propertyName *ast.Node) *ast.Node { if ast.IsComputedPropertyName(propertyName) { argumentExpression := ch.ensureIdentifier(ch.Visitor().VisitNode(propertyName.AsComputedPropertyName().Expression), false, propertyName.Loc) return ch.Factory().NewElementAccessExpression( value, nil, argumentExpression, ast.NodeFlagsNone, ) } else if ast.IsStringOrNumericLiteralLike(propertyName) || ast.IsBigIntLiteral(propertyName) { argumentExpression := propertyName.Clone(ch.Factory()) return ch.Factory().NewElementAccessExpression( value, nil, argumentExpression, ast.NodeFlagsNone, ) } else { name := ch.Factory().NewIdentifier(propertyName.AsIdentifier().Text) return ch.Factory().NewPropertyAccessExpression( value, nil, name, ast.NodeFlagsNone, ) } } func (ch *objectRestSpreadTransformer) createObjectBindingPattern(elements []*ast.Node) *ast.Node { return ch.Factory().NewBindingPattern(ast.KindObjectBindingPattern, ch.Factory().NewNodeList(elements)) } func (ch *objectRestSpreadTransformer) createArrayBindingPattern(elements []*ast.Node) *ast.Node { return ch.Factory().NewBindingPattern(ast.KindArrayBindingPattern, ch.Factory().NewNodeList(elements)) } func (ch *objectRestSpreadTransformer) createObjectAssignmentPattern(elements []*ast.Node) *ast.Node { return ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(elements), false) } func (ch *objectRestSpreadTransformer) createArrayAssignmentPattern(elements []*ast.Node) *ast.Node { return ch.Factory().NewArrayLiteralExpression(ch.Factory().NewNodeList(elements), false) } func (ch *objectRestSpreadTransformer) createArrayAssignmentElement(expr *ast.Node) *ast.Node { return expr } func (ch *objectRestSpreadTransformer) createArrayBindingElement(expr *ast.Node) *ast.Node { return ch.Factory().NewBindingElement(nil, nil, expr, nil) } func (ch *objectRestSpreadTransformer) emitExpression(node *ast.Node) { ch.ctx.currentExpressions = append(ch.ctx.currentExpressions, node) } func (ch *objectRestSpreadTransformer) emitAssignment(target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node) { debug.AssertNode(target, ast.IsExpression) expr := ch.Factory().NewAssignmentExpression(ch.Visitor().VisitNode(target), value) expr.Loc = location ch.EmitContext().SetOriginal(expr, original) ch.emitExpression(expr) } func isBindingName(node *ast.Node) bool { return node.Kind == ast.KindIdentifier || node.Kind == ast.KindArrayBindingPattern || node.Kind == ast.KindObjectBindingPattern } func (ch *objectRestSpreadTransformer) emitBinding(target *ast.Node, value *ast.Node, location core.TextRange, original *ast.Node) { debug.AssertNode(target, isBindingName) if len(ch.ctx.currentExpressions) > 0 { value = ch.Factory().InlineExpressions(append(ch.ctx.currentExpressions, value)) ch.ctx.currentExpressions = nil } ch.ctx.currentDeclarations = append(ch.ctx.currentDeclarations, pendingDecl{ ch.ctx.currentExpressions, target, value, location, original, }) } func (ch *objectRestSpreadTransformer) ensureIdentifier(value *ast.Node, reuseIdentifierExpressions bool, location core.TextRange) *ast.Node { if reuseIdentifierExpressions && ast.IsIdentifier(value) { return value } temp := ch.Factory().NewTempVariable() if ch.ctx.hoistTempVariables { ch.EmitContext().AddVariableDeclaration(temp) assign := ch.Factory().NewAssignmentExpression(temp, value) assign.Loc = location ch.emitExpression(assign) } else { ch.ctx.emitBindingOrAssignment(ch, temp, value, location, nil) } return temp } func (ch *objectRestSpreadTransformer) createDefaultValueCheck(value *ast.Expression, defaultValue *ast.Expression, location core.TextRange) *ast.Node { value = ch.ensureIdentifier(value, true, location) return ch.Factory().NewConditionalExpression( ch.Factory().NewTypeCheck(value, "undefined"), ch.Factory().NewToken(ast.KindQuestionToken), defaultValue, ch.Factory().NewToken(ast.KindColonToken), value, ) } func (ch *objectRestSpreadTransformer) visitObjectLiteralExpression(node *ast.ObjectLiteralExpression) *ast.Node { if (node.SubtreeFacts() & ast.SubtreeContainsObjectRestOrSpread) == 0 { return ch.Visitor().VisitEachChild(node.AsNode()) } // spread elements emit like so: // non-spread elements are chunked together into object literals, and then all are passed to __assign: // { a, ...o, b } => __assign(__assign({a}, o), {b}); // If the first element is a spread element, then the first argument to __assign is {}: // { ...o, a, b, ...o2 } => __assign(__assign(__assign({}, o), {a, b}), o2) // // We cannot call __assign with more than two elements, since any element could cause side effects. For // example: // var k = { a: 1, b: 2 }; // var o = { a: 3, ...k, b: k.a++ }; // // expected: { a: 1, b: 1 } // If we translate the above to `__assign({ a: 3 }, k, { b: k.a++ })`, the `k.a++` will evaluate before // `k` is spread and we end up with `{ a: 2, b: 1 }`. // // This also occurs for spread elements, not just property assignments: // var k = { a: 1, get b() { l = { z: 9 }; return 2; } }; // var l = { c: 3 }; // var o = { ...k, ...l }; // // expected: { a: 1, b: 2, z: 9 } // If we translate the above to `__assign({}, k, l)`, the `l` will evaluate before `k` is spread and we // end up with `{ a: 1, b: 2, c: 3 }` objects := ch.chunkObjectLiteralElements(node.Properties) if len(objects) > 0 && objects[0].Kind != ast.KindObjectLiteralExpression { objects = append([]*ast.Node{ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(nil), false)}, objects...) } expression := objects[0] if len(objects) > 1 { for i, obj := range objects { if i == 0 { continue } expression = ch.Factory().NewAssignHelper([]*ast.Node{expression, obj}, ch.compilerOptions.GetEmitScriptTarget()) } return expression } return ch.Factory().NewAssignHelper(objects, ch.compilerOptions.GetEmitScriptTarget()) } func (ch *objectRestSpreadTransformer) chunkObjectLiteralElements(list *ast.NodeList) []*ast.Node { if list == nil || len(list.Nodes) == 0 { return nil } elements := list.Nodes var chunkObject []*ast.Node objects := make([]*ast.Node, 0, 1) for _, e := range elements { if e.Kind == ast.KindSpreadAssignment { if len(chunkObject) > 0 { objects = append(objects, ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(chunkObject), false)) chunkObject = nil } target := e.Expression() objects = append(objects, ch.Visitor().VisitNode(target)) } else { var elem *ast.Node if e.Kind == ast.KindPropertyAssignment { elem = ch.Factory().NewPropertyAssignment(nil, e.Name(), nil, nil, ch.Visitor().VisitNode(e.Initializer())) } else { elem = ch.Visitor().VisitNode(e) } chunkObject = append(chunkObject, elem) } } if len(chunkObject) > 0 { objects = append(objects, ch.Factory().NewObjectLiteralExpression(ch.Factory().NewNodeList(chunkObject), false)) } return objects } func newObjectRestSpreadTransformer(opts *transformers.TransformOptions) *transformers.Transformer { tx := &objectRestSpreadTransformer{compilerOptions: opts.CompilerOptions} return tx.NewTransformer(tx.visit, opts.Context) } func bindingOrAssignmentElementAssignsToName(element *ast.Node, name string) bool { target := ast.GetTargetOfBindingOrAssignmentElement(element) if target == nil { return false } if ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target) { return bindingOrAssignmentPatternAssignsToName(target, name) } else if ast.IsIdentifier(target) { return target.AsIdentifier().Text == name } return false } func bindingOrAssignmentPatternAssignsToName(pattern *ast.Node, name string) bool { elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern) for _, element := range elements { if bindingOrAssignmentElementAssignsToName(element, name) { return true } } return false } func bindingOrAssignmentElementContainsNonLiteralComputedName(element *ast.Node) bool { propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element) if propertyName != nil && ast.IsComputedPropertyName(propertyName) && !ast.IsLiteralExpression(propertyName.AsComputedPropertyName().Expression) { return true } target := ast.GetTargetOfBindingOrAssignmentElement(element) return target != nil && (ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target)) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target) } func bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern *ast.Node) bool { elements := ast.GetElementsOfBindingOrAssignmentPattern(pattern) for _, element := range elements { if bindingOrAssignmentElementContainsNonLiteralComputedName(element) { return true } } return false } func getInitializerOfBindingOrAssignmentElement(bindingElement *ast.Node) *ast.Node { if ast.IsDeclarationBindingElement(bindingElement) { // `1` in `let { a = 1 } = ...` // `1` in `let { a: b = 1 } = ...` // `1` in `let { a: {b} = 1 } = ...` // `1` in `let { a: [b] = 1 } = ...` // `1` in `let [a = 1] = ...` // `1` in `let [{a} = 1] = ...` // `1` in `let [[a] = 1] = ...` return bindingElement.Initializer() } if ast.IsPropertyAssignment(bindingElement) { // `1` in `({ a: b = 1 } = ...)` // `1` in `({ a: {b} = 1 } = ...)` // `1` in `({ a: [b] = 1 } = ...)` initializer := bindingElement.Initializer() if ast.IsAssignmentExpression(initializer, true) { return initializer.AsBinaryExpression().Right } return nil } if ast.IsShorthandPropertyAssignment(bindingElement) { // `1` in `({ a = 1 } = ...)` return bindingElement.AsShorthandPropertyAssignment().ObjectAssignmentInitializer } if ast.IsAssignmentExpression(bindingElement, true) { // `1` in `[a = 1] = ...` // `1` in `[{a} = 1] = ...` // `1` in `[[a] = 1] = ...` return bindingElement.AsBinaryExpression().Right } if ast.IsSpreadElement(bindingElement) { // Recovery consistent with existing emit. return getInitializerOfBindingOrAssignmentElement(bindingElement.Expression()) } return nil } func isObjectBindingOrAssignmentPattern(node *ast.Node) bool { return node.Kind == ast.KindObjectBindingPattern || node.Kind == ast.KindObjectLiteralExpression } func isArrayBindingOrAssignmentPattern(node *ast.Node) bool { return node.Kind == ast.KindArrayBindingPattern || node.Kind == ast.KindArrayLiteralExpression } func isSimpleBindingOrAssignmentElement(element *ast.Node) bool { target := ast.GetTargetOfBindingOrAssignmentElement(element) if target == nil || ast.IsOmittedExpression(target) { return true } propertyName := ast.TryGetPropertyNameOfBindingOrAssignmentElement(element) if propertyName != nil && !ast.IsPropertyNameLiteral(propertyName) { return false } initializer := getInitializerOfBindingOrAssignmentElement(element) if initializer != nil && !transformers.IsSimpleInlineableExpression(initializer) { return false } if ast.IsBindingPattern(target) || ast.IsAssignmentPattern(target) { return core.Every(ast.GetElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement) } return ast.IsIdentifier(target) }