From 56500603da0563937ea907e7eba3f68a689fedf8 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Mon, 16 Mar 2020 21:17:11 -0400 Subject: [PATCH] Add stricter Optional Chain node validation (#11250) * Add stricter Optional Chain node validation Optional chains cannot start with `optional: false`. Somewhere in the `object`/`callee` tree, there must be an optional with `optional: true`. * Print current node's type when finding an error. --- .../src/definitions/experimental.js | 9 ++++-- packages/babel-types/src/definitions/utils.js | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index 27d4ca71deba..062f96660707 100644 --- a/packages/babel-types/src/definitions/experimental.js +++ b/packages/babel-types/src/definitions/experimental.js @@ -1,5 +1,6 @@ // @flow import defineType, { + assertOptionalChainStart, assertEach, assertNodeType, assertNodeOrValueType, @@ -105,7 +106,9 @@ defineType("OptionalMemberExpression", { default: false, }, optional: { - validate: assertValueType("boolean"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertValueType("boolean") + : chain(assertValueType("boolean"), assertOptionalChainStart()), }, }, }); @@ -151,7 +154,9 @@ defineType("OptionalCallExpression", { ), }, optional: { - validate: assertValueType("boolean"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertValueType("boolean") + : chain(assertValueType("boolean"), assertOptionalChainStart()), }, typeArguments: { validate: assertNodeType("TypeParameterInstantiation"), diff --git a/packages/babel-types/src/definitions/utils.js b/packages/babel-types/src/definitions/utils.js index cc3a347cca4a..6fc112be19a2 100644 --- a/packages/babel-types/src/definitions/utils.js +++ b/packages/babel-types/src/definitions/utils.js @@ -186,6 +186,34 @@ export function assertShape(shape: { [string]: FieldOptions }): Validator { return validate; } +export function assertOptionalChainStart(): Validator { + function validate(node) { + let current = node; + while (node) { + const { type } = current; + if (type === "OptionalCallExpression") { + if (current.optional) return; + current = current.callee; + continue; + } + + if (type === "OptionalMemberExpression") { + if (current.optional) return; + current = current.object; + continue; + } + + break; + } + + throw new TypeError( + `Non-optional ${node.type} must chain from an optional OptionalMemberExpression or OptionalCallExpression. Found chain from ${current?.type}`, + ); + } + + return validate; +} + export function chain(...fns: Array): Validator { function validate(...args) { for (const fn of fns) {