Repository: babel/babel (TypeScript)

Issue:
[Bug]: duplicated comment after `path.remove()`
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Duplicated comments occurs after using `path.remove()`

### Input code

```js
import {parse} from '@babel/parser';
import traverse from '@babel/traverse';

const ast = parse(`
    // update-tsconfig-file
    const a = 5;
    // const updateTSConfig = require('../update-tsconfig')
    // https://nextjs.org/docs/app/building-your-application/upgrading/from-vite
    const getValue = (a) => a.value;
    getValue();
`);

traverse(ast, {
    VariableDeclarator(path) {
        if (path.node.id.name === 'a')
          path.remove();
    }
});
```

Comments duplicated, it should be 3 of them, not 4:

![image](https://github.com/babel/babel/assets/1573141/6a97e326-d377-4a8b-885c-5549be2484d4)

Any ways for such code:

```js
import {parse} from '@babel/parser';
import traverse from '@babel/traverse';

const ast = parse(`
    // update-tsconfig-file
    const a = 5;
    // const updateTSConfig = require('../update-tsconfig')
    // https://nextjs.org/docs/app/building-your-application/upgrading/from-vite
    const getValue = (a) => a.value;
    getValue();
`);

traverse(ast, {
    VariableDeclarator(path) {
        if (path.node.id.name === 'a')
          path.remove();
    },
    Program: {
        exit(path) {
            console.log(path + '');
        }
    }
});
```

Output will be correct:

![image](https://github.com/babel/babel/assets/1573141/da6bcf86-86d5-4189-a7a2-166b0df26ce9)

So the problem that occurs in `@babel/traverse` is fixed by `@babel/generator`, but would be great if there was no duplication.

### Configuration file name

_No response_

### Configuration

_No response_

### Current and expected behavior

Current - 4 comments;
Expected - 3 comments, no duplicates;

### Environment

Latest Babel

### Possible solution

_No response_

### Additional context

It works in ast explorer, so as I understand, [`@babel/generator`](https://babeljs.io/docs/babel-generator) fixes all duplicates.

https://astexplorer.net/#/gist/7ed5bbcb21e2a3e96e609060936a41c2/3ba5a27a954d2a3e00aa5bcbaa359780ba5550e4


Hints:
Hey @coderaiser! 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.

Patch:
diff --git a/babel.config.js b/babel.config.js
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,12 +1,13 @@
 "use strict";
 
-if (
-  typeof it === "function" &&
+let jestSnapshot = false;
+if (typeof it === "function") {
   // Jest loads the Babel config to parse file and update inline snapshots.
   // This is ok, as it's not loading the Babel config to test Babel itself.
-  !new Error().stack.includes("jest-snapshot")
-) {
-  throw new Error("Monorepo root's babel.config.js loaded by a test.");
+  if (!new Error().stack.includes("jest-snapshot")) {
+    throw new Error("Monorepo root's babel.config.js loaded by a test.");
+  }
+  jestSnapshot = true;
 }
 
 const pathUtils = require("path");
@@ -304,6 +305,16 @@ module.exports = function (api) {
     ].filter(Boolean),
   };
 
+  if (jestSnapshot) {
+    config.plugins = [];
+    config.presets = [];
+    config.overrides = [];
+    config.parserOpts = {
+      plugins: ["typescript"],
+    };
+    config.sourceType = "unambiguous";
+  }
+
   return config;
 };
 
diff --git a/packages/babel-generator/src/buffer.ts b/packages/babel-generator/src/buffer.ts
--- a/packages/babel-generator/src/buffer.ts
+++ b/packages/babel-generator/src/buffer.ts
@@ -379,9 +379,6 @@ export default class Buffer {
 
   /**
    * check if current _last + queue ends with newline, return the character before newline
-   *
-   * @param {*} ch
-   * @memberof Buffer
    */
   endsWithCharAndNewline(): number {
     const queue = this._queue;
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
@@ -824,6 +824,10 @@ class Printer {
       if (i < len - 1) separator?.();
 
       if (opts.statement) {
+        if (!node.trailingComments?.length) {
+          this._lastCommentLine = 0;
+        }
+
         if (i + 1 === len) {
           this.newline(1);
         } else {
diff --git a/packages/babel-traverse/src/path/comments.ts b/packages/babel-traverse/src/path/comments.ts
--- a/packages/babel-traverse/src/path/comments.ts
+++ b/packages/babel-traverse/src/path/comments.ts
@@ -47,12 +47,10 @@ export function shareCommentsWithSiblings(this: NodePath) {
 }
 
 function removeIfExisting<T>(list: T[], toRemove?: T[]): T[] {
-  if (!toRemove) return list;
-  let lastFoundIndex = -1;
+  if (!toRemove?.length) return list;
+  const set = new Set(toRemove);
   return list.filter(el => {
-    const i = toRemove.indexOf(el, lastFoundIndex);
-    if (i === -1) return true;
-    lastFoundIndex = i;
+    return !set.has(el);
   });
 }
 


Test patch:
diff --git a/packages/babel-generator/test/fixtures/comments/variable-declarator-trailing-comment/output.js b/packages/babel-generator/test/fixtures/comments/variable-declarator-trailing-comment/output.js
--- a/packages/babel-generator/test/fixtures/comments/variable-declarator-trailing-comment/output.js
+++ b/packages/babel-generator/test/fixtures/comments/variable-declarator-trailing-comment/output.js
@@ -3,13 +3,11 @@
                * This is trailing comment
                */
 }
-
 {
   var tt = 20; /*
                 * This is trailing comment
                 */
 }
-
 {
   {
     var t = 20; /*
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
@@ -1492,6 +1492,26 @@ describe("programmatic generation", function () {
   val
 );`);
     });
+
+    it("multi-line leading comment after return 2", () => {
+      const ast = parse(
+        `return (
+        /*new
+        line*/ val);`,
+        {
+          allowReturnOutsideFunction: true,
+        },
+      );
+      // Remove `parenthesized`
+      ast.program.body[0].argument.extra = null;
+      expect(generate(ast).code).toMatchInlineSnapshot(`
+        "return (
+          /*new
+          line*/
+          val
+        );"
+      `);
+    });
   });
 });
 
diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/lazy-dep/unused/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/lazy-dep/unused/output.js
--- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/lazy-dep/unused/output.js
+++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/lazy-dep/unused/output.js
@@ -23,4 +23,5 @@ _e().e;
 
 // The first import is unused, but we keep the require call
 // because of the second one
+
 _g().h;
diff --git a/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-arguments/output.js b/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-arguments/output.js
--- a/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-arguments/output.js
+++ b/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-arguments/output.js
@@ -4,5 +4,4 @@ function func() {
   }
   console.log(_arguments); // [1, 2, 3]
 }
-
 func(1, 2, 3);
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/exports/declared-types/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/exports/declared-types/output.mjs
--- a/packages/babel-plugin-transform-typescript/test/fixtures/exports/declared-types/output.mjs
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/exports/declared-types/output.mjs
@@ -23,5 +23,6 @@ function foo() {}
 export { BB2 as BB3, foo }; // only BB2->BB3 and foo
 
 // export an interface before declaration
+
 // everything removed
 export { C2 as C4 }; // only C2->C4
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-star-from/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-star-from/output.mjs
--- a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-star-from/output.mjs
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-star-from/output.mjs
@@ -1,4 +1,5 @@
 // Note: TSC doesn't support string module specifiers yet,
 // but it's easier for us to support them than not.
+
 ;
 export {};
diff --git a/packages/babel-traverse/test/fixtures/comments/attachment/output.js b/packages/babel-traverse/test/fixtures/comments/attachment/output.js
--- a/packages/babel-traverse/test/fixtures/comments/attachment/output.js
+++ b/packages/babel-traverse/test/fixtures/comments/attachment/output.js
@@ -2,8 +2,7 @@
 {
   hasPrev;
   /* top */
-  /* left */
-  /* right */
+  /* left */ /* right */
   /* bottom */
 }
 {
@@ -16,6 +15,7 @@
   hasPrev;
   /* top */
   /* left */
+
   /* right */
   /* bottom */
   hasNext;
diff --git a/packages/babel-traverse/test/removal.js b/packages/babel-traverse/test/removal.js
--- a/packages/babel-traverse/test/removal.js
+++ b/packages/babel-traverse/test/removal.js
@@ -46,6 +46,87 @@ describe("removal", function () {
     expect(generate(ast).code).toBe("");
   });
 
+  it("leading comments", function () {
+    const ast = parse(`
+    // update-tsconfig-file
+    const a = 5;
+    // const updateTSConfig = require('../update-tsconfig')
+    // https://nextjs.org/docs/app/building-your-application/upgrading/from-vite
+    const getValue = (a) => a.value;
+    getValue();
+`);
+    traverse(ast, {
+      VariableDeclarator: function (path) {
+        path.remove();
+      },
+    });
+
+    expect(ast.program.body[0].leadingComments).toMatchInlineSnapshot(`
+      Array [
+        Object {
+          "end": 28,
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 27,
+              "index": 28,
+              "line": 2,
+            },
+            "filename": undefined,
+            "identifierName": undefined,
+            "start": Position {
+              "column": 4,
+              "index": 5,
+              "line": 2,
+            },
+          },
+          "start": 5,
+          "type": "CommentLine",
+          "value": " update-tsconfig-file",
+        },
+        Object {
+          "end": 105,
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 59,
+              "index": 105,
+              "line": 4,
+            },
+            "filename": undefined,
+            "identifierName": undefined,
+            "start": Position {
+              "column": 4,
+              "index": 50,
+              "line": 4,
+            },
+          },
+          "start": 50,
+          "type": "CommentLine",
+          "value": " const updateTSConfig = require('../update-tsconfig')",
+        },
+        Object {
+          "end": 186,
+          "loc": SourceLocation {
+            "end": Position {
+              "column": 80,
+              "index": 186,
+              "line": 5,
+            },
+            "filename": undefined,
+            "identifierName": undefined,
+            "start": Position {
+              "column": 4,
+              "index": 110,
+              "line": 5,
+            },
+          },
+          "start": 110,
+          "type": "CommentLine",
+          "value": " https://nextjs.org/docs/app/building-your-application/upgrading/from-vite",
+        },
+      ]
+    `);
+  });
+
   describe("within an IfStatement", function () {
     it("does not make consequent null", function () {
       const rootPath = getPath("if (x) foo(); else bar();");
diff --git a/packages/babel-traverse/test/replacement.js b/packages/babel-traverse/test/replacement.js
--- a/packages/babel-traverse/test/replacement.js
+++ b/packages/babel-traverse/test/replacement.js
@@ -342,13 +342,13 @@ describe("path/replacement", function () {
         const node = parseStmt("{ return }");
         path.replaceExpressionWithStatements([undefinedNode, node]);
         expect(path.toString()).toMatchInlineSnapshot(`
-        "function () {
-          undefined;
-          {
-            return;
-          }
-        }()"
-      `);
+          "function () {
+            undefined;
+            {
+              return;
+            }
+          }()"
+        `);
       });
     });
   });