Repository: babel/babel (TypeScript)

Issue:
[Bug]: `export { type <ident> }` doesn't mark `<ident>` as a type
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Other (Next.js, Gatsby, vue-cli, ...)

### Input code

```ts
import { A } from "./a.js";
export { type A };
```

### Configuration file name

_No response_

### Configuration

_No response_

### Current and expected behavior

Current:

```js
import { A } from "./a.js";
export {};
```

Expected:

```js
export {};
```

### Environment

Repl as of 2023-03-13

### Possible solution

Mark `<ident>` as a type, just like you would with `const a: <ident> = 1;`.

### Additional context

TS has the correct behaviour: https://www.typescriptlang.org/play?isolatedModules=true#code/PTAECMDoBcGcCgCWBbADgewE7VAb1AIKgC+oAZpusqAESTACGkAVrDQNzwCmAHhtnlDQAnqi6ES7IA

SWC has the same bug: https://github.com/swc-project/swc/issues/7076


Hints:
Hey @lucacasonato! 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.
<!-- ALERT!!!!!!!
    Before submitting this comment, please:
      1. Write where to start searching the bug
      2. Write where to add a new test
    See https://github.com/babel/babel/issues/9563#issuecomment-466799807 for an example
-->

This is a bug in the logic that `@babel/plugin-transform-typescript` uses to determine if an imported binding is only referenced in type positions, that happens at https://github.com/babel/babel/blob/48089fb29faf9c167c5ef2130f92a2f652b6d378/packages/babel-plugin-transform-typescript/src/index.ts#L676-L691

The problem is probably that, when we introduced the `export { type A }` syntax we forgot to consider it in the `isInType` function used above.

If anyone wants to contribute a fix, you can use https://astexplorer.net/ to see the shape of the AST generated by Babel (make sure to select JavaScript as the language, `@babel/parser` as the parser, and to enable the TypeScript plugin in `@babel/parser`'s options!) and add it to the cases in `isInType`.

If it is the first time that you contribute to Babel, follow these steps: (you need to have `make` and `yarn` available on your machine)
1. **Write a comment here** to let other possible contributors know that you are working on this bug.
1. Fork the repo
1. Run `git clone https://github.com/<YOUR_USERNAME>/babel.git && cd babel`
1. Run `yarn && make bootstrap`
1. Wait :hourglass_flowing_sand: 
1. Run `make watch` (or `make build` whenever you change a file)
1. Add a test in https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-typescript/test/fixtures/imports, copying one of the existing import elision tests (only `input.ts`; `output.js` will be automatically generated)
1. Update the code!
1. `yarn jest transform-typescript` to run the tests
    - If some test outputs don't match but the new results are correct, you can delete the bad `output.js` files and run the tests again
    - If you prefer, you can run `OVERWRITE=true yarn jest [name-of-the-package-to-test]` and they will be automatically updated.
1. If it is working, run `make test` to run all the tests
1. Run `git push` and open a PR!
It is unsafe to remove imports that have not been manually marked with a type by the user, given the absence of cross-file type information.

Consider the following codes.
```TypeScript
// foo.ts
export class Foo {
    static {
        console.log("Foo");
    }
}
```

```TypeScript
// main.ts
import { Foo } from "./foo";

export { type Foo };
```


TypeScript's behavior here is not affected by the contents of the imported module.
> TypeScript's behavior here is not affected by the contents of the imported module.

TypeScript with verbatimModuleSyntax
https://www.typescriptlang.org/play?isolatedModules=true&verbatimModuleSyntax=true&ts=5.1.0-dev.20230313#code/PTAEAEDcFMCcCMCGAXAlgWwLIHsAmBXAG2gGUBPAO2UQA8AuUZWfaAKBFHgDpkBnVjAAdssZKADeoAIKgAvqABmsbOlAAiLsERcAVrzUBuVtBrDRExmUHRpcg0A
Babel's default behavior is to match TS just with `--isolatedModules`. However, that reminds me that it would be a good idea to also add a test with Babel's [`onlyRemoveTypeImports`](https://babeljs.io/docs/babel-plugin-transform-typescript#onlyremovetypeimports) option, that matches TS's `verbatimModuleSyntax`.

Patch:
diff --git a/packages/babel-plugin-transform-typescript/src/index.ts b/packages/babel-plugin-transform-typescript/src/index.ts
--- a/packages/babel-plugin-transform-typescript/src/index.ts
+++ b/packages/babel-plugin-transform-typescript/src/index.ts
@@ -24,9 +24,12 @@ function isInType(path: NodePath) {
       );
     case "ExportSpecifier":
       return (
+        // export { type foo };
+        path.parent.exportKind === "type" ||
+        // export type { foo };
         // @ts-expect-error: DeclareExportDeclaration does not have `exportKind`
         (path.parentPath as NodePath<t.ExportSpecifier>).parent.exportKind ===
-        "type"
+          "type"
       );
     default:
       return false;


Test patch:
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/input.ts
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/input.ts
@@ -0,0 +1,4 @@
+import { foo, bar, type baz } from "Foo";
+export { type foo };
+export type { bar };
+export { baz };
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/output.mjs
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-1/output.mjs
@@ -0,0 +1 @@
+export {};
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/input.ts b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/input.ts
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/input.ts
@@ -0,0 +1,4 @@
+import { foo, bar, type baz } from "Foo";
+export { type foo };
+export type { bar };
+export { baz };
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/options.json b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/options.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/options.json
@@ -0,0 +1,8 @@
+{
+  "plugins": [[
+    "transform-typescript",
+    {
+      "onlyRemoveTypeImports": true
+    }
+  ]]
+}
diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/output.mjs
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/type-only-export-specifier-2/output.mjs
@@ -0,0 +1,2 @@
+import { foo, bar } from "Foo";
+export {};