diff --git a/package.json b/package.json index 023d481126..97ea133479 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "escope": "^3.6.0", "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint-import-resolver-node": "file:./resolvers/node", - "eslint-import-resolver-typescript": "^1.0.2 || ^1.1.1", + "eslint-import-resolver-typescript": "^1.0.2 || ^2.7.0", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-import-test-order-redirect": "file:./tests/files/order-redirect", "eslint-module-utils": "file:./utils", @@ -92,7 +92,7 @@ "safe-publish-latest": "^2.0.0", "semver": "^6.3.0", "sinon": "^2.4.1", - "typescript": "^2.8.1 || ~3.9.5", + "typescript": "^2.8.1 || ^3.9.5 || ~4.7.3", "typescript-eslint-parser": "^15 || ^20 || ^22" }, "peerDependencies": { diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index efd9583fbc..10998c833d 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -7,7 +7,7 @@ function checkImports(imported, context) { const message = `'${module}' imported multiple times.`; const [first, ...rest] = nodes; const sourceCode = context.getSourceCode(); - const fix = getFix(first, rest, sourceCode); + const fix = getFix(first, rest, sourceCode, context); context.report({ node: first.source, @@ -25,7 +25,7 @@ function checkImports(imported, context) { } } -function getFix(first, rest, sourceCode) { +function getFix(first, rest, sourceCode, context) { // Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports // requires multiple `fixer.whatever()` calls in the `fix`: We both need to // update the first one, and remove the rest. Support for multiple @@ -108,10 +108,17 @@ function getFix(first, rest, sourceCode) { const [specifiersText] = specifiers.reduce( ([result, needsComma], specifier) => { + let insertText = specifier.text; + const isTypeSpecifier = specifier.importNode.importKind === 'type'; + const inlineTypeImport = context.options[0] && + context.options[0]['inlineTypeImport']; + if (inlineTypeImport && isTypeSpecifier) { + insertText = `type ${insertText}`; + } return [ needsComma && !specifier.isEmpty - ? `${result},${specifier.text}` - : `${result}${specifier.text}`, + ? `${result},${insertText}` + : `${result}${insertText}`, specifier.isEmpty ? needsComma : true, ]; }, @@ -255,6 +262,9 @@ module.exports = { considerQueryString: { type: 'boolean', }, + inlineTypeImport: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -288,6 +298,8 @@ module.exports = { const map = moduleMaps.get(n.parent); if (n.importKind === 'type') { return n.specifiers.length > 0 && n.specifiers[0].type === 'ImportDefaultSpecifier' ? map.defaultTypesImported : map.namedTypesImported; + } else if (n.specifiers.some((spec) => spec.importKind === 'type')) { + return map.namedTypesImported; } return hasNamespace(n) ? map.nsImported : map.imported; diff --git a/tests/src/rules/no-duplicates.js b/tests/src/rules/no-duplicates.js index cde41b3a07..97dfe3413d 100644 --- a/tests/src/rules/no-duplicates.js +++ b/tests/src/rules/no-duplicates.js @@ -430,7 +430,7 @@ context('TypeScript', function () { ruleTester.run('no-duplicates', rule, { valid: [ - // #1667: ignore duplicate if is a typescript type import + // #1667: ignore duplicate if is a typescript type import test({ code: "import type { x } from './foo'; import y from './foo'", ...parserConfig, @@ -468,6 +468,19 @@ context('TypeScript', function () { `, ...parserConfig, }), + // #2470: ignore duplicate if is a typescript inline type import + test({ + code: "import { type x } from './foo'; import y from './foo'", + ...parserConfig, + }), + test({ + code: "import { type x } from './foo'; import { y } from './foo'", + ...parserConfig, + }), + test({ + code: "import { type x } from './foo'; import type y from 'foo'", + ...parserConfig, + }), ], invalid: [ test({ @@ -520,6 +533,76 @@ context('TypeScript', function () { }, ], }), + test({ + code: "import {type x} from './foo'; import type {y} from './foo'", + ...parserConfig, + options: [{ 'inlineTypeImport': false }], + 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.", + }, + ], + }), + test({ + code: "import {type x} from 'foo'; import type {y} from 'foo'", + ...parserConfig, + options: [{ 'inlineTypeImport': true }], + output: `import {type x,type y} from 'foo'; `, + errors: [ + { + line: 1, + column: 22, + message: "'foo' imported multiple times.", + }, + { + line: 1, + column: 50, + message: "'foo' imported multiple times.", + }, + ], + }), + test({ + code: "import {type x} from './foo'; import {type y} from './foo'", + ...parserConfig, + output: `import {type x,type y} from './foo'; `, + errors: [ + { + line: 1, + column: 22, + message: "'./foo' imported multiple times.", + }, + { + line: 1, + column: 52, + message: "'./foo' imported multiple times.", + }, + ], + }), + test({ + code: "import {AValue, type x, BValue} from './foo'; import {type y} from './foo'", + ...parserConfig, + output: `import {AValue, type x, BValue,type y} from './foo'; `, + errors: [ + { + line: 1, + column: 38, + message: "'./foo' imported multiple times.", + }, + { + line: 1, + column: 68, + message: "'./foo' imported multiple times.", + }, + ], + }), ], }); });