Repository: import-js/eslint-plugin-import (js)

Issue:
`no-duplicates` false positive and autofix creates a syntax error
From:

```ts
import type { AccountQuery } from '@netflix-internal/dnajs';
import type dnajs from '@netflix-internal/dnajs';
```

To:

```ts
import dnajs, type { AccountQuery } from '@netflix-internal/dnajs';
```

The proper autocorrect is:

```ts
import type {default as dnajs, AccountQuery} from '@netflix-internal/dnajs';
```

On version `2.20.0` (**edit: and `2.23.4`**)

Another bug:

From: 
```ts
import type Registration from './Registration';
import type { RouteResolver } from './Registration';
```

To: 
```ts
import type Registration, { RouteResolver } from './Registration';
```

This gives TS error:

```
A type-only import can specify a default import or named bindings, but not both.ts(1363)
```

Pull request description:
[Fix] `no-duplicates`: type import fixer generates valid TypeScript 
Fixes #2114, #2130

Note that #2114 suggests the correct fix looks like:
```ts
import type {default as dnajs, AccountQuery} from '@netflix-internal/dnajs';
```

but this PR will generate:
```ts
import type dnajs from '@netflix-internal/dnajs';
import type {AccountQuery} from '@netflix-internal/dnajs';
```

This avoids conflicts with `no-named-default` mentioned in #2130.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e59d2462..a05937ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 ### Fixed
 - `ExportMap`: Add default export when esModuleInterop is true and anything is exported ([#2184], thanks [@Maxim-Mazurok])
 - [`named`], [`namespace`]: properly set reexports on `export * as … from` ([#1998], [#2161], thanks [@ljharb])
+- [`no-duplicates`]: correctly handle case of mixed default/named type imports ([#2149], thanks [@GoodForOneFare], [@nwalters512])
 
 ### Changed
 - [Docs] `max-dependencies`: 📖 Document `ignoreTypeImports` option ([#2196], thanks [@himynameisdave])
@@ -897,6 +898,7 @@ for info on changes for earlier releases.
 [#2160]: https://github.com/import-js/eslint-plugin-import/pull/2160
 [#2158]: https://github.com/import-js/eslint-plugin-import/pull/2158
 [#2156]: https://github.com/import-js/eslint-plugin-import/pull/2156
+[#2149]: https://github.com/benmosher/eslint-plugin-import/pull/2149
 [#2146]: https://github.com/import-js/eslint-plugin-import/pull/2146
 [#2140]: https://github.com/import-js/eslint-plugin-import/pull/2140
 [#2138]: https://github.com/import-js/eslint-plugin-import/pull/2138
@@ -1404,6 +1406,7 @@ for info on changes for earlier releases.
 [@gavriguy]: https://github.com/gavriguy
 [@giodamelio]: https://github.com/giodamelio
 [@golopot]: https://github.com/golopot
+[@GoodForOneFare]: https://github.com/GoodForOneFare
 [@graingert]: https://github.com/graingert
 [@grit96]: https://github.com/grit96
 [@guillaumewuip]: https://github.com/guillaumewuip
@@ -1471,6 +1474,7 @@ for info on changes for earlier releases.
 [@nicolashenry]: https://github.com/nicolashenry
 [@noelebrun]: https://github.com/noelebrun
 [@ntdb]: https://github.com/ntdb
+[@nwalters512]: https://github.com/nwalters512
 [@panrafal]: https://github.com/panrafal
 [@paztis]: https://github.com/paztis
 [@pcorpet]: https://github.com/pcorpet
@@ -1533,4 +1537,4 @@ for info on changes for earlier releases.
 [@wtgtybhertgeghgtwtg]: https://github.com/wtgtybhertgeghgtwtg
 [@xpl]: https://github.com/xpl
 [@yordis]: https://github.com/yordis
-[@zloirock]: https://github.com/zloirock
\ No newline at end of file
+[@zloirock]: https://github.com/zloirock
diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js
index 43a29506..63441019 100644
--- a/src/rules/no-duplicates.js
+++ b/src/rules/no-duplicates.js
@@ -276,13 +276,22 @@ module.exports = {
 
     const imported = new Map();
     const nsImported = new Map();
-    const typesImported = new Map();
+    const defaultTypesImported = new Map();
+    const namedTypesImported = new Map();
+
+    function getImportMap(n) {
+      if (n.importKind === 'type') {
+        return n.specifiers[0].type === 'ImportDefaultSpecifier' ? defaultTypesImported : namedTypesImported;
+      }
+
+      return hasNamespace(n) ? nsImported : imported;
+    }
+
     return {
-      'ImportDeclaration': function (n) {
+      ImportDeclaration(n) {
         // resolved path will cover aliased duplicates
         const resolvedPath = resolver(n.source.value);
-        const importMap = n.importKind === 'type' ? typesImported :
-          (hasNamespace(n) ? nsImported : imported);
+        const importMap = getImportMap(n);
 
         if (importMap.has(resolvedPath)) {
           importMap.get(resolvedPath).push(n);
@@ -291,10 +300,11 @@ module.exports = {
         }
       },
 
-      'Program:exit': function () {
+      'Program:exit'() {
         checkImports(imported, context);
         checkImports(nsImported, context);
-        checkImports(typesImported, context);
+        checkImports(defaultTypesImported, context);
+        checkImports(namedTypesImported, context);
       },
     };
   },


Test patch:
diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js
index d1b9c745..c6b355ab 100644
--- a/tests/src/rules/no-duplicates.js
+++ b/tests/src/rules/no-duplicates.js
@@ -434,8 +434,71 @@ context('TypeScript', function() {
             code: "import type { x } from './foo'; import y from './foo'",
             ...parserConfig,
           }),
+          test({
+            code: "import type x from './foo'; import type y from './bar'",
+            ...parserConfig,
+          }),
+          test({
+            code: "import type {x} from './foo'; import type {y} from './bar'",
+            ...parserConfig,
+          }),
+          test({
+            code: "import type x from './foo'; import type {y} from './foo'",
+            ...parserConfig,
+          }),
+        ],
+        invalid: [
+          test({
+            code: "import type x from './foo'; import type y from './foo'",
+            ...parserConfig,
+            errors: [
+              {
+                line: 1,
+                column: 20,
+                message: "'./foo' imported multiple times.",
+              },
+              {
+                line: 1,
+                column: 48,
+                message: "'./foo' imported multiple times.",
+              },
+            ],
+          }),
+          test({
+            code: "import type x from './foo'; import type x from './foo'",
+            output: "import type x from './foo'; ",
+            ...parserConfig,
+            errors: [
+              {
+                line: 1,
+                column: 20,
+                message: "'./foo' imported multiple times.",
+              },
+              {
+                line: 1,
+                column: 48,
+                message: "'./foo' imported multiple times.",
+              },
+            ],
+          }),
+          test({
+            code: "import type {x} from './foo'; import type {y} from './foo'",
+            ...parserConfig,
+            output: `import type {x,y} from './foo'; `,
+            errors: [
+              {
+                line: 1,
+                column: 22,
+                message: "'./foo' imported multiple times.",
+              },
+              {
+                line: 1,
+                column: 52,
+                message: "'./foo' imported multiple times.",
+              },
+            ],
+          }),
         ],
-        invalid: [],
       });
     });
 });