Skip to content

Instantly share code, notes, and snippets.

@JLHwung
Created May 5, 2020 20:21
Show Gist options
  • Save JLHwung/6ec08a87e4da88874c50788c37d6fdf4 to your computer and use it in GitHub Desktop.
Save JLHwung/6ec08a87e4da88874c50788c37d6fdf4 to your computer and use it in GitHub Desktop.
A wavy-dot AST representation

Extend from the ChainingExpression approach.

Expressions

ChainingExpression

extend interface Chain <: Node {
    eventual: boolean
}

Examples

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": [],
}
@michaelfig
Copy link

Thanks for sketching this out. Looking forward to the decision on how Babel supports this syntax.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment