Repository: import-js/eslint-plugin-import
Pull request #2436 (2022-04-14 09:43:58)

[Fix] `no-unresolved`, `extensions`: ignore type-only exports
In #2220 and #2270, a fix was released that ignored type-only imports for the rules `no-unresolved` and `extensions` respectively.

However type-only **ex**ports are not ignored.

The rules could always be tricked by this code anyway:

```typescript
import type { A } from 'non-existing';

export { A };
```

the code above is equivalent to

```typescript
export type { A } from 'non-existing`;
```

but the second one would throw an error while the first one does not.

This PR fixes this inconsistencies.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfa64512..e349c4a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 - [`no-cycle`]: add ExportNamedDeclaration statements to dependencies ([#2511], thanks [@BenoitZugmeyer])
 - [`dynamic-import-chunkname`]: prevent false report on a valid webpack magic comment ([#2330], thanks [@mhmadhamster])
 - [`export`]: do not error on TS export overloads ([#1590], thanks [@ljharb])
+- [`no-unresolved`], [`extensions`]: ignore type only exports ([#2436], thanks [@Lukas-Kullmann])
 
 ### Changed
 - [Tests] [`named`]: Run all TypeScript test ([#2427], thanks [@ProdigySim])
@@ -1014,6 +1015,7 @@ for info on changes for earlier releases.
 [#2466]: https://github.com/import-js/eslint-plugin-import/pull/2466
 [#2440]: https://github.com/import-js/eslint-plugin-import/pull/2440
 [#2438]: https://github.com/import-js/eslint-plugin-import/pull/2438
+[#2436]: https://github.com/import-js/eslint-plugin-import/pull/2436
 [#2427]: https://github.com/import-js/eslint-plugin-import/pull/2427
 [#2424]: https://github.com/import-js/eslint-plugin-import/pull/2424
 [#2419]: https://github.com/import-js/eslint-plugin-import/pull/2419
@@ -1645,6 +1647,7 @@ for info on changes for earlier releases.
 [@loganfsmyth]: https://github.com/loganfsmyth
 [@luczsoma]: https://github.com/luczsoma
 [@ludofischer]: https://github.com/ludofischer
+[@Lukas-Kullmann]: https://github.com/Lukas-Kullmann
 [@lukeapage]: https://github.com/lukeapage
 [@lydell]: https://github.com/lydell
 [@magarcia]: https://github.com/magarcia
diff --git a/src/rules/extensions.js b/src/rules/extensions.js
index 8596cbfd..9dad56f8 100644
--- a/src/rules/extensions.js
+++ b/src/rules/extensions.js
@@ -138,7 +138,7 @@ module.exports = {
     function checkFileExtension(source, node) {
       // bail if the declaration doesn't have a source, e.g. "export { foo };", or if it's only partially typed like in an editor
       if (!source || !source.value) return;
-      
+
       const importPathWithQueryString = source.value;
 
       // don't enforce anything on builtins
@@ -164,8 +164,8 @@ module.exports = {
       ) || isScoped(importPath);
 
       if (!extension || !importPath.endsWith(`.${extension}`)) {
-        // ignore type-only imports
-        if (node.importKind === 'type') return;
+        // ignore type-only imports and exports
+        if (node.importKind === 'type' || node.exportKind === 'type') return;
         const extensionRequired = isUseOfExtensionRequired(extension, isPackage);
         const extensionForbidden = isUseOfExtensionForbidden(extension);
         if (extensionRequired && !extensionForbidden) {
diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js
index b9dae97c..dafc7cb1 100644
--- a/src/rules/no-unresolved.js
+++ b/src/rules/no-unresolved.js
@@ -27,8 +27,8 @@ module.exports = {
     const options = context.options[0] || {};
 
     function checkSourceValue(source, node) {
-      // ignore type-only imports
-      if (node.importKind === 'type') {
+      // ignore type-only imports and exports
+      if (node.importKind === 'type' || node.exportKind === 'type') {
         return;
       }
 


Test patch:
diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js
index cf93fac9..45b4498f 100644
--- a/tests/src/rules/extensions.js
+++ b/tests/src/rules/extensions.js
@@ -613,6 +613,14 @@ describe('TypeScript', () => {
             ],
             parser,
           }),
+          test({
+            code: 'export type { MyType } from "./typescript-declare";',
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never' },
+            ],
+            parser,
+          }),
         ],
         invalid: [
           test({
@@ -624,6 +632,15 @@ describe('TypeScript', () => {
             ],
             parser,
           }),
+          test({
+            code: 'export { MyType } from "./typescript-declare";',
+            errors: ['Missing file extension for "./typescript-declare"'],
+            options: [
+              'always',
+              { ts: 'never', tsx: 'never', js: 'never', jsx: 'never' },
+            ],
+            parser,
+          }),
         ],
       });
     });
diff --git a/tests/src/rules/no-unresolved.js b/tests/src/rules/no-unresolved.js
index 198d4616..f5245a6b 100644
--- a/tests/src/rules/no-unresolved.js
+++ b/tests/src/rules/no-unresolved.js
@@ -451,6 +451,10 @@ context('TypeScript', () => {
           code: 'import type { JSONSchema7Type } from "@types/json-schema";',
           parser,
         }),
+        test({
+          code: 'export type { JSONSchema7Type } from "@types/json-schema";',
+          parser,
+        }),
       ],
       invalid: [
         test({
@@ -458,6 +462,11 @@ context('TypeScript', () => {
           errors: [ "Unable to resolve path to module '@types/json-schema'." ],
           parser,
         }),
+        test({
+          code: 'export { JSONSchema7Type } from "@types/json-schema";',
+          errors: [ "Unable to resolve path to module '@types/json-schema'." ],
+          parser,
+        }),
       ],
     });
   });