Repository: babel/babel (TypeScript)

Issue:
[Bug]: Pure annotations are incorrectly modified to wrong scope
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Programmatic API (`babel.transform`, `babel.parse`)

### Input code

```js
(/* @__PURE__ */ f()).g()
```

[REPL Link](https://babel.dev/repl#?browsers=&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=BQegVABAAg-jAKBVASgUThMIIDNgEp8A6AcwKA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=false&presets=&prettier=false&targets=&version=7.18.9&externalPlugins=&assumptions=%7B%7D)

### Configuration file name

_No response_

### Configuration

None

### Current and expected behavior

Currently babel strips the parentheses of `(/* @__PURE__ */ f())`, causing the whole expression `f().g()` be marked as pure.

```js
/* @__PURE__ */
f().g();

// expect: same as input
```

### Environment

```
System:
  OS: macOS 12.5
Binaries:
  Node: 18.6.0 - /usr/local/bin/node
  Yarn: 1.22.19 - /usr/local/bin/yarn
  npm: 8.15.0 - /usr/local/bin/npm
npmPackages:
  @babel/core: ^7.7.4 => 7.18.9
  @babel/plugin-proposal-class-properties: ^7.7.4 => 7.18.6
  @babel/plugin-proposal-object-rest-spread: ^7.7.4 => 7.18.9
  @babel/plugin-transform-modules-commonjs: ^7.15.0 => 7.18.6
  @babel/preset-env: ^7.7.4 => 7.18.9
  @babel/preset-react: ^7.7.4 => 7.18.6
  @babel/preset-typescript: ^7.7.4 => 7.18.6
  babel-loader: ^8.0.6 => 8.2.5
  webpack: ^5.74.0 => 5.74.0
```

### Possible solution

_No response_

### Additional context

_No response_


Hints:
Hey @hyrious! 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.
As a workaround, you can enable the `createParenthesizedExpressions` parser option:

```js
{
  // babel config
  plugins: [...],
  parserOpts: { createParenthesizedExpressions: true }
}
```

It will generate the `ParenthesizedExpressions` AST node where the pure annotation comment can be attached.

Patch:
diff --git a/packages/babel-generator/src/printer.ts b/packages/babel-generator/src/printer.ts
--- a/packages/babel-generator/src/printer.ts
+++ b/packages/babel-generator/src/printer.ts
@@ -22,7 +22,6 @@ import type { TraceMap } from "@jridgewell/trace-mapping";
 
 const SCIENTIFIC_NOTATION = /e/i;
 const ZERO_DECIMAL_INTEGER = /\.0+$/;
-const PURE_ANNOTATION_RE = /^\s*[@#]__PURE__\s*$/;
 const HAS_NEWLINE = /[\n\r\u2028\u2029]/;
 const HAS_NEWLINE_OR_BlOCK_COMMENT_END = /[\n\r\u2028\u2029]|\*\//;
 
@@ -534,16 +533,7 @@ class Printer {
 
       if (chaPost === charCodes.asterisk) {
         // This is a block comment
-
-        if (PURE_ANNOTATION_RE.test(str.slice(i + 2, len - 2))) {
-          // We avoid printing newlines after #__PURE__ comments (we treat
-          // then as unary operators), but we must keep the old
-          // parenPushNewlineState because, if a newline was forbidden, it is
-          // still forbidden after the comment.
-          return;
-        }
-
-        // NOTE: code flow continues from here to after these if/elses
+        return;
       } else if (chaPost !== charCodes.slash) {
         // This is neither a block comment, nor a line comment.
         // After a normal token, the parentheses aren't needed anymore
@@ -674,13 +664,37 @@ class Printer {
     this._insideAux = node.loc == undefined;
     this._maybeAddAuxComment(this._insideAux && !oldInAux);
 
-    const shouldPrintParens =
+    const parenthesized = node.extra?.parenthesized as boolean | undefined;
+    let shouldPrintParens =
       forceParens ||
-      (format.retainFunctionParens &&
-        nodeType === "FunctionExpression" &&
-        node.extra?.parenthesized) ||
+      (parenthesized &&
+        format.retainFunctionParens &&
+        nodeType === "FunctionExpression") ||
       needsParens(node, parent, this._printStack);
 
+    if (
+      !shouldPrintParens &&
+      parenthesized &&
+      node.leadingComments?.length &&
+      node.leadingComments[0].type === "CommentBlock"
+    ) {
+      const parentType = parent?.type;
+      switch (parentType) {
+        case "ExpressionStatement":
+        case "VariableDeclarator":
+        case "AssignmentExpression":
+        case "ReturnStatement":
+          break;
+        case "CallExpression":
+        case "OptionalCallExpression":
+        case "NewExpression":
+          if (parent.callee !== node) break;
+        // falls through
+        default:
+          shouldPrintParens = true;
+      }
+    }
+
     if (shouldPrintParens) {
       this.token("(");
       this._endsWithInnerRaw = false;


Test patch:
diff --git a/packages/babel-generator/test/fixtures/comments/pure-in-paren/input.js b/packages/babel-generator/test/fixtures/comments/pure-in-paren/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-generator/test/fixtures/comments/pure-in-paren/input.js
@@ -0,0 +1,4 @@
+(/* @__PURE__ */ f()).g();
+(/* @__PURE__ */ f())();
+(/* @__PURE__ */ f())?.bar;
+(/* @__PURE__ */ f())`bar`;
diff --git a/packages/babel-generator/test/fixtures/comments/pure-in-paren/output.js b/packages/babel-generator/test/fixtures/comments/pure-in-paren/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-generator/test/fixtures/comments/pure-in-paren/output.js
@@ -0,0 +1,4 @@
+( /* @__PURE__ */f()).g();
+( /* @__PURE__ */f())();
+( /* @__PURE__ */f())?.bar;
+( /* @__PURE__ */f())`bar`;
\ No newline at end of file
diff --git a/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js b/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
--- a/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
+++ b/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
@@ -1,13 +1,13 @@
 function assertElement(assertFn, shouldBeElement, opt_message) {
-  return (/** @type {!Ele	ment} */(
-    assertType_(
-      assertFn,
-      shouldBeElement,
-      isElement(shouldBeElement),
-      'Element expected',
-      opt_message
-    )
-    ));
+  return /** @type {!Ele	ment} */(
+  assertType_(
+    assertFn,
+    shouldBeElement,
+    isElement(shouldBeElement),
+    'Element expected',
+    opt_message
+  )
+  );
 }
 
 const slot = /** @type {!HTMLSlotElement} */(e.target);
diff --git a/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg/output.js b/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg/output.js
--- a/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg/output.js
+++ b/packages/babel-generator/test/fixtures/regression/comment-before-parentheses-return-arg/output.js
@@ -1,5 +1,5 @@
 function assertElement(assertFn, shouldBeElement, opt_message) {
-  return (/** @type {!Ele	ment} */
+  return /** @type {!Ele	ment} */(
     assertType_(assertFn, shouldBeElement, isElement(shouldBeElement), 'Element expected', opt_message)
   );
 }
diff --git a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
--- a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
+++ b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/output.js
@@ -1,13 +1,13 @@
 function assertElement(assertFn, shouldBeElement, opt_message) {
-  return (/** @type {!Ele	ment} */(
-    assertType_(
-      assertFn,
-      shouldBeElement,
-      isElement(shouldBeElement),
-      'Element expected',
-      opt_message
-    )
-    ));
+  return /** @type {!Ele	ment} */(
+  assertType_(
+    assertFn,
+    shouldBeElement,
+    isElement(shouldBeElement),
+    'Element expected',
+    opt_message
+  )
+  );
 }
 
 const slot = /** @type {!HTMLSlotElement} */(e.target);
diff --git a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/source-map.json b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/source-map.json
--- a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/source-map.json
+++ b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg-createParenthesizedExpressions/source-map.json
@@ -18,5 +18,5 @@
   "sourcesContent": [
     "function assertElement(assertFn, shouldBeElement, opt_message) {\n  return /** @type {!Ele\tment} */ (\n\t  assertType_(\n\t    assertFn,\n\t\t  shouldBeElement,\n\t\t  isElement(shouldBeElement),\n\t\t  'Element expected',\n\t\t  opt_message\n\t  )\n\t);\n}\n\nconst slot = /** @type {!HTMLSlotElement} */ (e.target);\n\nassertElement(\n  /** @type {Element} */ (el),\n);"
   ],
-  "mappings": "AAAA,SAASA,aAAaA,CAACC,QAAQ,EAAEC,eAAe,EAAEC,WAAW,EAAE;EAC7D,OAAO,yBAAyB;IAC/BC,WAAW;MACTH,QAAQ;MACTC,eAAe;MACfG,SAAS,CAACH,eAAe,CAAC;MAC1B,kBAAkB;MAClBC;IACD;IACF,CAAC;AACF;;AAEA,MAAMG,IAAI,GAAG,+BAAgC,CAACC,CAAC,CAACC,MAAM,CAAC;;AAEvDR,aAAa;EACX,sBAAuB,CAACS,EAAE;AAC5B,CAAC"
+  "mappings": "AAAA,SAASA,aAAaA,CAACC,QAAQ,EAAEC,eAAe,EAAEC,WAAW,EAAE;EAC7D,OAAO,wBAAyB;EAC/BC,WAAW;IACTH,QAAQ;IACTC,eAAe;IACfG,SAAS,CAACH,eAAe,CAAC;IAC1B,kBAAkB;IAClBC;EACD;EACF,CAAC;AACF;;AAEA,MAAMG,IAAI,GAAG,+BAAgC,CAACC,CAAC,CAACC,MAAM,CAAC;;AAEvDR,aAAa;EACX,sBAAuB,CAACS,EAAE;AAC5B,CAAC"
 }
\ No newline at end of file
diff --git a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg/output.js b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg/output.js
--- a/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg/output.js
+++ b/packages/babel-generator/test/fixtures/sourcemaps/comment-before-parentheses-return-arg/output.js
@@ -1,5 +1,5 @@
 function assertElement(assertFn, shouldBeElement, opt_message) {
-  return (/** @type {!Ele	ment} */
+  return /** @type {!Ele	ment} */(
     assertType_(
       assertFn,
       shouldBeElement,
diff --git a/packages/babel-generator/test/index.js b/packages/babel-generator/test/index.js
--- a/packages/babel-generator/test/index.js
+++ b/packages/babel-generator/test/index.js
@@ -1482,6 +1482,16 @@ describe("programmatic generation", function () {
         `"(x, y): any /*foo*/ => z"`,
       );
     });
+
+    it("multi-line leading comment after return", () => {
+      const val = t.identifier("val");
+      val.leadingComments = [{ type: "CommentBlock", value: "new\nline" }];
+      expect(generate(t.returnStatement(val)).code).toMatch(`return (
+  /*new
+  line*/
+  val
+);`);
+    });
   });
 });