interface OptionalMemberExpression <: Expression {
type: "OptionalMemberExpression";
object: Expression;
property: Expression;
computed: boolean;
optional: boolean;
}interface OptionalCallExpression <: Expression {
type: "OptionalCallExpression";
callee: Expression;
arguments: [ Expression | SpreadElement ];
optional: boolean;
}The Optional(Call|Member)Expression aligns to the OptionalChain production. When optional is true, the property/arguments must follow a ?. token. Any member/call in the optional chain is considered optional call or optional member. That said, if a node is Optional(Call|Member)Expression, there must exist a Optional(Call|Member)Expression[optional=true] to its left that starts this optional chain.
computed property is always false in the following examples. It can extend to computed properties w.l.o.g.
// obj?.aaa?.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": true
}// obj?.aaa.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": false
}bbb is a part of optional chain, so it is OptionalMemberExpression, but it does not follow a ?. token, so it has optional: false setting.
// obj.aaa?.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" }
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": true
}// obj.aaa.bbb
{
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" }
},
"property": { "type": "Identifier", "name": "bbb" }
}This proposal does not introduce changes for member expressions obj.aaa.bbb.
// (obj?.aaa)?.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": true
}Both (obj?.aaa)?.bbb and obj?.aaa?.bbb shares the same AST structures because they are equivalent. Just like (obj.aaa).bbb is same as obj.aaa.bbb.
// (obj?.aaa).bbb
{
"type": "MemberExpression",
"object": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" }
}However (obj?.aaa).bbb is not equivalent to obj?.aaa.bbb. Because .bbb is not a part of the optional chain, it is now a member expression.
// func?.()?.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalCallExpression",
"callee": { "type": "Identifier", "name": "func" },
"arguments": [],
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": true
}// func?.().bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalCallExpression",
"callee": { "type": "Identifier", "name": "func" },
"arguments": [],
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": false
}.bbb is a part of optional chain, so it is OptionalMemberExpression, but it does not follow a ?. token, so it has optional: false setting.
// (func?.())?.bbb
{
"type": "OptionalMemberExpression",
"object": {
"type": "OptionalCallExpression",
"callee": { "type": "Identifier", "name": "func" },
"arguments": [],
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" },
"optional": true
}Both (func?.())?.bbb and func?.()?.bbb shares the same AST structures because they are equivalent. Just like (func()).bbb is same as func().bbb.
// (func?.()).bbb
{
"type": "MemberExpression",
"object": {
"type": "OptionalCallExpression",
"callee": { "type": "Identifier", "name": "func" },
"arguments": [],
"optional": true
},
"property": { "type": "Identifier", "name": "bbb" }
}However (func?.()).bbb is not equivalent to func?.().bbb. Because .bbb is not a part of the optional chain, it is now a member expression.
// obj?.aaa?.()
{
"type": "OptionalCallExpression",
"callee": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"arguments": [],
"optional": true
}// obj?.aaa()
{
"type": "OptionalCallExpression",
"callee": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"arguments": [],
"optional": false
}() is a part of optional chain, so it is OptionalCallExpression, but it does not follow a ?. token, so it has optional: false setting.
// (obj?.aaa)?.()
{
"type": "OptionalCallExpression",
"callee": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"arguments": [],
"optional": true
}Both (obj?.aaa)?.() and obj?.aaa?.() shares the same AST structures because they are equivalent. Just like (obj.aaa)() is same as obj.aaa().
// (obj?.aaa)()
{
"type": "CallExpression",
"callee": {
"type": "OptionalMemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
"optional": true
},
"arguments": []
}However (obj?.aaa)() is not equivalent to obj?.aaa(). Because () is not a part of the optional chain, it is now a call expression.
Hey @JLHwung is this to be found in Babel already? i've been trying to grab a dedicated AST from the latest
@babel/standalonefor optional chains but only get already-transpiled conditional expressions. I need non-transpiled ASTs for optional chains for my code to be relevant, wondering whether that's on the stove somewhere…?