diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f595d8ec..2b4f8b4b73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Fixed - [`no-duplicates`]: ensure autofix avoids excessive newlines ([#2028], thanks [@ertrzyiks]) +- [`no-duplicates`]: type import fixer now generates valid TypeScript - [`extensions`]: avoid crashing on partially typed import/export statements ([#2118], thanks [@ljharb]) - [`no-extraneous-dependencies`]: add ESM intermediate package.json support] ([#2121], thanks [@paztis]) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index 43a29506be..3c88ab9ebb 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -276,13 +276,23 @@ 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) { // 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); @@ -294,7 +304,8 @@ module.exports = { 'Program:exit': function () { checkImports(imported, context); checkImports(nsImported, context); - checkImports(typesImported, context); + checkImports(defaultTypesImported, context); + checkImports(namedTypesImported, context); }, }; }, diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index d16f37b103..0ad76a2460 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -437,8 +437,92 @@ context('TypeScript', function() { }, parserConfig, ), + test( + { + code: "import type x from './foo'; import type y from './bar'", + parser, + output: null, + }, + parserConfig, + ), + test( + { + code: "import type {x} from './foo'; import type {y} from './bar'", + parser, + output: null, + }, + parserConfig, + ), + test( + { + code: "import type x from './foo'; import type {y} from './foo'", + parser, + output: null, + }, + parserConfig, + ), + ], + invalid: [ + test( + { + code: "import type x from './foo'; import type y from './foo'", + parser, + errors: [ + { + line: 1, + column: 20, + message: "'./foo' imported multiple times.", + }, + { + line: 1, + column: 48, + message: "'./foo' imported multiple times.", + }, + ], + }, + parserConfig, + ), + test( + { + code: "import type x from './foo'; import type x from './foo'", + output: "import type x from './foo'; ", + parser, + errors: [ + { + line: 1, + column: 20, + message: "'./foo' imported multiple times.", + }, + { + line: 1, + column: 48, + message: "'./foo' imported multiple times.", + }, + ], + }, + parserConfig, + ), + test( + { + code: "import type {x} from './foo'; import type {y} from './foo'", + parser, + 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.", + }, + ], + }, + parserConfig, + ), ], - invalid: [], }); }); });