Skip to content

Port object rest and spread transform #1529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 5 additions & 5 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2754,7 +2754,7 @@ func (node *ForInOrOfStatement) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Initializer) |
propagateSubtreeFacts(node.Expression) |
propagateSubtreeFacts(node.Statement) |
core.IfElse(node.AwaitModifier != nil, SubtreeContainsES2018, SubtreeFactsNone)
core.IfElse(node.AwaitModifier != nil, SubtreeContainsForAwaitOrAsyncGenerator, SubtreeFactsNone)
}

func IsForInStatement(node *Node) bool {
Expand Down Expand Up @@ -3651,7 +3651,7 @@ func (node *BindingElement) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.PropertyName) |
propagateSubtreeFacts(node.name) |
propagateSubtreeFacts(node.Initializer) |
core.IfElse(node.DotDotDotToken != nil, SubtreeContainsRest, SubtreeFactsNone)
core.IfElse(node.DotDotDotToken != nil, SubtreeContainsRestOrSpread, SubtreeFactsNone)
}

func IsBindingElement(node *Node) bool {
Expand Down Expand Up @@ -6039,7 +6039,7 @@ func (node *YieldExpression) Clone(f NodeFactoryCoercible) *Node {
}

func (node *YieldExpression) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Expression) | SubtreeContainsES2018
return propagateSubtreeFacts(node.Expression) | SubtreeContainsForAwaitOrAsyncGenerator
}

// ArrowFunction
Expand Down Expand Up @@ -6661,7 +6661,7 @@ func (node *SpreadElement) Clone(f NodeFactoryCoercible) *Node {
}

func (node *SpreadElement) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Expression)
return propagateSubtreeFacts(node.Expression) | SubtreeContainsRestOrSpread
}

func IsSpreadElement(node *Node) bool {
Expand Down Expand Up @@ -6984,7 +6984,7 @@ func (node *SpreadAssignment) Clone(f NodeFactoryCoercible) *Node {
}

func (node *SpreadAssignment) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Expression) | SubtreeContainsES2018 | SubtreeContainsObjectRestOrSpread
return propagateSubtreeFacts(node.Expression) | SubtreeContainsESObjectRestOrSpread | SubtreeContainsObjectRestOrSpread
}

