From 4d93baeea9d293b85a7aef564885a8ae3a49e2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 25 Sep 2023 15:07:44 +0900 Subject: [PATCH] Add support for optional chain in assignments (#15751) Co-authored-by: CanadaHonk <19228318+CanadaHonk@users.noreply.github.com> --- .../babel-helper-function-name/src/index.ts | 6 +- .../src/index.ts | 99 ++++++++++--------- packages/babel-helpers/src/helpers.ts | 6 ++ packages/babel-parser/data/schema.json | 17 ++++ .../src/parse-error/standard-errors.ts | 4 + packages/babel-parser/src/parser/lval.ts | 29 +++++- packages/babel-parser/src/plugin-utils.ts | 11 +++ packages/babel-parser/src/plugins/estree.ts | 7 ++ packages/babel-parser/src/typings.d.ts | 1 + .../output.json | 2 +- .../optional-chaining-assign/input.js | 1 + .../optional-chaining-assign/options.json | 3 + .../invalid-destruturing-arr/input.js | 1 + .../invalid-destruturing-arr/output.json | 52 ++++++++++ .../invalid-destruturing-obj/input.js | 1 + .../invalid-destruturing-obj/output.json | 68 +++++++++++++ .../invalid-fn-param-arrow/input.js | 1 + .../invalid-fn-param-arrow/output.json | 52 ++++++++++ .../invalid-fn-param-assign/input.js | 1 + .../invalid-fn-param-assign/options.json | 3 + .../invalid-fn-param/input.js | 1 + .../invalid-fn-param/options.json | 3 + .../invalid-for-await-of/input.js | 1 + .../invalid-for-await-of/output.json | 46 +++++++++ .../invalid-for-in/input.js | 1 + .../invalid-for-in/output.json | 45 +++++++++ .../invalid-for-of/input.js | 1 + .../invalid-for-of/output.json | 46 +++++++++ .../invalid-inc-postfix/input.js | 1 + .../invalid-inc-postfix/output.json | 42 ++++++++ .../invalid-inc-prefix/input.js | 1 + .../invalid-inc-prefix/output.json | 42 ++++++++ .../input.js | 1 + .../options.json | 3 + .../output.json | 56 +++++++++++ .../invalid-parenthesized-in-destr/input.js | 1 + .../output.json | 56 +++++++++++ .../optional-chaining-assign/options.json | 3 + .../valid-lhs-eq/input.js | 1 + .../valid-lhs-eq/output.json | 54 ++++++++++ .../valid-lhs-plus-eq/input.js | 1 + .../valid-lhs-plus-eq/output.json | 54 ++++++++++ .../input.js | 1 + .../options.json | 3 + .../output.json | 47 +++++++++ .../valid-parenthesized/input.js | 1 + .../valid-parenthesized/output.json | 47 +++++++++ .../babel-parser/typings/babel-parser.d.ts | 1 + .../src/util.ts | 8 +- .../.npmignore | 3 + .../README.md | 19 ++++ .../package.json | 57 +++++++++++ .../src/index.ts | 53 ++++++++++ .../assumption-noDocumentAll/basic/input.js | 1 + .../assumption-noDocumentAll/basic/output.js | 2 + .../assumption-noDocumentAll/call/input.js | 1 + .../assumption-noDocumentAll/call/output.js | 2 + .../assumption-noDocumentAll/options.json | 6 ++ .../parenthesized/exec.js | 13 +++ .../parenthesized/input.js | 2 + .../parenthesized/output.js | 3 + .../private-field-transformed/input.js | 12 +++ .../private-field-transformed/options.json | 6 ++ .../private-field-transformed/output.js | 18 ++++ .../private-field/input.js | 12 +++ .../private-field/output.js | 12 +++ .../assumption-pureGetters/call/input.js | 1 + .../assumption-pureGetters/call/output.js | 2 + .../assumption-pureGetters/options.json | 6 ++ .../test/fixtures/general/basic/exec.js | 38 +++++++ .../test/fixtures/general/basic/input.js | 1 + .../test/fixtures/general/basic/output.js | 2 + .../test/fixtures/general/call/input.js | 1 + .../test/fixtures/general/call/output.js | 2 + .../fixtures/general/parenthesized/exec.js | 13 +++ .../fixtures/general/parenthesized/input.js | 2 + .../fixtures/general/parenthesized/output.js | 3 + .../test/fixtures/general/plus-eq/exec.js | 6 ++ .../test/fixtures/general/plus-eq/input.js | 1 + .../test/fixtures/general/plus-eq/output.js | 2 + .../input.js | 12 +++ .../options.json | 9 ++ .../output.js | 18 ++++ .../private-field-transformed/input.js | 12 +++ .../private-field-transformed/options.json | 6 ++ .../private-field-transformed/output.js | 18 ++++ .../fixtures/general/private-field/input.js | 12 +++ .../fixtures/general/private-field/output.js | 12 +++ .../fixtures/general/value-ignored/input.js | 3 + .../fixtures/general/value-ignored/output.js | 4 + .../invalid/optional-call-lhs/input.js | 1 + .../invalid/optional-call-lhs/options.json | 3 + .../invalid/unary-inc-postfix/input.js | 1 + .../invalid/unary-inc-postfix/options.json | 3 + .../invalid/unary-inc-prefix/input.js | 1 + .../invalid/unary-inc-prefix/options.json | 3 + .../test/fixtures/options.json | 3 + .../test/index.js | 3 + .../test/package.json | 1 + .../.npmignore | 3 + .../README.md | 19 ++++ .../package.json | 56 +++++++++++ .../src/index.ts | 28 ++++++ .../src/index.ts | 2 +- .../src/util.ts | 6 +- .../src/index.ts | 4 +- .../lhs-assignment-read-and-update/input.js | 1 - .../options.json | 3 - .../fixtures/general/lhs-assignment/input.js | 1 - .../general/lhs-assignment/options.json | 3 - .../test/fixtures/general/lhs-update/input.js | 1 - .../fixtures/general/lhs-update/options.json | 3 - packages/babel-runtime-corejs2/package.json | 9 ++ packages/babel-runtime-corejs3/package.json | 9 ++ packages/babel-runtime/package.json | 9 ++ packages/babel-standalone/package.json | 2 + .../scripts/pluginConfig.json | 4 +- .../babel-standalone/src/generated/plugins.ts | 12 ++- .../babel-standalone/src/preset-stage-1.ts | 5 + .../test/preset-stage-1.test.js | 9 ++ .../src/ast-types/generated/index.ts | 2 +- .../src/builders/generated/index.ts | 2 +- packages/babel-types/src/definitions/core.ts | 3 +- tsconfig.json | 8 ++ yarn.lock | 29 ++++++ 125 files changed, 1513 insertions(+), 80 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/output.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/.npmignore create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/README.md create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/package.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/src/index.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/index.js create mode 100644 packages/babel-plugin-proposal-optional-chaining-assign/test/package.json create mode 100644 packages/babel-plugin-syntax-optional-chaining-assign/.npmignore create mode 100644 packages/babel-plugin-syntax-optional-chaining-assign/README.md create mode 100644 packages/babel-plugin-syntax-optional-chaining-assign/package.json create mode 100644 packages/babel-plugin-syntax-optional-chaining-assign/src/index.ts delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/input.js delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/options.json delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/input.js delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/options.json delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/input.js delete mode 100644 packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/options.json diff --git a/packages/babel-helper-function-name/src/index.ts b/packages/babel-helper-function-name/src/index.ts index 48be2c9bb0b5..176d97e0b9c0 100644 --- a/packages/babel-helper-function-name/src/index.ts +++ b/packages/babel-helper-function-name/src/index.ts @@ -224,7 +224,11 @@ export default function ( node: N; parent?: NodePath["parent"]; scope: Scope; - id?: t.LVal | t.StringLiteral | t.NumericLiteral | t.BigIntLiteral; + id?: + | t.AssignmentExpression["left"] + | t.StringLiteral + | t.NumericLiteral + | t.BigIntLiteral; }, localBinding = false, supportUnicodeId = false, diff --git a/packages/babel-helper-member-expression-to-functions/src/index.ts b/packages/babel-helper-member-expression-to-functions/src/index.ts index 627a80dcf19f..ff1c79e35094 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.ts +++ b/packages/babel-helper-member-expression-to-functions/src/index.ts @@ -169,12 +169,12 @@ const handle = { const willEndPathCastToBoolean = willPathCastToBoolean(endPath); const rootParentPath = endPath.parentPath; - if ( - rootParentPath.isUpdateExpression({ argument: node }) || - rootParentPath.isAssignmentExpression({ left: node }) - ) { - throw member.buildCodeFrameError(`can't handle assignment`); + if (rootParentPath.isUpdateExpression({ argument: node })) { + throw member.buildCodeFrameError(`can't handle update expression`); } + const isAssignment = rootParentPath.isAssignmentExpression({ + left: endPath.node, + }); const isDeleteOperation = rootParentPath.isUnaryExpression({ operator: "delete", }); @@ -252,6 +252,9 @@ const handle = { parentPath.isUnaryExpression({ operator: "delete" }) ) { parentPath.replaceWith(this.delete(member)); + } else if (parentPath.isAssignmentExpression()) { + // `a?.#b = c` to `(a == null ? void 0 : a.#b = c)` + handleAssignment(this, member, parentPath); } else { member.replaceWith(this.get(member)); } @@ -295,7 +298,7 @@ const handle = { } let replacementPath: NodePath = endPath; - if (isDeleteOperation) { + if (isDeleteOperation || isAssignment) { replacementPath = endParentPath; regular = endParentPath.node; } @@ -432,44 +435,7 @@ const handle = { // MEMBER += VALUE -> _set(MEMBER, _get(MEMBER) + VALUE) // MEMBER ??= VALUE -> _get(MEMBER) ?? _set(MEMBER, VALUE) if (parentPath.isAssignmentExpression({ left: node })) { - if (this.simpleSet) { - member.replaceWith(this.simpleSet(member)); - return; - } - - const { operator, right: value } = parentPath.node; - - if (operator === "=") { - parentPath.replaceWith(this.set(member, value)); - } else { - const operatorTrunc = operator.slice(0, -1); - if (LOGICAL_OPERATORS.includes(operatorTrunc)) { - // Give the state handler a chance to memoise the member, since we'll - // reference it twice. The first access (the get) should do the memo - // assignment. - this.memoise(member, 1); - parentPath.replaceWith( - logicalExpression( - operatorTrunc as t.LogicalExpression["operator"], - this.get(member), - this.set(member, value), - ), - ); - } else { - // Here, the second access (the set) is evaluated first. - this.memoise(member, 2); - parentPath.replaceWith( - this.set( - member, - binaryExpression( - operatorTrunc as t.BinaryExpression["operator"], - this.get(member), - value, - ), - ), - ); - } - } + handleAssignment(this, member, parentPath); return; } @@ -549,6 +515,51 @@ const handle = { }, }; +function handleAssignment( + state: HandlerState, + member: NodePath, + parentPath: NodePath, +) { + if (state.simpleSet) { + member.replaceWith(state.simpleSet(member)); + return; + } + + const { operator, right: value } = parentPath.node; + + if (operator === "=") { + parentPath.replaceWith(state.set(member, value)); + } else { + const operatorTrunc = operator.slice(0, -1); + if (LOGICAL_OPERATORS.includes(operatorTrunc)) { + // Give the state handler a chance to memoise the member, since we'll + // reference it twice. The first access (the get) should do the memo + // assignment. + state.memoise(member, 1); + parentPath.replaceWith( + logicalExpression( + operatorTrunc as t.LogicalExpression["operator"], + state.get(member), + state.set(member, value), + ), + ); + } else { + // Here, the second access (the set) is evaluated first. + state.memoise(member, 2); + parentPath.replaceWith( + state.set( + member, + binaryExpression( + operatorTrunc as t.BinaryExpression["operator"], + state.get(member), + value, + ), + ), + ); + } + } +} + export interface Handler { memoise?( this: HandlerState & State, diff --git a/packages/babel-helpers/src/helpers.ts b/packages/babel-helpers/src/helpers.ts index 2caee39b637a..df6b2393cb03 100644 --- a/packages/babel-helpers/src/helpers.ts +++ b/packages/babel-helpers/src/helpers.ts @@ -1884,3 +1884,9 @@ helpers.identity = helper("7.17.0")` return x; } `; + +helpers.nullishReceiverError = helper("7.22.6")` + export default function _nullishReceiverError(r) { + throw new TypeError("Cannot set property of null or undefined."); + } +`; diff --git a/packages/babel-parser/data/schema.json b/packages/babel-parser/data/schema.json index 370b14e0ac54..323f6e931a49 100644 --- a/packages/babel-parser/data/schema.json +++ b/packages/babel-parser/data/schema.json @@ -145,6 +145,23 @@ "additionalItems": false, "type": "array" }, + { + "items": [ + { + "type": "string", + "enum": ["optionalChainingAssign"] + }, + { + "type": "object", + "properties": { + "version": { + "type": "string", + "enum": ["2023-07"] + } + } + } + ] + }, { "enum": [ "asyncDoExpressions", diff --git a/packages/babel-parser/src/parse-error/standard-errors.ts b/packages/babel-parser/src/parse-error/standard-errors.ts index 040ff0077507..68ca19392e29 100644 --- a/packages/babel-parser/src/parse-error/standard-errors.ts +++ b/packages/babel-parser/src/parse-error/standard-errors.ts @@ -147,6 +147,10 @@ export default { `Invalid left-hand side in ${toNodeDescription(ancestor)}.`, InvalidLhsBinding: ({ ancestor }: { ancestor: LValAncestor }) => `Binding invalid left-hand side in ${toNodeDescription(ancestor)}.`, + InvalidLhsOptionalChaining: ({ ancestor }: { ancestor: LValAncestor }) => + `Invalid optional chaining in the left-hand side of ${toNodeDescription( + ancestor, + )}.`, InvalidNumber: "Invalid number.", InvalidOrMissingExponent: "Floating-point numbers require a valid exponent after the 'e'.", diff --git a/packages/babel-parser/src/parser/lval.ts b/packages/babel-parser/src/parser/lval.ts index 576cec017ed8..f347dff5c483 100644 --- a/packages/babel-parser/src/parser/lval.ts +++ b/packages/babel-parser/src/parser/lval.ts @@ -113,7 +113,10 @@ export default abstract class LValParser extends NodeUtils { Errors.InvalidParenthesizedAssignment, { at: node }, ); - } else if (parenthesized.type !== "MemberExpression") { + } else if ( + parenthesized.type !== "MemberExpression" && + !this.isOptionalMemberExpression(parenthesized) + ) { // A parenthesized member expression can be in LHS but not in pattern. // If the LHS is later interpreted as a pattern, `checkLVal` will throw for member expression binding // i.e. `([(a.b) = []] = []) => {}` @@ -552,6 +555,11 @@ export default abstract class LValParser extends NodeUtils { ); } + // Overridden by the estree plugin + isOptionalMemberExpression(expression: Node) { + return expression.type === "OptionalMemberExpression"; + } + /** * Verify that a target expression is an lval (something that can be assigned to). * @@ -600,7 +608,20 @@ export default abstract class LValParser extends NodeUtils { // toAssignable already reported this error with a nicer message. if (this.isObjectMethod(expression)) return; - if (type === "MemberExpression") { + const isOptionalMemberExpression = + this.isOptionalMemberExpression(expression); + + if (isOptionalMemberExpression || type === "MemberExpression") { + if (isOptionalMemberExpression) { + this.expectPlugin("optionalChainingAssign", expression.loc.start); + if (ancestor.type !== "AssignmentExpression") { + this.raise(Errors.InvalidLhsOptionalChaining, { + at: expression, + ancestor, + }); + } + } + if (binding !== BindingFlag.TYPE_NONE) { this.raise(Errors.InvalidPropertyBindingPattern, { at: expression }); } @@ -649,9 +670,7 @@ export default abstract class LValParser extends NodeUtils { ? validity : [validity, type === "ParenthesizedExpression"]; const nextAncestor = - type === "ArrayPattern" || - type === "ObjectPattern" || - type === "ParenthesizedExpression" + type === "ArrayPattern" || type === "ObjectPattern" ? ({ type } as const) : ancestor; diff --git a/packages/babel-parser/src/plugin-utils.ts b/packages/babel-parser/src/plugin-utils.ts index 29833e82b24d..d8ad0050b133 100644 --- a/packages/babel-parser/src/plugin-utils.ts +++ b/packages/babel-parser/src/plugin-utils.ts @@ -232,6 +232,17 @@ export function validatePlugins(plugins: PluginList) { error.missingPlugins = "doExpressions"; throw error; } + + if ( + hasPlugin(plugins, "optionalChainingAssign") && + getPluginOption(plugins, "optionalChainingAssign", "version") !== "2023-07" + ) { + throw new Error( + "The 'optionalChainingAssign' plugin requires a 'version' option," + + " representing the last proposal update. Currently, the" + + " only supported value is '2023-07'.", + ); + } } // These plugins are defined using a mixin which extends the parser class. diff --git a/packages/babel-parser/src/plugins/estree.ts b/packages/babel-parser/src/plugins/estree.ts index e72d2021ab4d..c57dd03a16bb 100644 --- a/packages/babel-parser/src/plugins/estree.ts +++ b/packages/babel-parser/src/plugins/estree.ts @@ -535,6 +535,13 @@ export default (superClass: typeof Parser) => return node; } + isOptionalMemberExpression(node: N.Node) { + if (node.type === "ChainExpression") { + return node.expression.type === "MemberExpression"; + } + return super.isOptionalMemberExpression(node); + } + hasPropertyAsPrivateName(node: N.Node): boolean { if (node.type === "ChainExpression") { node = node.expression; diff --git a/packages/babel-parser/src/typings.d.ts b/packages/babel-parser/src/typings.d.ts index dbbaf4f76163..a6dba38e633f 100644 --- a/packages/babel-parser/src/typings.d.ts +++ b/packages/babel-parser/src/typings.d.ts @@ -47,6 +47,7 @@ export type ParserPluginWithOptions = | ["importAttributes", { deprecatedAssertSyntax: boolean }] // @deprecated | ["moduleAttributes", { version: "may-2020" }] + | ["optionalChainingAssign", { version: "2023-07" }] | ["pipelineOperator", PipelineOperatorPluginOptions] | ["recordAndTuple", RecordAndTuplePluginOptions] | ["flow", FlowPluginOptions] diff --git a/packages/babel-parser/test/fixtures/core/create-parenthesized-expressions/invalid-parenthesized-left-hand-side/output.json b/packages/babel-parser/test/fixtures/core/create-parenthesized-expressions/invalid-parenthesized-left-hand-side/output.json index 46b329a6f922..e4807ef168a4 100644 --- a/packages/babel-parser/test/fixtures/core/create-parenthesized-expressions/invalid-parenthesized-left-hand-side/output.json +++ b/packages/babel-parser/test/fixtures/core/create-parenthesized-expressions/invalid-parenthesized-left-hand-side/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":9,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":9,"index":9}}, "errors": [ - "SyntaxError: Invalid left-hand side in parenthesized expression. (1:1)" + "SyntaxError: Invalid left-hand side in assignment expression. (1:1)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/input.js b/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/input.js new file mode 100644 index 000000000000..4e1c3881fc2e --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/input.js @@ -0,0 +1 @@ +a?.b = c; diff --git a/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/options.json b/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/options.json new file mode 100644 index 000000000000..96ef37b604ac --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/_no-plugin/optional-chaining-assign/options.json @@ -0,0 +1,3 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin: \"optionalChainingAssign\". (1:0)" +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/input.js new file mode 100644 index 000000000000..d2d56ccd9f79 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/input.js @@ -0,0 +1 @@ +[a?.b] = []; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/output.json new file mode 100644 index 000000000000..feca44862cf6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-arr/output.json @@ -0,0 +1,52 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of array destructuring pattern. (1:1)" + ], + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "operator": "=", + "left": { + "type": "ArrayPattern", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "elements": [ + { + "type": "OptionalMemberExpression", + "start":1,"end":5,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":5,"index":5}}, + "object": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + ] + }, + "right": { + "type": "ArrayExpression", + "start":9,"end":11,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":11,"index":11}}, + "elements": [] + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/input.js new file mode 100644 index 000000000000..818ba60fe742 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/input.js @@ -0,0 +1 @@ +({ prop: a?.b } = {}); diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/output.json new file mode 100644 index 000000000000..3a1705efb620 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-destruturing-obj/output.json @@ -0,0 +1,68 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of object destructuring pattern. (1:9)" + ], + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "expression": { + "type": "AssignmentExpression", + "start":1,"end":20,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":20,"index":20}}, + "operator": "=", + "left": { + "type": "ObjectPattern", + "start":1,"end":15,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":15,"index":15}}, + "properties": [ + { + "type": "ObjectProperty", + "start":3,"end":13,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":13,"index":13}}, + "method": false, + "key": { + "type": "Identifier", + "start":3,"end":7,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":7,"index":7},"identifierName":"prop"}, + "name": "prop" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "OptionalMemberExpression", + "start":9,"end":13,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":13,"index":13}}, + "object": { + "type": "Identifier", + "start":9,"end":10,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":10,"index":10},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":13,"index":13},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + } + ] + }, + "right": { + "type": "ObjectExpression", + "start":18,"end":20,"loc":{"start":{"line":1,"column":18,"index":18},"end":{"line":1,"column":20,"index":20}}, + "properties": [] + }, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/input.js new file mode 100644 index 000000000000..21cf5338f838 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/input.js @@ -0,0 +1 @@ +(a?.b) => {} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/output.json new file mode 100644 index 000000000000..40dbc9b8b77c --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-arrow/output.json @@ -0,0 +1,52 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of function parameter list. (1:1)", + "SyntaxError: Binding member expression. (1:1)" + ], + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "expression": { + "type": "ArrowFunctionExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "OptionalMemberExpression", + "start":1,"end":5,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":5,"index":5}}, + "object": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + ], + "body": { + "type": "BlockStatement", + "start":10,"end":12,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":12,"index":12}}, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/input.js new file mode 100644 index 000000000000..c8085ff71aa3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/input.js @@ -0,0 +1 @@ +function f(a?.b = c) {} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/options.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/options.json new file mode 100644 index 000000000000..99a2bc92f2c0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param-assign/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token, expected \",\" (1:12)" +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/input.js new file mode 100644 index 000000000000..1db702ecf65a --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/input.js @@ -0,0 +1 @@ +function f(a?.b) {} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/options.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/options.json new file mode 100644 index 000000000000..99a2bc92f2c0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-fn-param/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token, expected \",\" (1:12)" +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/input.js new file mode 100644 index 000000000000..ba964a0edb9f --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/input.js @@ -0,0 +1 @@ +for (a?.b of x); diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/output.json new file mode 100644 index 000000000000..28d8b1bc2470 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-await-of/output.json @@ -0,0 +1,46 @@ +{ + "type": "File", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of for-of statement. (1:5)" + ], + "program": { + "type": "Program", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ForOfStatement", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "await": false, + "left": { + "type": "OptionalMemberExpression", + "start":5,"end":9,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":9,"index":9}}, + "object": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":8,"end":9,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":9,"index":9},"identifierName":"b"}, + "name": "b" + }, + "optional": true + }, + "right": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"}, + "name": "x" + }, + "body": { + "type": "EmptyStatement", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16}} + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/input.js new file mode 100644 index 000000000000..57cc56518df0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/input.js @@ -0,0 +1 @@ +for (a?.b in x); diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/output.json new file mode 100644 index 000000000000..7f0ddce6922e --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-in/output.json @@ -0,0 +1,45 @@ +{ + "type": "File", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of for-in statement. (1:5)" + ], + "program": { + "type": "Program", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ForInStatement", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "left": { + "type": "OptionalMemberExpression", + "start":5,"end":9,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":9,"index":9}}, + "object": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":8,"end":9,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":9,"index":9},"identifierName":"b"}, + "name": "b" + }, + "optional": true + }, + "right": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"}, + "name": "x" + }, + "body": { + "type": "EmptyStatement", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16}} + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/input.js new file mode 100644 index 000000000000..ba964a0edb9f --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/input.js @@ -0,0 +1 @@ +for (a?.b of x); diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/output.json new file mode 100644 index 000000000000..28d8b1bc2470 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-for-of/output.json @@ -0,0 +1,46 @@ +{ + "type": "File", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of for-of statement. (1:5)" + ], + "program": { + "type": "Program", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ForOfStatement", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "await": false, + "left": { + "type": "OptionalMemberExpression", + "start":5,"end":9,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":9,"index":9}}, + "object": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":8,"end":9,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":9,"index":9},"identifierName":"b"}, + "name": "b" + }, + "optional": true + }, + "right": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"}, + "name": "x" + }, + "body": { + "type": "EmptyStatement", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":16,"index":16}} + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/input.js new file mode 100644 index 000000000000..9bbcb40bf476 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/input.js @@ -0,0 +1 @@ +a?.b++; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/output.json new file mode 100644 index 000000000000..b77f0daec67f --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-postfix/output.json @@ -0,0 +1,42 @@ +{ + "type": "File", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of postfix operation. (1:0)" + ], + "program": { + "type": "Program", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "expression": { + "type": "UpdateExpression", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "operator": "++", + "prefix": false, + "argument": { + "type": "OptionalMemberExpression", + "start":0,"end":4,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":4,"index":4}}, + "object": { + "type": "Identifier", + "start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":3,"end":4,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":4,"index":4},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/input.js new file mode 100644 index 000000000000..72c17bb988f6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/input.js @@ -0,0 +1 @@ +++a?.b; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/output.json new file mode 100644 index 000000000000..d48e56590f94 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-inc-prefix/output.json @@ -0,0 +1,42 @@ +{ + "type": "File", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of prefix operation. (1:2)" + ], + "program": { + "type": "Program", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "expression": { + "type": "UpdateExpression", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "operator": "++", + "prefix": true, + "argument": { + "type": "OptionalMemberExpression", + "start":2,"end":6,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":6,"index":6}}, + "object": { + "type": "Identifier", + "start":2,"end":3,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":3,"index":3},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/input.js new file mode 100644 index 000000000000..596b663d0768 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/input.js @@ -0,0 +1 @@ +[(a?.b)] = c; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/options.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/options.json new file mode 100644 index 000000000000..0861962d889d --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/options.json @@ -0,0 +1,3 @@ +{ + "createParenthesizedExpressions": true +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/output.json new file mode 100644 index 000000000000..50be7690e738 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr-createParenthesizedExpressions copy/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of array destructuring pattern. (1:2)" + ], + "program": { + "type": "Program", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "operator": "=", + "left": { + "type": "ArrayPattern", + "start":0,"end":8,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":8,"index":8}}, + "elements": [ + { + "type": "ParenthesizedExpression", + "start":1,"end":7,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":7,"index":7}}, + "expression": { + "type": "OptionalMemberExpression", + "start":2,"end":6,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":6,"index":6}}, + "object": { + "type": "Identifier", + "start":2,"end":3,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":3,"index":3},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + } + ] + }, + "right": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"c"}, + "name": "c" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/input.js new file mode 100644 index 000000000000..596b663d0768 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/input.js @@ -0,0 +1 @@ +[(a?.b)] = c; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/output.json new file mode 100644 index 000000000000..7c885b3c8e70 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/invalid-parenthesized-in-destr/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "errors": [ + "SyntaxError: Invalid optional chaining in the left-hand side of array destructuring pattern. (1:2)" + ], + "program": { + "type": "Program", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "operator": "=", + "left": { + "type": "ArrayPattern", + "start":0,"end":8,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":8,"index":8}}, + "elements": [ + { + "type": "OptionalMemberExpression", + "start":2,"end":6,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":6,"index":6}}, + "object": { + "type": "Identifier", + "start":2,"end":3,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":3,"index":3},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"b"}, + "name": "b" + }, + "optional": true, + "extra": { + "parenthesized": true, + "parenStart": 1 + } + } + ] + }, + "right": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"c"}, + "name": "c" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/options.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/options.json new file mode 100644 index 000000000000..7ab304501e3d --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["optionalChainingAssign", { "version": "2023-07" }]] +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/input.js new file mode 100644 index 000000000000..b709b1cd657e --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/input.js @@ -0,0 +1 @@ +a?.b.c = d; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/output.json new file mode 100644 index 000000000000..91f9fb48b407 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-eq/output.json @@ -0,0 +1,54 @@ +{ + "type": "File", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "program": { + "type": "Program", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "operator": "=", + "left": { + "type": "OptionalMemberExpression", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "object": { + "type": "OptionalMemberExpression", + "start":0,"end":4,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":4,"index":4}}, + "object": { + "type": "Identifier", + "start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":3,"end":4,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":4,"index":4},"identifierName":"b"}, + "name": "b" + }, + "optional": true + }, + "computed": false, + "property": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"c"}, + "name": "c" + }, + "optional": false + }, + "right": { + "type": "Identifier", + "start":9,"end":10,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":10,"index":10},"identifierName":"d"}, + "name": "d" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/input.js new file mode 100644 index 000000000000..decb2e9d6fb0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/input.js @@ -0,0 +1 @@ +a?.b.c += d; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/output.json new file mode 100644 index 000000000000..46037bd91383 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-lhs-plus-eq/output.json @@ -0,0 +1,54 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "operator": "+=", + "left": { + "type": "OptionalMemberExpression", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "object": { + "type": "OptionalMemberExpression", + "start":0,"end":4,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":4,"index":4}}, + "object": { + "type": "Identifier", + "start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":3,"end":4,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":4,"index":4},"identifierName":"b"}, + "name": "b" + }, + "optional": true + }, + "computed": false, + "property": { + "type": "Identifier", + "start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"c"}, + "name": "c" + }, + "optional": false + }, + "right": { + "type": "Identifier", + "start":10,"end":11,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":11,"index":11},"identifierName":"d"}, + "name": "d" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/input.js new file mode 100644 index 000000000000..5d060e412e48 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/input.js @@ -0,0 +1 @@ +(a?.b) = c; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/options.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/options.json new file mode 100644 index 000000000000..0861962d889d --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/options.json @@ -0,0 +1,3 @@ +{ + "createParenthesizedExpressions": true +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/output.json new file mode 100644 index 000000000000..dab4c013174d --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized-createParenthesizedExpressions/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "program": { + "type": "Program", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "operator": "=", + "left": { + "type": "ParenthesizedExpression", + "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, + "expression": { + "type": "OptionalMemberExpression", + "start":1,"end":5,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":5,"index":5}}, + "object": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5},"identifierName":"b"}, + "name": "b" + }, + "optional": true + } + }, + "right": { + "type": "Identifier", + "start":9,"end":10,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":10,"index":10},"identifierName":"c"}, + "name": "c" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/input.js b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/input.js new file mode 100644 index 000000000000..5d060e412e48 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/input.js @@ -0,0 +1 @@ +(a?.b) = c; diff --git a/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/output.json b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/output.json new file mode 100644 index 000000000000..f4e0cb4309c4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/optional-chaining-assign/valid-parenthesized/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "program": { + "type": "Program", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":11,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":11,"index":11}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "operator": "=", + "left": { + "type": "OptionalMemberExpression", + "start":1,"end":5,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":5,"index":5}}, + "object": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5},"identifierName":"b"}, + "name": "b" + }, + "optional": true, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + }, + "right": { + "type": "Identifier", + "start":9,"end":10,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":10,"index":10},"identifierName":"c"}, + "name": "c" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/typings/babel-parser.d.ts b/packages/babel-parser/typings/babel-parser.d.ts index a74b1bc46da8..ddf32d38cc15 100644 --- a/packages/babel-parser/typings/babel-parser.d.ts +++ b/packages/babel-parser/typings/babel-parser.d.ts @@ -51,6 +51,7 @@ type ParserPluginWithOptions = | ["importAttributes", { deprecatedAssertSyntax: boolean }] // @deprecated | ["moduleAttributes", { version: "may-2020" }] + | ["optionalChainingAssign", { version: "2023-07" }] | ["pipelineOperator", PipelineOperatorPluginOptions] | ["recordAndTuple", RecordAndTuplePluginOptions] | ["flow", FlowPluginOptions] diff --git a/packages/babel-plugin-proposal-destructuring-private/src/util.ts b/packages/babel-plugin-proposal-destructuring-private/src/util.ts index f674c08a01f2..c5eb72f8ac90 100644 --- a/packages/babel-plugin-proposal-destructuring-private/src/util.ts +++ b/packages/babel-plugin-proposal-destructuring-private/src/util.ts @@ -151,7 +151,7 @@ function buildAssignmentsFromPatternList( } type StackItem = { - node: t.LVal | t.ObjectProperty | null; + node: t.AssignmentExpression["left"] | t.ObjectProperty | null; index: number; depth: number; }; @@ -169,9 +169,9 @@ type StackItem = { * @param visitor */ export function* traversePattern( - root: t.LVal, + root: t.AssignmentExpression["left"], visitor: ( - node: t.LVal | t.ObjectProperty, + node: t.AssignmentExpression["left"] | t.ObjectProperty, index: number, depth: number, ) => Generator, @@ -221,7 +221,7 @@ export function* traversePattern( } } -export function hasPrivateKeys(pattern: t.LVal) { +export function hasPrivateKeys(pattern: t.AssignmentExpression["left"]) { let result = false; traversePattern(pattern, function* (node) { if (isObjectProperty(node) && isPrivateName(node.key)) { diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/.npmignore b/packages/babel-plugin-proposal-optional-chaining-assign/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/README.md b/packages/babel-plugin-proposal-optional-chaining-assign/README.md new file mode 100644 index 000000000000..e0b004ad6c85 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/README.md @@ -0,0 +1,19 @@ +# @babel/plugin-proposal-optional-chaining-assign + +> Transform optional chaining on the left-hand side of assignment expressions + +See our website [@babel/plugin-proposal-optional-chaining-assign](https://babeljs.io/docs/babel-plugin-proposal-optional-chaining-assign) for more information. + +## Install + +Using npm: + +```sh +npm install --save-dev @babel/plugin-proposal-optional-chaining-assign +``` + +or using yarn: + +```sh +yarn add @babel/plugin-proposal-optional-chaining-assign --dev +``` diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/package.json b/packages/babel-plugin-proposal-optional-chaining-assign/package.json new file mode 100644 index 000000000000..dec8e5e0c459 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/package.json @@ -0,0 +1,57 @@ +{ + "name": "@babel/plugin-proposal-optional-chaining-assign", + "version": "7.22.5", + "description": "Transform optional chaining on the left-hand side of assignment expressions", + "repository": { + "type": "git", + "url": "https://github.com/babel/babel.git", + "directory": "packages/babel-plugin-proposal-optional-chaining-assign" + }, + "homepage": "https://babel.dev/docs/en/next/babel-plugin-proposal-optional-chaining-assign", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "main": "./lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": { + "@babel/helper-plugin-utils": "workspace:^", + "@babel/helper-skip-transparent-expression-wrappers": "workspace:^", + "@babel/plugin-syntax-optional-chaining-assign": "workspace:^", + "@babel/plugin-transform-optional-chaining": "workspace:^" + }, + "peerDependencies": { + "@babel/core": "^7.22.5" + }, + "devDependencies": { + "@babel/core": "workspace:^", + "@babel/helper-plugin-test-runner": "workspace:^" + }, + "engines": { + "node": ">=6.9.0" + }, + "author": "The Babel Team (https://babel.dev/team)", + "conditions": { + "BABEL_8_BREAKING": [ + { + "engines": { + "node": "^16.20.0 || ^18.16.0 || >=20.0.0" + } + }, + {} + ], + "USE_ESM": [ + { + "type": "module" + }, + null + ] + }, + "exports": { + ".": "./lib/index.js", + "./package.json": "./package.json" + }, + "type": "commonjs" +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/src/index.ts b/packages/babel-plugin-proposal-optional-chaining-assign/src/index.ts new file mode 100644 index 000000000000..7ce2a87b0dff --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/src/index.ts @@ -0,0 +1,53 @@ +import { declare } from "@babel/helper-plugin-utils"; +import syntaxOptionalChainingAssign from "@babel/plugin-syntax-optional-chaining-assign"; +import type { NodePath } from "@babel/traverse"; +import type * as t from "@babel/types"; +import { skipTransparentExprWrappers } from "@babel/helper-skip-transparent-expression-wrappers"; +import { transformOptionalChain } from "@babel/plugin-transform-optional-chaining"; + +export default declare(api => { + api.assertVersion("^7.22.5"); + + const assumptions = { + noDocumentAll: api.assumption("noDocumentAll") ?? false, + pureGetters: api.assumption("pureGetters") ?? false, + }; + + const { types: t } = api; + + return { + name: "transform-optional-chaining-assign", + inherits: syntaxOptionalChainingAssign, + + visitor: { + AssignmentExpression(path, state) { + let lhs = path.get("left"); + if (!lhs.isExpression()) return; + const isParenthesized = + lhs.node.extra?.parenthesized || + t.isParenthesizedExpression(lhs.node); + + lhs = skipTransparentExprWrappers(lhs) as NodePath< + t.LVal & t.Expression + >; + if (!lhs.isOptionalMemberExpression()) return; + + let ifNullish: t.Expression = path.scope.buildUndefinedNode(); + if (isParenthesized) { + ifNullish = t.callExpression( + state.addHelper("nullishReceiverError"), + [], + ); + if (path.node.operator === "=") { + ifNullish = t.sequenceExpression([ + t.cloneNode(path.node.right), + ifNullish, + ]); + } + } + + transformOptionalChain(lhs, assumptions, path, ifNullish); + }, + }, + }; +}); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/input.js new file mode 100644 index 000000000000..df09504a3b67 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/input.js @@ -0,0 +1 @@ +a.b?.c.d?.e.f = g; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/output.js new file mode 100644 index 000000000000..f2db1e3f8cf4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/basic/output.js @@ -0,0 +1,2 @@ +var _a$b; +(_a$b = a.b) == null || (_a$b = _a$b.c.d) == null ? void 0 : _a$b.e.f = g; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/input.js new file mode 100644 index 000000000000..cb0e2f0872cd --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/input.js @@ -0,0 +1 @@ +a.b?.c.d?.().e.f = g; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/output.js new file mode 100644 index 000000000000..58a05e18fd91 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/call/output.js @@ -0,0 +1,2 @@ +var _a$b, _a$b$c$d, _a$b$c; +(_a$b = a.b) == null || (_a$b$c$d = (_a$b$c = _a$b.c).d) == null ? void 0 : _a$b$c$d.call(_a$b$c).e.f = g; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/options.json new file mode 100644 index 000000000000..8d0b204b0360 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/options.json @@ -0,0 +1,6 @@ +{ + "assumptions": { + "noDocumentAll": true + }, + "plugins": [["proposal-optional-chaining-assign", { "version": "2023-07" }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/exec.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/exec.js new file mode 100644 index 000000000000..3ea875580145 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/exec.js @@ -0,0 +1,13 @@ +let a = null; + +let evaluated = false; +expect(() => { + (a?.b) = (evaluated = true); +}).toThrow(TypeError); +//expect(evaluated).toBe(true); + +evaluated = false; +expect(() => { + (a?.b) += (evaluated = true); +}).toThrow(TypeError); +expect(evaluated).toBe(false); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/input.js new file mode 100644 index 000000000000..f354ad32e4c3 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/input.js @@ -0,0 +1,2 @@ +(a?.b) = c; +(a?.b) += c; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/output.js new file mode 100644 index 000000000000..354193932ddd --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/parenthesized/output.js @@ -0,0 +1,3 @@ +var _a, _a2; +(_a = a) == null ? (c, babelHelpers.nullishReceiverError()) : _a.b = c; +(_a2 = a) == null ? babelHelpers.nullishReceiverError() : _a2.b += c; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/input.js new file mode 100644 index 000000000000..8c1cb0c82a5f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/input.js @@ -0,0 +1,12 @@ +class A { + #x; + + method() { + obj?.#x = 1; + obj?.#x += 2; + obj?.#x ??= 3; + obj?.#x.y = 4; + obj?.#x.y += 5; + obj?.#x.y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/options.json new file mode 100644 index 000000000000..7fc569afdcde --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["proposal-optional-chaining-assign", { "version": "2023-07" }], + "transform-class-properties" + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/output.js new file mode 100644 index 000000000000..3e426f949857 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field-transformed/output.js @@ -0,0 +1,18 @@ +var _x = /*#__PURE__*/new WeakMap(); +class A { + constructor() { + babelHelpers.classPrivateFieldInitSpec(this, _x, { + writable: true, + value: void 0 + }); + } + method() { + var _obj, _obj2, _obj3, _obj4, _obj5, _obj6; + (_obj = obj) == null ? void 0 : babelHelpers.classPrivateFieldSet(_obj, _x, 1); + (_obj2 = obj) == null ? void 0 : babelHelpers.classPrivateFieldSet(_obj2, _x, babelHelpers.classPrivateFieldGet(_obj2, _x) + 2); + (_obj3 = obj) == null ? void 0 : babelHelpers.classPrivateFieldGet(_obj3, _x) ?? babelHelpers.classPrivateFieldSet(_obj3, _x, 3); + (_obj4 = obj) == null ? void 0 : babelHelpers.classPrivateFieldGet(_obj4, _x).y = 4; + (_obj5 = obj) == null ? void 0 : babelHelpers.classPrivateFieldGet(_obj5, _x).y += 5; + (_obj6 = obj) == null ? void 0 : babelHelpers.classPrivateFieldGet(_obj6, _x).y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/input.js new file mode 100644 index 000000000000..8c1cb0c82a5f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/input.js @@ -0,0 +1,12 @@ +class A { + #x; + + method() { + obj?.#x = 1; + obj?.#x += 2; + obj?.#x ??= 3; + obj?.#x.y = 4; + obj?.#x.y += 5; + obj?.#x.y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/output.js new file mode 100644 index 000000000000..b50547ea8b1f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-noDocumentAll/private-field/output.js @@ -0,0 +1,12 @@ +class A { + #x; + method() { + var _obj, _obj2, _obj3, _obj4, _obj5, _obj6; + (_obj = obj) == null || (_obj.#x = 1); + (_obj2 = obj) == null || (_obj2.#x += 2); + (_obj3 = obj) == null || (_obj3.#x ??= 3); + (_obj4 = obj) == null || (_obj4.#x.y = 4); + (_obj5 = obj) == null || (_obj5.#x.y += 5); + (_obj6 = obj) == null || (_obj6.#x.y ??= 6); + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/input.js new file mode 100644 index 000000000000..cb0e2f0872cd --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/input.js @@ -0,0 +1 @@ +a.b?.c.d?.().e.f = g; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/output.js new file mode 100644 index 000000000000..c5c7594e5c41 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/call/output.js @@ -0,0 +1,2 @@ +var _a$b; +(_a$b = a.b) === null || _a$b === void 0 || _a$b.c.d === null || _a$b.c.d === void 0 ? void 0 : _a$b.c.d().e.f = g; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/options.json new file mode 100644 index 000000000000..77d425f30c1b --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/assumption-pureGetters/options.json @@ -0,0 +1,6 @@ +{ + "assumptions": { + "pureGetters": true + }, + "plugins": [["proposal-optional-chaining-assign", { "version": "2023-07" }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/exec.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/exec.js new file mode 100644 index 000000000000..cc77520afd08 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/exec.js @@ -0,0 +1,38 @@ +let a = null; + +function never(i) { + throw new Error("This should not be evaluated " + i); +} + +expect(() => { + a.b?.c.d?.e.f = never(1); +}).toThrow(TypeError); + +a = { b: null }; +expect(() => { + a.b?.c.d?.e.f = never(2); +}).not.toThrow(); + +a = { b: { c: null } }; +expect(() => { + a.b?.c.d?.e.f = never(3); +}).toThrow(TypeError); + +a = { b: { c: { d: null } } }; +expect(() => { + a.b?.c.d?.e.f = never(4); +}).not.toThrow(); + +a = { b: { c: { d: { e: null } } } }; +let evaluated = false; +expect(() => { + a.b?.c.d?.e.f = (evaluated = true); +}).toThrow(TypeError); +expect(evaluated).toBe(true); + +a = { b: { c: { d: { e: {} } } } }; +let g = {}; +expect(() => { + a.b?.c.d?.e.f = g; +}).not.toThrow(); +expect(a.b.c.d.e.f).toBe(g); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/input.js new file mode 100644 index 000000000000..df09504a3b67 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/input.js @@ -0,0 +1 @@ +a.b?.c.d?.e.f = g; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/output.js new file mode 100644 index 000000000000..c97be9ee4f52 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/basic/output.js @@ -0,0 +1,2 @@ +var _a$b; +(_a$b = a.b) === null || _a$b === void 0 || (_a$b = _a$b.c.d) === null || _a$b === void 0 ? void 0 : _a$b.e.f = g; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/input.js new file mode 100644 index 000000000000..cb0e2f0872cd --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/input.js @@ -0,0 +1 @@ +a.b?.c.d?.().e.f = g; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/output.js new file mode 100644 index 000000000000..736ef71929a8 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/call/output.js @@ -0,0 +1,2 @@ +var _a$b, _a$b$c$d, _a$b$c; +(_a$b = a.b) === null || _a$b === void 0 || (_a$b$c$d = (_a$b$c = _a$b.c).d) === null || _a$b$c$d === void 0 ? void 0 : _a$b$c$d.call(_a$b$c).e.f = g; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/exec.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/exec.js new file mode 100644 index 000000000000..3ea875580145 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/exec.js @@ -0,0 +1,13 @@ +let a = null; + +let evaluated = false; +expect(() => { + (a?.b) = (evaluated = true); +}).toThrow(TypeError); +//expect(evaluated).toBe(true); + +evaluated = false; +expect(() => { + (a?.b) += (evaluated = true); +}).toThrow(TypeError); +expect(evaluated).toBe(false); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/input.js new file mode 100644 index 000000000000..f354ad32e4c3 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/input.js @@ -0,0 +1,2 @@ +(a?.b) = c; +(a?.b) += c; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/output.js new file mode 100644 index 000000000000..8bbd98e76b38 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/parenthesized/output.js @@ -0,0 +1,3 @@ +var _a, _a2; +(_a = a) === null || _a === void 0 ? (c, babelHelpers.nullishReceiverError()) : _a.b = c; +(_a2 = a) === null || _a2 === void 0 ? babelHelpers.nullishReceiverError() : _a2.b += c; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/exec.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/exec.js new file mode 100644 index 000000000000..591f13b173b6 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/exec.js @@ -0,0 +1,6 @@ +let obj = null; +expect(obj?.x += 3).toBe(undefined); + +obj = { x: 1 }; +expect(obj?.x += 3).toBe(4); +expect(obj.x).toBe(4); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/input.js new file mode 100644 index 000000000000..7fd123892dd4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/input.js @@ -0,0 +1 @@ +a?.b.c?.d.e += 3; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/output.js new file mode 100644 index 000000000000..d929b1a71bde --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/plus-eq/output.js @@ -0,0 +1,2 @@ +var _a; +(_a = a) === null || _a === void 0 || (_a = _a.b.c) === null || _a === void 0 ? void 0 : _a.d.e += 3; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/input.js new file mode 100644 index 000000000000..8c1cb0c82a5f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/input.js @@ -0,0 +1,12 @@ +class A { + #x; + + method() { + obj?.#x = 1; + obj?.#x += 2; + obj?.#x ??= 3; + obj?.#x.y = 4; + obj?.#x.y += 5; + obj?.#x.y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/options.json new file mode 100644 index 000000000000..1e85864db164 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/options.json @@ -0,0 +1,9 @@ +{ + "plugins": [ + ["proposal-optional-chaining-assign", { "version": "2023-07" }], + "transform-class-properties" + ], + "assumptions": { + "privateFieldsAsProperties": true + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/output.js new file mode 100644 index 000000000000..61634de5b0b4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed-privateFieldsAsProperties/output.js @@ -0,0 +1,18 @@ +var _x = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("x"); +class A { + constructor() { + Object.defineProperty(this, _x, { + writable: true, + value: void 0 + }); + } + method() { + var _obj, _obj2, _obj3, _obj4, _obj5, _obj6; + (_obj = obj) === null || _obj === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj, _x)[_x] = 1; + (_obj2 = obj) === null || _obj2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj2, _x)[_x] += 2; + (_obj3 = obj) === null || _obj3 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj3, _x)[_x] ??= 3; + (_obj4 = obj) === null || _obj4 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj4, _x)[_x].y = 4; + (_obj5 = obj) === null || _obj5 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj5, _x)[_x].y += 5; + (_obj6 = obj) === null || _obj6 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_obj6, _x)[_x].y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/input.js new file mode 100644 index 000000000000..8c1cb0c82a5f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/input.js @@ -0,0 +1,12 @@ +class A { + #x; + + method() { + obj?.#x = 1; + obj?.#x += 2; + obj?.#x ??= 3; + obj?.#x.y = 4; + obj?.#x.y += 5; + obj?.#x.y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/options.json new file mode 100644 index 000000000000..7fc569afdcde --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["proposal-optional-chaining-assign", { "version": "2023-07" }], + "transform-class-properties" + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/output.js new file mode 100644 index 000000000000..b28892554049 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field-transformed/output.js @@ -0,0 +1,18 @@ +var _x = /*#__PURE__*/new WeakMap(); +class A { + constructor() { + babelHelpers.classPrivateFieldInitSpec(this, _x, { + writable: true, + value: void 0 + }); + } + method() { + var _obj, _obj2, _obj3, _obj4, _obj5, _obj6; + (_obj = obj) === null || _obj === void 0 ? void 0 : babelHelpers.classPrivateFieldSet(_obj, _x, 1); + (_obj2 = obj) === null || _obj2 === void 0 ? void 0 : babelHelpers.classPrivateFieldSet(_obj2, _x, babelHelpers.classPrivateFieldGet(_obj2, _x) + 2); + (_obj3 = obj) === null || _obj3 === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(_obj3, _x) ?? babelHelpers.classPrivateFieldSet(_obj3, _x, 3); + (_obj4 = obj) === null || _obj4 === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(_obj4, _x).y = 4; + (_obj5 = obj) === null || _obj5 === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(_obj5, _x).y += 5; + (_obj6 = obj) === null || _obj6 === void 0 ? void 0 : babelHelpers.classPrivateFieldGet(_obj6, _x).y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/input.js new file mode 100644 index 000000000000..8c1cb0c82a5f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/input.js @@ -0,0 +1,12 @@ +class A { + #x; + + method() { + obj?.#x = 1; + obj?.#x += 2; + obj?.#x ??= 3; + obj?.#x.y = 4; + obj?.#x.y += 5; + obj?.#x.y ??= 6; + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/output.js new file mode 100644 index 000000000000..6fa39a132acb --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/private-field/output.js @@ -0,0 +1,12 @@ +class A { + #x; + method() { + var _obj, _obj2, _obj3, _obj4, _obj5, _obj6; + (_obj = obj) === null || _obj === void 0 || (_obj.#x = 1); + (_obj2 = obj) === null || _obj2 === void 0 || (_obj2.#x += 2); + (_obj3 = obj) === null || _obj3 === void 0 || (_obj3.#x ??= 3); + (_obj4 = obj) === null || _obj4 === void 0 || (_obj4.#x.y = 4); + (_obj5 = obj) === null || _obj5 === void 0 || (_obj5.#x.y += 5); + (_obj6 = obj) === null || _obj6 === void 0 || (_obj6.#x.y ??= 6); + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/input.js new file mode 100644 index 000000000000..da40321e8393 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/input.js @@ -0,0 +1,3 @@ +function fn(obj) { + obj?.prop.x?.y = 2; +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/output.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/output.js new file mode 100644 index 000000000000..e97762ae3f9d --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/general/value-ignored/output.js @@ -0,0 +1,4 @@ +function fn(obj) { + var _obj$prop$x; + obj === null || obj === void 0 || (_obj$prop$x = obj.prop.x) === null || _obj$prop$x === void 0 || (_obj$prop$x.y = 2); +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/input.js new file mode 100644 index 000000000000..51ce904b624a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/input.js @@ -0,0 +1 @@ +a?.() = b; \ No newline at end of file diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/options.json new file mode 100644 index 000000000000..4653803c74a5 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/optional-call-lhs/options.json @@ -0,0 +1,3 @@ +{ + "throws": "a" +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/input.js new file mode 100644 index 000000000000..72c17bb988f6 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/input.js @@ -0,0 +1 @@ +++a?.b; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/options.json new file mode 100644 index 000000000000..4653803c74a5 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-postfix/options.json @@ -0,0 +1,3 @@ +{ + "throws": "a" +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/input.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/input.js new file mode 100644 index 000000000000..72c17bb988f6 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/input.js @@ -0,0 +1 @@ +++a?.b; diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/options.json new file mode 100644 index 000000000000..4653803c74a5 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/invalid/unary-inc-prefix/options.json @@ -0,0 +1,3 @@ +{ + "throws": "a" +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/options.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/options.json new file mode 100644 index 000000000000..1409695c94c4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/fixtures/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining-assign", { "version": "2023-07" }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/index.js b/packages/babel-plugin-proposal-optional-chaining-assign/test/index.js new file mode 100644 index 000000000000..21a55ce6b5e7 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/index.js @@ -0,0 +1,3 @@ +import runner from "@babel/helper-plugin-test-runner"; + +runner(import.meta.url); diff --git a/packages/babel-plugin-proposal-optional-chaining-assign/test/package.json b/packages/babel-plugin-proposal-optional-chaining-assign/test/package.json new file mode 100644 index 000000000000..5ffd9800b97c --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining-assign/test/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/packages/babel-plugin-syntax-optional-chaining-assign/.npmignore b/packages/babel-plugin-syntax-optional-chaining-assign/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-plugin-syntax-optional-chaining-assign/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-syntax-optional-chaining-assign/README.md b/packages/babel-plugin-syntax-optional-chaining-assign/README.md new file mode 100644 index 000000000000..0c1d46c9593c --- /dev/null +++ b/packages/babel-plugin-syntax-optional-chaining-assign/README.md @@ -0,0 +1,19 @@ +# @babel/plugin-syntax-optional-chaining-assign + +> Allow parsing of optional chaining on the left-hand side of assignment expressions + +See our website [@babel/plugin-syntax-optional-chaining-assign](https://babeljs.io/docs/babel-plugin-syntax-optional-chaining-assign) for more information. + +## Install + +Using npm: + +```sh +npm install --save-dev @babel/plugin-syntax-optional-chaining-assign +``` + +or using yarn: + +```sh +yarn add @babel/plugin-syntax-optional-chaining-assign --dev +``` diff --git a/packages/babel-plugin-syntax-optional-chaining-assign/package.json b/packages/babel-plugin-syntax-optional-chaining-assign/package.json new file mode 100644 index 000000000000..afcdb4c8550f --- /dev/null +++ b/packages/babel-plugin-syntax-optional-chaining-assign/package.json @@ -0,0 +1,56 @@ +{ + "name": "@babel/plugin-syntax-optional-chaining-assign", + "version": "7.22.5", + "description": "Allow parsing of optional chaining on the left-hand side of assignment expressions", + "repository": { + "type": "git", + "url": "https://github.com/babel/babel.git", + "directory": "packages/babel-plugin-syntax-optional-chaining-assign" + }, + "homepage": "https://babel.dev/docs/en/next/babel-plugin-syntax-optional-chaining-assign", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "main": "./lib/index.js", + "keywords": [ + "babel-plugin" + ], + "dependencies": { + "@babel/helper-plugin-utils": "workspace:^", + "@babel/helper-validator-option": "workspace:^" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "devDependencies": { + "@babel/core": "workspace:^" + }, + "engines": { + "node": ">=6.9.0" + }, + "author": "The Babel Team (https://babel.dev/team)", + "conditions": { + "BABEL_8_BREAKING": [ + { + "engines": { + "node": "^16.20.0 || ^18.16.0 || >=20.0.0" + } + }, + { + "exports": null + } + ], + "USE_ESM": [ + { + "type": "module" + }, + null + ] + }, + "exports": { + ".": "./lib/index.js", + "./package.json": "./package.json" + }, + "type": "commonjs" +} diff --git a/packages/babel-plugin-syntax-optional-chaining-assign/src/index.ts b/packages/babel-plugin-syntax-optional-chaining-assign/src/index.ts new file mode 100644 index 000000000000..46599a9014f8 --- /dev/null +++ b/packages/babel-plugin-syntax-optional-chaining-assign/src/index.ts @@ -0,0 +1,28 @@ +import { declare } from "@babel/helper-plugin-utils"; +import { OptionValidator } from "@babel/helper-validator-option"; + +const v = new OptionValidator(PACKAGE_JSON.name); + +export interface Options { + version: "2023-07"; +} + +export default declare((api, options: Options) => { + api.assertVersion(7); + + v.validateTopLevelOptions(options, { version: "version" }); + const { version } = options; + v.invariant( + version === "2023-07", + "'.version' option required, representing the last proposal update. " + + "Currently, the only supported value is '2023-07'.", + ); + + return { + name: "syntax-optional-chaining-assign", + + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push(["optionalChainingAssign", { version }]); + }, + }; +}); diff --git a/packages/babel-plugin-transform-destructuring/src/index.ts b/packages/babel-plugin-transform-destructuring/src/index.ts index 3efd941334a8..100342071c81 100644 --- a/packages/babel-plugin-transform-destructuring/src/index.ts +++ b/packages/babel-plugin-transform-destructuring/src/index.ts @@ -158,7 +158,7 @@ export default declare((api, options: Options) => { AssignmentExpression(path, state) { if (!t.isPattern(path.node.left)) return; convertAssignmentExpression( - path, + path as NodePath, name => state.addHelper(name), arrayLikeIsIterable, iterableIsArray, diff --git a/packages/babel-plugin-transform-destructuring/src/util.ts b/packages/babel-plugin-transform-destructuring/src/util.ts index 10c3294d5f15..c8a33d222812 100644 --- a/packages/babel-plugin-transform-destructuring/src/util.ts +++ b/packages/babel-plugin-transform-destructuring/src/util.ts @@ -133,7 +133,7 @@ export class DestructuringTransformer { init: t.Expression, ) { let op = this.operator; - if (t.isMemberExpression(id)) op = "="; + if (t.isMemberExpression(id) || t.isOptionalMemberExpression(id)) op = "="; let node: t.ExpressionStatement | t.VariableDeclaration; @@ -155,7 +155,7 @@ export class DestructuringTransformer { } node = t.variableDeclaration(this.kind, [ - t.variableDeclarator(id, nodeInit), + t.variableDeclarator(id as t.LVal, nodeInit), ]); } @@ -703,7 +703,7 @@ export function convertVariableDeclaration( } export function convertAssignmentExpression( - path: NodePath, + path: NodePath, addHelper: File["addHelper"], arrayLikeIsIterable: boolean, iterableIsArray: boolean, diff --git a/packages/babel-plugin-transform-optional-chaining/src/index.ts b/packages/babel-plugin-transform-optional-chaining/src/index.ts index c0a8b551a0d4..aa5b5762081c 100644 --- a/packages/babel-plugin-transform-optional-chaining/src/index.ts +++ b/packages/babel-plugin-transform-optional-chaining/src/index.ts @@ -1,5 +1,5 @@ import { declare } from "@babel/helper-plugin-utils"; -import { transform } from "./transform.ts"; +import { transform, transformOptionalChain } from "./transform.ts"; import type { NodePath } from "@babel/traverse"; import type * as t from "@babel/types"; @@ -32,4 +32,4 @@ export default declare((api, options: Options) => { }; }); -export { transform }; +export { transform, transformOptionalChain }; diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/input.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/input.js deleted file mode 100644 index 86f4ccc69b4b..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/input.js +++ /dev/null @@ -1 +0,0 @@ -a?.b += 1 diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/options.json b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/options.json deleted file mode 100644 index ea37454d9b87..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment-read-and-update/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Invalid left-hand side in assignment expression" -} diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/input.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/input.js deleted file mode 100644 index bf7a81758e69..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/input.js +++ /dev/null @@ -1 +0,0 @@ -a?.b = 42 diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/options.json b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/options.json deleted file mode 100644 index ea37454d9b87..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-assignment/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Invalid left-hand side in assignment expression" -} diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/input.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/input.js deleted file mode 100644 index 5a451b8587c5..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/input.js +++ /dev/null @@ -1 +0,0 @@ -a?.b++ diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/options.json b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/options.json deleted file mode 100644 index 0153e85a5e1c..000000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/lhs-update/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Invalid left-hand side in postfix operation" -} diff --git a/packages/babel-runtime-corejs2/package.json b/packages/babel-runtime-corejs2/package.json index d470f6a29f31..d536f2e3d403 100644 --- a/packages/babel-runtime-corejs2/package.json +++ b/packages/babel-runtime-corejs2/package.json @@ -945,6 +945,15 @@ "./helpers/identity.js" ], "./helpers/esm/identity": "./helpers/esm/identity.js", + "./helpers/nullishReceiverError": [ + { + "node": "./helpers/nullishReceiverError.js", + "import": "./helpers/esm/nullishReceiverError.js", + "default": "./helpers/nullishReceiverError.js" + }, + "./helpers/nullishReceiverError.js" + ], + "./helpers/esm/nullishReceiverError": "./helpers/esm/nullishReceiverError.js", "./package": "./package.json", "./package.json": "./package.json", "./regenerator": "./regenerator/index.js", diff --git a/packages/babel-runtime-corejs3/package.json b/packages/babel-runtime-corejs3/package.json index 760b7ef1c2ed..efd4bf58369e 100644 --- a/packages/babel-runtime-corejs3/package.json +++ b/packages/babel-runtime-corejs3/package.json @@ -944,6 +944,15 @@ "./helpers/identity.js" ], "./helpers/esm/identity": "./helpers/esm/identity.js", + "./helpers/nullishReceiverError": [ + { + "node": "./helpers/nullishReceiverError.js", + "import": "./helpers/esm/nullishReceiverError.js", + "default": "./helpers/nullishReceiverError.js" + }, + "./helpers/nullishReceiverError.js" + ], + "./helpers/esm/nullishReceiverError": "./helpers/esm/nullishReceiverError.js", "./package": "./package.json", "./package.json": "./package.json", "./regenerator": "./regenerator/index.js", diff --git a/packages/babel-runtime/package.json b/packages/babel-runtime/package.json index b5b3a5c81b6a..abda9ccef21b 100644 --- a/packages/babel-runtime/package.json +++ b/packages/babel-runtime/package.json @@ -944,6 +944,15 @@ "./helpers/identity.js" ], "./helpers/esm/identity": "./helpers/esm/identity.js", + "./helpers/nullishReceiverError": [ + { + "node": "./helpers/nullishReceiverError.js", + "import": "./helpers/esm/nullishReceiverError.js", + "default": "./helpers/nullishReceiverError.js" + }, + "./helpers/nullishReceiverError.js" + ], + "./helpers/esm/nullishReceiverError": "./helpers/esm/nullishReceiverError.js", "./package": "./package.json", "./package.json": "./package.json", "./regenerator": "./regenerator/index.js", diff --git a/packages/babel-standalone/package.json b/packages/babel-standalone/package.json index a78cdb056fe4..c778cbcc79bb 100644 --- a/packages/babel-standalone/package.json +++ b/packages/babel-standalone/package.json @@ -20,6 +20,7 @@ "@babel/plugin-proposal-export-default-from": "workspace:^", "@babel/plugin-proposal-function-bind": "workspace:^", "@babel/plugin-proposal-function-sent": "workspace:^", + "@babel/plugin-proposal-optional-chaining-assign": "workspace:^", "@babel/plugin-proposal-pipeline-operator": "workspace:^", "@babel/plugin-proposal-record-and-tuple": "workspace:^", "@babel/plugin-proposal-regexp-modifiers": "workspace:^", @@ -38,6 +39,7 @@ "@babel/plugin-syntax-import-reflection": "workspace:^", "@babel/plugin-syntax-jsx": "workspace:^", "@babel/plugin-syntax-module-blocks": "workspace:^", + "@babel/plugin-syntax-optional-chaining-assign": "workspace:^", "@babel/plugin-syntax-pipeline-operator": "workspace:^", "@babel/plugin-syntax-record-and-tuple": "workspace:^", "@babel/plugin-syntax-typescript": "workspace:^", diff --git a/packages/babel-standalone/scripts/pluginConfig.json b/packages/babel-standalone/scripts/pluginConfig.json index cec59ae0f0cc..fb0aade1b30d 100644 --- a/packages/babel-standalone/scripts/pluginConfig.json +++ b/packages/babel-standalone/scripts/pluginConfig.json @@ -32,8 +32,9 @@ "syntax-import-assertions", "syntax-import-attributes", "syntax-import-reflection", - "syntax-module-blocks", "syntax-jsx", + "syntax-module-blocks", + "syntax-optional-chaining-assign", "syntax-pipeline-operator", "syntax-record-and-tuple", "syntax-typescript", @@ -56,6 +57,7 @@ "transform-object-rest-spread", "transform-optional-catch-binding", "transform-optional-chaining", + "proposal-optional-chaining-assign", "proposal-pipeline-operator", "transform-private-methods", "transform-private-property-in-object", diff --git a/packages/babel-standalone/src/generated/plugins.ts b/packages/babel-standalone/src/generated/plugins.ts index fa6c00041d33..3fd32ae2df2d 100644 --- a/packages/babel-standalone/src/generated/plugins.ts +++ b/packages/babel-standalone/src/generated/plugins.ts @@ -16,8 +16,9 @@ import syntaxFunctionSent from "@babel/plugin-syntax-function-sent"; import syntaxImportAssertions from "@babel/plugin-syntax-import-assertions"; import syntaxImportAttributes from "@babel/plugin-syntax-import-attributes"; import syntaxImportReflection from "@babel/plugin-syntax-import-reflection"; -import syntaxModuleBlocks from "@babel/plugin-syntax-module-blocks"; import syntaxJsx from "@babel/plugin-syntax-jsx"; +import syntaxModuleBlocks from "@babel/plugin-syntax-module-blocks"; +import syntaxOptionalChainingAssign from "@babel/plugin-syntax-optional-chaining-assign"; import syntaxPipelineOperator from "@babel/plugin-syntax-pipeline-operator"; import syntaxRecordAndTuple from "@babel/plugin-syntax-record-and-tuple"; import syntaxTypescript from "@babel/plugin-syntax-typescript"; @@ -40,6 +41,7 @@ import transformNumericSeparator from "@babel/plugin-transform-numeric-separator import transformObjectRestSpread from "@babel/plugin-transform-object-rest-spread"; import transformOptionalCatchBinding from "@babel/plugin-transform-optional-catch-binding"; import transformOptionalChaining from "@babel/plugin-transform-optional-chaining"; +import proposalOptionalChainingAssign from "@babel/plugin-proposal-optional-chaining-assign"; import proposalPipelineOperator from "@babel/plugin-proposal-pipeline-operator"; import transformPrivateMethods from "@babel/plugin-transform-private-methods"; import transformPrivatePropertyInObject from "@babel/plugin-transform-private-property-in-object"; @@ -121,8 +123,9 @@ export { syntaxImportAssertions, syntaxImportAttributes, syntaxImportReflection, - syntaxModuleBlocks, syntaxJsx, + syntaxModuleBlocks, + syntaxOptionalChainingAssign, syntaxPipelineOperator, syntaxRecordAndTuple, syntaxTypescript, @@ -145,6 +148,7 @@ export { transformObjectRestSpread, transformOptionalCatchBinding, transformOptionalChaining, + proposalOptionalChainingAssign, proposalPipelineOperator, transformPrivateMethods, transformPrivatePropertyInObject, @@ -227,8 +231,9 @@ export const all: { [k: string]: any } = { "syntax-import-assertions": syntaxImportAssertions, "syntax-import-attributes": syntaxImportAttributes, "syntax-import-reflection": syntaxImportReflection, - "syntax-module-blocks": syntaxModuleBlocks, "syntax-jsx": syntaxJsx, + "syntax-module-blocks": syntaxModuleBlocks, + "syntax-optional-chaining-assign": syntaxOptionalChainingAssign, "syntax-pipeline-operator": syntaxPipelineOperator, "syntax-record-and-tuple": syntaxRecordAndTuple, "syntax-typescript": syntaxTypescript, @@ -252,6 +257,7 @@ export const all: { [k: string]: any } = { "transform-object-rest-spread": transformObjectRestSpread, "transform-optional-catch-binding": transformOptionalCatchBinding, "transform-optional-chaining": transformOptionalChaining, + "proposal-optional-chaining-assign": proposalOptionalChainingAssign, "proposal-pipeline-operator": proposalPipelineOperator, "transform-private-methods": transformPrivateMethods, "transform-private-property-in-object": transformPrivatePropertyInObject, diff --git a/packages/babel-standalone/src/preset-stage-1.ts b/packages/babel-standalone/src/preset-stage-1.ts index 8885d2c035cd..9605af19b625 100644 --- a/packages/babel-standalone/src/preset-stage-1.ts +++ b/packages/babel-standalone/src/preset-stage-1.ts @@ -11,6 +11,7 @@ export default (_: any, opts: any = {}) => { pipelineProposal, pipelineTopicToken, recordAndTupleSyntax, + optionalChainingAssignVersion = "2023-07", } = opts; return { @@ -33,6 +34,10 @@ export default (_: any, opts: any = {}) => { babelPlugins.syntaxDecimal, babelPlugins.proposalExportDefaultFrom, babelPlugins.proposalDoExpressions, + [ + babelPlugins.proposalOptionalChainingAssign, + { version: optionalChainingAssignVersion }, + ], ], }; }; diff --git a/packages/babel-standalone/test/preset-stage-1.test.js b/packages/babel-standalone/test/preset-stage-1.test.js index fb098d587d49..31d726077aca 100644 --- a/packages/babel-standalone/test/preset-stage-1.test.js +++ b/packages/babel-standalone/test/preset-stage-1.test.js @@ -78,4 +78,13 @@ describe("stage-1 preset", () => { }).code; expect(output).toMatchInlineSnapshot(`"Tuple(Record({}));"`); }); + it("should support optional chaining assignment", () => { + const output = Babel.transform("expr1?.prop = val", { + presets: [["stage-1", { decoratorsVersion: "2021-12" }]], + }).code; + expect(output).toMatchInlineSnapshot(` + "var _expr; + (_expr = expr1) === null || _expr === void 0 ? void 0 : _expr.prop = val;" + `); + }); }); diff --git a/packages/babel-types/src/ast-types/generated/index.ts b/packages/babel-types/src/ast-types/generated/index.ts index 3b1be988c48b..80dee257fa79 100644 --- a/packages/babel-types/src/ast-types/generated/index.ts +++ b/packages/babel-types/src/ast-types/generated/index.ts @@ -311,7 +311,7 @@ export interface ArrayExpression extends BaseNode { export interface AssignmentExpression extends BaseNode { type: "AssignmentExpression"; operator: string; - left: LVal; + left: LVal | OptionalMemberExpression; right: Expression; } diff --git a/packages/babel-types/src/builders/generated/index.ts b/packages/babel-types/src/builders/generated/index.ts index d27a37af22a8..ab9c0e3ee9cd 100644 --- a/packages/babel-types/src/builders/generated/index.ts +++ b/packages/babel-types/src/builders/generated/index.ts @@ -15,7 +15,7 @@ export function arrayExpression( } export function assignmentExpression( operator: string, - left: t.LVal, + left: t.LVal | t.OptionalMemberExpression, right: t.Expression, ): t.AssignmentExpression { return validateNode({ diff --git a/packages/babel-types/src/definitions/core.ts b/packages/babel-types/src/definitions/core.ts index 72c28393f662..8831e1195ab9 100644 --- a/packages/babel-types/src/definitions/core.ts +++ b/packages/babel-types/src/definitions/core.ts @@ -63,10 +63,11 @@ defineType("AssignmentExpression", { }, left: { validate: !process.env.BABEL_TYPES_8_BREAKING - ? assertNodeType("LVal") + ? assertNodeType("LVal", "OptionalMemberExpression") : assertNodeType( "Identifier", "MemberExpression", + "OptionalMemberExpression", "ArrayPattern", "ObjectPattern", "TSAsExpression", diff --git a/tsconfig.json b/tsconfig.json index b2a6d0dc8f5f..4b77fe8dc8d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -51,6 +51,7 @@ "./packages/babel-plugin-proposal-function-bind/src/**/*.ts", "./packages/babel-plugin-proposal-function-sent/src/**/*.ts", "./packages/babel-plugin-proposal-import-attributes-to-assertions/src/**/*.ts", + "./packages/babel-plugin-proposal-optional-chaining-assign/src/**/*.ts", "./packages/babel-plugin-proposal-partial-application/src/**/*.ts", "./packages/babel-plugin-proposal-pipeline-operator/src/**/*.ts", "./packages/babel-plugin-proposal-record-and-tuple/src/**/*.ts", @@ -71,6 +72,7 @@ "./packages/babel-plugin-syntax-import-reflection/src/**/*.ts", "./packages/babel-plugin-syntax-jsx/src/**/*.ts", "./packages/babel-plugin-syntax-module-blocks/src/**/*.ts", + "./packages/babel-plugin-syntax-optional-chaining-assign/src/**/*.ts", "./packages/babel-plugin-syntax-partial-application/src/**/*.ts", "./packages/babel-plugin-syntax-pipeline-operator/src/**/*.ts", "./packages/babel-plugin-syntax-record-and-tuple/src/**/*.ts", @@ -323,6 +325,9 @@ "@babel/plugin-proposal-import-attributes-to-assertions": [ "./packages/babel-plugin-proposal-import-attributes-to-assertions/src" ], + "@babel/plugin-proposal-optional-chaining-assign": [ + "./packages/babel-plugin-proposal-optional-chaining-assign/src" + ], "@babel/plugin-proposal-partial-application": [ "./packages/babel-plugin-proposal-partial-application/src" ], @@ -383,6 +388,9 @@ "@babel/plugin-syntax-module-blocks": [ "./packages/babel-plugin-syntax-module-blocks/src" ], + "@babel/plugin-syntax-optional-chaining-assign": [ + "./packages/babel-plugin-syntax-optional-chaining-assign/src" + ], "@babel/plugin-syntax-partial-application": [ "./packages/babel-plugin-syntax-partial-application/src" ], diff --git a/yarn.lock b/yarn.lock index cf421993de5c..8f7bc998767b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1562,6 +1562,21 @@ __metadata: languageName: unknown linkType: soft +"@babel/plugin-proposal-optional-chaining-assign@workspace:^, @babel/plugin-proposal-optional-chaining-assign@workspace:packages/babel-plugin-proposal-optional-chaining-assign": + version: 0.0.0-use.local + resolution: "@babel/plugin-proposal-optional-chaining-assign@workspace:packages/babel-plugin-proposal-optional-chaining-assign" + dependencies: + "@babel/core": "workspace:^" + "@babel/helper-plugin-test-runner": "workspace:^" + "@babel/helper-plugin-utils": "workspace:^" + "@babel/helper-skip-transparent-expression-wrappers": "workspace:^" + "@babel/plugin-syntax-optional-chaining-assign": "workspace:^" + "@babel/plugin-transform-optional-chaining": "workspace:^" + peerDependencies: + "@babel/core": ^7.22.5 + languageName: unknown + linkType: soft + "@babel/plugin-proposal-partial-application@workspace:packages/babel-plugin-proposal-partial-application": version: 0.0.0-use.local resolution: "@babel/plugin-proposal-partial-application@workspace:packages/babel-plugin-proposal-partial-application" @@ -2117,6 +2132,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-optional-chaining-assign@workspace:^, @babel/plugin-syntax-optional-chaining-assign@workspace:packages/babel-plugin-syntax-optional-chaining-assign": + version: 0.0.0-use.local + resolution: "@babel/plugin-syntax-optional-chaining-assign@workspace:packages/babel-plugin-syntax-optional-chaining-assign" + dependencies: + "@babel/core": "workspace:^" + "@babel/helper-plugin-utils": "workspace:^" + "@babel/helper-validator-option": "workspace:^" + peerDependencies: + "@babel/core": ^7.0.0-0 + languageName: unknown + linkType: soft + "@babel/plugin-syntax-optional-chaining@condition:BABEL_8_BREAKING ? : ^7.8.3": version: 0.0.0-condition-4311ec resolution: "@babel/plugin-syntax-optional-chaining@condition:BABEL_8_BREAKING?:^7.8.3#4311ec" @@ -4106,6 +4133,7 @@ __metadata: "@babel/plugin-proposal-export-default-from": "workspace:^" "@babel/plugin-proposal-function-bind": "workspace:^" "@babel/plugin-proposal-function-sent": "workspace:^" + "@babel/plugin-proposal-optional-chaining-assign": "workspace:^" "@babel/plugin-proposal-pipeline-operator": "workspace:^" "@babel/plugin-proposal-record-and-tuple": "workspace:^" "@babel/plugin-proposal-regexp-modifiers": "workspace:^" @@ -4124,6 +4152,7 @@ __metadata: "@babel/plugin-syntax-import-reflection": "workspace:^" "@babel/plugin-syntax-jsx": "workspace:^" "@babel/plugin-syntax-module-blocks": "workspace:^" + "@babel/plugin-syntax-optional-chaining-assign": "workspace:^" "@babel/plugin-syntax-pipeline-operator": "workspace:^" "@babel/plugin-syntax-record-and-tuple": "workspace:^" "@babel/plugin-syntax-typescript": "workspace:^"