diff --git a/packages/babel-types/src/definitions/experimental.js b/packages/babel-types/src/definitions/experimental.js index a77350105a5b..22f302a9eb5e 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, assertValueType, @@ -104,7 +105,9 @@ defineType("OptionalMemberExpression", { default: false, }, optional: { - validate: assertValueType("boolean"), + validate: !process.env.BABEL_TYPES_8_BREAKING + ? assertValueType("boolean") + : chain(assertValueType("boolean"), assertOptionalChainStart()), }, }, }); @@ -150,7 +153,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) {