func IsSpreadAssignment(node *Node) bool {
Expand Down
16 changes: 8 additions & 8 deletions internal/ast/subtreefacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
SubtreeContainsNullishCoalescing
SubtreeContainsOptionalChaining
SubtreeContainsMissingCatchClauseVariable
SubtreeContainsESObjectRestOrSpread
SubtreeContainsESObjectRestOrSpread // subtree has a `...` somewhere inside it, never cleared
SubtreeContainsForAwaitOrAsyncGenerator
SubtreeContainsAnyAwait
SubtreeContainsExponentiationOperator
Expand All @@ -30,8 +30,8 @@ const (

SubtreeContainsLexicalThis
SubtreeContainsLexicalSuper
SubtreeContainsRest
SubtreeContainsObjectRestOrSpread
SubtreeContainsRestOrSpread // marker on any `...` - cleared on binding pattern exit
SubtreeContainsObjectRestOrSpread // marker on any `{...x}` - cleared on most scope exits
SubtreeContainsAwait
SubtreeContainsDynamicImport
SubtreeContainsClassFields
Expand Down Expand Up @@ -76,7 +76,7 @@ const (
SubtreeExclusionsVariableDeclarationList = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsParameter = SubtreeExclusionsNode
SubtreeExclusionsCatchClause = SubtreeExclusionsNode | SubtreeContainsObjectRestOrSpread
SubtreeExclusionsBindingPattern = SubtreeExclusionsNode | SubtreeContainsRest
SubtreeExclusionsBindingPattern = SubtreeExclusionsNode | SubtreeContainsRestOrSpread

// Masks
// - Additional bitmasks
Expand All @@ -94,15 +94,15 @@ func propagateEraseableSyntaxSubtreeFacts(child *TypeNode) SubtreeFacts {

func propagateObjectBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
facts := propagateSubtreeFacts(child)
if facts&SubtreeContainsRest != 0 {
facts &= ^SubtreeContainsRest
facts |= SubtreeContainsObjectRestOrSpread
if facts&SubtreeContainsRestOrSpread != 0 {
facts &= ^SubtreeContainsRestOrSpread
facts |= SubtreeContainsObjectRestOrSpread | SubtreeContainsESObjectRestOrSpread
}
return facts
}

func propagateBindingElementSubtreeFacts(child *BindingElementNode) SubtreeFacts {
return propagateSubtreeFacts(child) & ^SubtreeContainsRest
return propagateSubtreeFacts(child) & ^SubtreeContainsRestOrSpread
}

func propagateSubtreeFacts(child *Node) SubtreeFacts {
Expand Down
199 changes: 199 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3635,3 +3635,202 @@ func GetSemanticJsxChildren(children []*JsxChild) []*JsxChild {
}
})
}

func IsAssignmentPattern(node *Node) bool {
return node.Kind == KindArrayLiteralExpression || node.Kind == KindObjectLiteralExpression
}

func GetElementsOfBindingOrAssignmentPattern(name *Node) []*Node {
switch name.Kind {
case KindObjectBindingPattern, KindArrayBindingPattern:
// `a` in `{a}`
// `a` in `[a]`
return name.AsBindingPattern().Elements.Nodes
case KindArrayLiteralExpression:
// `a` in `[a]`
return name.AsArrayLiteralExpression().Elements.Nodes
case KindObjectLiteralExpression:
// `a` in `{a}`
return name.AsObjectLiteralExpression().Properties.Nodes
}
return nil
}

func IsDeclarationBindingElement(bindingElement *Node) bool {
switch bindingElement.Kind {
case KindVariableDeclaration, KindParameter, KindBindingElement:
return true
default:
return false
}
}

/**
* Gets the name of an BindingOrAssignmentElement.
*/
func GetTargetOfBindingOrAssignmentElement(bindingElement *Node) *Node {
if IsDeclarationBindingElement(bindingElement) {
// `a` in `let { a } = ...`
// `a` in `let { a = 1 } = ...`
// `b` in `let { a: b } = ...`
// `b` in `let { a: b = 1 } = ...`
// `a` in `let { ...a } = ...`
// `{b}` in `let { a: {b} } = ...`
// `{b}` in `let { a: {b} = 1 } = ...`
// `[b]` in `let { a: [b] } = ...`
// `[b]` in `let { a: [b] = 1 } = ...`
// `a` in `let [a] = ...`
// `a` in `let [a = 1] = ...`
// `a` in `let [...a] = ...`
// `{a}` in `let [{a}] = ...`
// `{a}` in `let [{a} = 1] = ...`
// `[a]` in `let [[a]] = ...`
// `[a]` in `let [[a] = 1] = ...`
return bindingElement.Name()
}

if IsObjectLiteralElement(bindingElement) {
switch bindingElement.Kind {
case KindPropertyAssignment:
// `b` in `({ a: b } = ...)`
// `b` in `({ a: b = 1 } = ...)`
// `{b}` in `({ a: {b} } = ...)`
// `{b}` in `({ a: {b} = 1 } = ...)`
// `[b]` in `({ a: [b] } = ...)`
// `[b]` in `({ a: [b] = 1 } = ...)`
// `b.c` in `({ a: b.c } = ...)`
// `b.c` in `({ a: b.c = 1 } = ...)`
// `b[0]` in `({ a: b[0] } = ...)`
// `b[0]` in `({ a: b[0] = 1 } = ...)`
return GetTargetOfBindingOrAssignmentElement(bindingElement.Initializer())
case KindShorthandPropertyAssignment:
// `a` in `({ a } = ...)`
// `a` in `({ a = 1 } = ...)`
return bindingElement.Name()
case KindSpreadAssignment:
// `a` in `({ ...a } = ...)`
return GetTargetOfBindingOrAssignmentElement(bindingElement.AsSpreadAssignment().Expression)
}

// no target
return nil
}

if IsAssignmentExpression(bindingElement /*excludeCompoundAssignment*/, true) {
// `a` in `[a = 1] = ...`
// `{a}` in `[{a} = 1] = ...`
// `[a]` in `[[a] = 1] = ...`
// `a.b` in `[a.b = 1] = ...`
// `a[0]` in `[a[0] = 1] = ...`
return GetTargetOfBindingOrAssignmentElement(bindingElement.AsBinaryExpression().Left)
}

if IsSpreadElement(bindingElement) {
// `a` in `[...a] = ...`
return GetTargetOfBindingOrAssignmentElement(bindingElement.AsSpreadElement().Expression)
}

// `a` in `[a] = ...`
// `{a}` in `[{a}] = ...`
// `[a]` in `[[a]] = ...`
// `a.b` in `[a.b] = ...`
// `a[0]` in `[a[0]] = ...`
return bindingElement
}

func TryGetPropertyNameOfBindingOrAssignmentElement(bindingElement *Node) *Node {
switch bindingElement.Kind {
case KindBindingElement:
// `a` in `let { a: b } = ...`
// `[a]` in `let { [a]: b } = ...`
// `"a"` in `let { "a": b } = ...`
// `1` in `let { 1: b } = ...`
if bindingElement.AsBindingElement().PropertyName != nil {
propertyName := bindingElement.AsBindingElement().PropertyName
// if ast.IsPrivateIdentifier(propertyName) {
// return Debug.failBadSyntaxKind(propertyName) // !!!
// }
if IsComputedPropertyName(propertyName) && IsStringOrNumericLiteralLike(propertyName.AsComputedPropertyName().Expression) {
return propertyName.AsComputedPropertyName().Expression
}
return propertyName
}
case KindPropertyAssignment:
// `a` in `({ a: b } = ...)`
// `[a]` in `({ [a]: b } = ...)`
// `"a"` in `({ "a": b } = ...)`
// `1` in `({ 1: b } = ...)`
if bindingElement.Name() != nil {
propertyName := bindingElement.Name()
// if ast.IsPrivateIdentifier(propertyName) {
// return Debug.failBadSyntaxKind(propertyName) // !!!
// }
if IsComputedPropertyName(propertyName) && IsStringOrNumericLiteralLike(propertyName.AsComputedPropertyName().Expression) {
return propertyName.AsComputedPropertyName().Expression
}
return propertyName
}
case KindSpreadAssignment:
// `a` in `({ ...a } = ...)`
// if ast.IsPrivateIdentifier(bindingElement.Name()) {
// return Debug.failBadSyntaxKind(bindingElement.Name()) // !!!
// }
return bindingElement.Name()
}

target := GetTargetOfBindingOrAssignmentElement(bindingElement)
if target != nil && IsPropertyName(target) {
return target
}
return nil
}

/**
* Walk an AssignmentPattern to determine if it contains object rest (`...`) syntax. We cannot rely on
* propagation of `TransformFlags.ContainsObjectRestOrSpread` since it isn't propagated by default in
* ObjectLiteralExpression and ArrayLiteralExpression since we do not know whether they belong to an
* AssignmentPattern at the time the nodes are parsed.
*/
func ContainsObjectRestOrSpread(node *Node) bool {
if node.SubtreeFacts()&SubtreeContainsObjectRestOrSpread != 0 {
return true
}
if node.SubtreeFacts()&SubtreeContainsESObjectRestOrSpread != 0 {
// check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c'
// will not be correctly interpreted by the rest/spread transformer
for _, element := range GetElementsOfBindingOrAssignmentPattern(node) {
target := GetTargetOfBindingOrAssignmentElement(element)
if target != nil && IsAssignmentPattern(target) {
if target.SubtreeFacts()&SubtreeContainsObjectRestOrSpread != 0 {
return true
}
if target.SubtreeFacts()&SubtreeContainsESObjectRestOrSpread != 0 {
if ContainsObjectRestOrSpread(target) {
return true
}
}
}
}
}
return false
}

func IsEmptyObjectLiteral(expression *Node) bool {
return expression.Kind == KindObjectLiteralExpression && len(expression.AsObjectLiteralExpression().Properties.Nodes) == 0
}

func IsEmptyArrayLiteral(expression *Node) bool {
return expression.Kind == KindArrayLiteralExpression && len(expression.AsArrayLiteralExpression().Elements.Nodes) == 0
}

func GetRestIndicatorOfBindingOrAssignmentElement(bindingElement *Node) *Node {
switch bindingElement.Kind {
case KindParameter:
return bindingElement.AsParameterDeclaration().DotDotDotToken
case KindBindingElement:
return bindingElement.AsBindingElement().DotDotDotToken
case KindSpreadElement, KindSpreadAssignment:
return bindingElement
}
return nil
}
Loading
Loading