Extend from the ChainingExpression
approach.
extend interface Chain <: Node {
eventual: boolean
}
Examples: (`optional` is always `false` in the following chain elements)
// obj~.aaa~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj~.aaa.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj.aaa~.bbb
{
"type": "ChainingExpression",
"base": {
// For backward compatibility, non-eventual chains in the left of the first
// eventual chain are represented by the existing nodes.
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj.aaa.bbb
{
// For backward compatibility, non-eventual chains in the left of the first
// eventual chain are represented by the existing nodes.
"type": "MemberExpression",
"object": {
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
},
"property": { "type": "Identifier", "name": "bbb" },
}
// (obj~.aaa)~.bbb
{
// Parentheses disconnect chains. Therefore, short-circuit behavior doesn't
// propagate.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (obj~.aaa).bbb
{
// Parentheses disconnect chains.
"type": "MemberExpression",
"object": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"property": { "type": "Identifier", "name": "bbb" },
}
// func~.()~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// func~.().bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func~.())~.bbb
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"arguments": [],
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func~.()).bbb
{
// Parentheses disconnect chains.
"type": "MemberExpression",
"object": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"arguments": [],
}
],
},
"property": { "type": "Identifier", "name": "bbb" },
}
// obj~.aaa~.()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": true,
"arguments": [],
}
],
}
// obj~.aaa()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": false,
"arguments": [],
}
],
}
// (obj~.aaa)~.()
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "CallChain",
"eventual": true,
"arguments": [],
}
],
}
// (obj~.aaa)()
{
// Parentheses disconnect chains.
"type": "CallExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"arguments": [],
}
Examples of integration with optional chaining
// obj?~.aaa?~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj?~.aaa~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj~.aaa?~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj?~.aaa?.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": false,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj?~.aaa.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": false,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj~.aaa?.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "MemberChain",
"eventual": false,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// obj.aaa?~.bbb
{
"type": "ChainingExpression",
"base": {
// For backward compatibility, non-eventual chains in the left of the first
// eventual chain are represented by the existing nodes.
"type": "MemberExpression",
"object": { "type": "Identifier", "name": "obj" },
"property": { "type": "Identifier", "name": "aaa" },
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (obj?~.aaa)?~.bbb
{
// Parentheses disconnect chains. Therefore, short-circuit behavior doesn't
// propagate.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (obj?~.aaa)~.bbb
{
// Parentheses disconnect chains. Therefore, short-circuit behavior doesn't
// propagate.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (obj~.aaa)?~.bbb
{
// Parentheses disconnect chains. Therefore, short-circuit behavior doesn't
// propagate.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (obj?~.aaa).bbb
{
// Parentheses disconnect chains.
"type": "MemberExpression",
"object": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"property": { "type": "Identifier", "name": "bbb" },
}
// func?~.()?~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// func?~.()~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// func~.()?~.bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": false,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// func?~.().bbb
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
},
{
"type": "MemberChain",
"eventual": false,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func?~.())?~.bbb
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func?~.())~.bbb
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func~.())?~.bbb
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": false,
"arguments": [],
}
],
},
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "bbb" },
}
],
}
// (func?~.()).bbb
{
// Parentheses disconnect chains.
"type": "MemberExpression",
"object": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "func" },
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
},
"property": { "type": "Identifier", "name": "bbb" },
}
// obj?~.aaa?~.()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
}
// obj?~.aaa~.()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": true,
"optional": false,
"arguments": [],
}
],
}
// obj~.aaa?~.()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
}
// obj?~.aaa()
{
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
},
{
"type": "CallChain",
"eventual": false,
"arguments": [],
}
],
}
// (obj?~.aaa)?~.()
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
}
// (obj?~.aaa)~.()
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": false,
"arguments": [],
}
],
}
// (obj~.aaa)?~.()
{
// Parentheses disconnect chains.
"type": "ChainingExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": false,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"chain": [
{
"type": "CallChain",
"eventual": true,
"optional": true,
"arguments": [],
}
],
}
// (obj?~.aaa)()
{
// Parentheses disconnect chains.
"type": "CallExpression",
"base": {
"type": "ChainingExpression",
"base": { "type": "Identifier", "name": "obj" },
"chain": [
{
"type": "MemberChain",
"eventual": true,
"optional": true,
"computed": false,
"property": { "type": "Identifier", "name": "aaa" },
}
],
},
"arguments": [],
}
Thanks for sketching this out. Looking forward to the decision on how Babel supports this syntax.