Repository: babel/babel (TypeScript)

Issue:
[Bug]: Optional chaining expressions resolving as `true` instead of undefined
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

babel-loader (webpack)

### Input code
https://babel.dev/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=PTAEDMEsCcGcBdQBtIDsCmoAWBDWoADACgH1p1xQBeUVAVySQEpqqb7GDQ9QAHHaIgD2leFkzoAHr3KxYkIalAB3LJADGWUOgBuOJHXjp88IYXjQ66LumjRF6IXVhIAnt1QATULIbx8aKBimKpCSBLSsvKKoABG6GgA5uaW1gBQHEgA_AB0RghpaSBBavgoGKCw6OgAtiZmsmE6mOpCdujq8G5pdF4UaOieufnwQA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.22.11&externalPlugins=&assumptions=%7B%7D

### Configuration file name

reproduced in babel try it out - see for configuration

### Configuration

reproduced in babel try it out - see for configuration

### Current and expected behavior

In my application expressions like `null?.a` are evaluating as true. This behavior started today with version 7.22.11

### Environment

Babel version 7.22.11

### Possible solution

Changes like these in a recent change might be the issue? It looks like if a value is undefined, babel gives an expression that evalutes to true.

![image](https://github.com/babel/babel/assets/22520333/d859d3ff-adfe-4dad-b777-fabb3861db33)

https://github.com/babel/babel/pull/15871/commits/170c83478fbb3115aca98bef532bd8e8cc3bf7ae



Hints:
Hey @EJLearner! We really appreciate you taking the time to report an issue. The collaborators on this project attempt to help as many people as possible, but we're a limited number of volunteers, so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant [Slack community](https://babeljs.slack.com) that typically always has someone willing to help. You can sign-up [here](https://slack.babeljs.io/) for an invite.
This is an optimization we introduced, that maybe is triggered too often 🤔 

Could you provide a code example that, when evaluated, has different behavior? Btw, this optimization does not apply in the last line of a script. You need to add another line in the repl after the one using optional chaining.
Hi @EJLearner! This issue is missing some important information we'll need to be able to reproduce this issue.

Please understand that we receive a high volume of issues, and there are only a limited number of volunteers that help maintain this project. The easier it is for us to decipher an issue with the info provided, the more likely it is that we'll be able to help.

Please make sure you have the following information documented in this ticket:
1. Your Babel configuration (typically from `.babelrc` or `babel.config.js`)
2. The current (incorrect) behavior you're seeing
3. The behavior you expect
4. A [short, self-contained example](http://sscce.org/)

Please provide either a link to the problem via the [`repl`](https://babeljs.io/repl/), or if the `repl` is insufficient, a new and minimal repository with instructions on how to build/replicate the issue.
> This is an optimization we introduced, that maybe is triggered too often 🤔
> 
> Could you provide a code example that, when evaluated, has different behavior? Btw, this optimization does not apply in the last line of a script. You need to add another line in the repl after the one using optional chaining.

thanks, I updated the repl to have multiple lines and it now shows better how the top will evaluate to `true`
> > This is an optimization we introduced, that maybe is triggered too often 🤔
> > Could you provide a code example that, when evaluated, has different behavior? Btw, this optimization does not apply in the last line of a script. You need to add another line in the repl after the one using optional chaining.
> 
> thanks, I updated the repl to have multiple lines and it now shows better how the top will evaluate to `true`

In the REPL example provided above
```js
null?.test
undefined?.test
```
The evaluation result of `null?.test` is not used anyway, so Babel outputs code that is shorter. If you assign the value to a variable, Babel will fallback to the compliant transform so that it will evaluate to `undefined`.

```js
var a = null?.test
// transformed to
var a = (_ref = null) === null || _ref === void 0 ? void 0 : _ref.test;
```

Do you have an example that such optimization actually breaks your application?
> > > This is an optimization we introduced, that maybe is triggered too often 🤔
> > > Could you provide a code example that, when evaluated, has different behavior? Btw, this optimization does not apply in the last line of a script. You need to add another line in the repl after the one using optional chaining.
> > 
> > 
> > thanks, I updated the repl to have multiple lines and it now shows better how the top will evaluate to `true`
> 
> In the REPL example provided above
> 
> ```js
> null?.test
> undefined?.test
> ```
> 
> The evaluation result of `null?.test` is not used anyway, so Babel outputs code that is shorter. If you assign the value to a variable, Babel will fallback to the compliant transform so that it will evaluate to `undefined`.
> 
> ```js
> var a = null?.test
> // transformed to
> var a = (_ref = null) === null || _ref === void 0 ? void 0 : _ref.test;
> ```
> 
> Do you have an example that such optimization actually breaks your application?

Yes, sorry. I'm just realizing that the code that my attempted reproduction is exactly how this is supposed to work. I'm still looking into it and starting to suspect that this may be an issue with our testing setup. This can probably be dropped for now. If I need to, I can open a new one with hopefully more details.
To give a little more information. It seems to be only an issue when running jest tests with coverage. Right now we suspect that jest or another tool is somehow changing the code in a way that babel detects that the expression is not being assigned to anything and then the recent optimization kicks in when we don't want it to.

We're still trying to figure out if it's a babel issue, a jest issue, or an issue with our configuration.
Jest probably transforms your files with https://github.com/istanbuljs/babel-plugin-istanbul for coverage, so a reproduction would probably need to use that plugin.

Also, you might try using the [v8 coverage provider](https://jestjs.io/docs/configuration#coverageprovider-string), which does not require code transform.
This is a babel bug :)

https://babel.dev/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=PTAEDMEsCcGcBdQBtIDsCmoAWBDWoADACgH1p1xQBeUVAVySQEpqqb7GDQ9QAHHaIgD2leFkzoAHr3KxYkIalAB3LJADGWUOgBuOJHXjp88IYXjQ66LumjRF6IXVhIAnt1QATULIbx8aKBimKpCSBLSsvKKoABG6GgA5uaW1gBQetDc1KBEAAwANLQMSAD8AHRGCEwA3GlpIEFq-CgYoLDo6AC2JmayYTqY6kJ26Orwbml0XhRo6J4VVfBAA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.22.11&externalPlugins=&assumptions=%7B%7D

Patch:
diff --git a/packages/babel-plugin-transform-optional-chaining/src/transform.ts b/packages/babel-plugin-transform-optional-chaining/src/transform.ts
--- a/packages/babel-plugin-transform-optional-chaining/src/transform.ts
+++ b/packages/babel-plugin-transform-optional-chaining/src/transform.ts
@@ -6,6 +6,9 @@ import {
 } from "@babel/helper-skip-transparent-expression-wrappers";
 import { willPathCastToBoolean, findOutermostTransparentParent } from "./util";
 
+// TODO(Babel 9): Use .at(-1)
+const last = <T>(arr: T[]) => arr[arr.length - 1];
+
 function isSimpleMemberExpression(
   expression: t.Expression | t.Super,
 ): expression is t.Identifier | t.Super | t.MemberExpression {
@@ -209,9 +212,10 @@ export function transformOptionalChain(
     !ifNullishBoolean && t.isUnaryExpression(ifNullish, { operator: "void" });
 
   const isEvaluationValueIgnored =
-    (t.isExpressionStatement(replacementPath.parent) ||
-      t.isSequenceExpression(replacementPath.parent)) &&
-    !replacementPath.isCompletionRecord();
+    (t.isExpressionStatement(replacementPath.parent) &&
+      !replacementPath.isCompletionRecord()) ||
+    (t.isSequenceExpression(replacementPath.parent) &&
+      last(replacementPath.parent.expressions) !== replacementPath.node);
 
   // prettier-ignore
   const tpl = ifNullishFalse


Test patch:
diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/output.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/output.js
--- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/output.js
+++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/output.js
@@ -7,5 +7,5 @@ var _foo, _a, _a$b, _a$b$c, _orders, _orders2, _client, _orders$client$key, _a2,
 (_orders2 = orders) === null || _orders2 === void 0 || (_orders2 = _orders2[0]) === null || _orders2 === void 0 || _orders2.price;
 orders[(_client = client) === null || _client === void 0 ? void 0 : _client.key].price;
 (_orders$client$key = orders[client.key]) === null || _orders$client$key === void 0 || _orders$client$key.price;
-(0, (_a2 = a) === null || _a2 === void 0 || _a2.b).c;
+(0, (_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b).c;
 (0, (_c = (0, (_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b).c) === null || _c === void 0 ? void 0 : _c.d).e;
diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/exec.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/exec.js
@@ -0,0 +1,3 @@
+let a = (0, null?.prop);
+
+expect(a).toBe(undefined);
diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/input.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/input.js
@@ -0,0 +1,3 @@
+let a = (null?.prop1, null?.prop2);
+
+expect(a).toBe(undefined);
diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/output.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/regression/15887/output.js
@@ -0,0 +1,3 @@
+var _ref, _ref2;
+let a = ((_ref = null) !== null && _ref !== void 0 && _ref.prop1, (_ref2 = null) === null || _ref2 === void 0 ? void 0 : _ref2.prop2);
+expect(a).toBe(undefined);