Skip to content

Commit

Permalink
[New] no-duplicates: support inline type import with `inlineTypeImp…
Browse files Browse the repository at this point in the history
…ort` option
  • Loading branch information
snewcomer authored and ljharb committed Jun 10, 2022
1 parent de895ac commit 6304ddc
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 95 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
- Add [`no-empty-named-blocks`] rule ([#2568], thanks [@guilhermelimak])
- [`prefer-default-export`]: add "target" option ([#2602], thanks [@azyzz228])
- [`no-absolute-path`]: add fixer ([#2613], thanks [@adipascu])
- [`no-duplicates`]: support inline type import with `inlineTypeImport` option ([#2475], thanks [@snewcomer])

### Fixed
- [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311])
Expand Down Expand Up @@ -1045,6 +1046,7 @@ for info on changes for earlier releases.
[#2506]: https://github.com/import-js/eslint-plugin-import/pull/2506
[#2503]: https://github.com/import-js/eslint-plugin-import/pull/2503
[#2490]: https://github.com/import-js/eslint-plugin-import/pull/2490
[#2475]: https://github.com/import-js/eslint-plugin-import/pull/2475
[#2473]: https://github.com/import-js/eslint-plugin-import/pull/2473
[#2466]: https://github.com/import-js/eslint-plugin-import/pull/2466
[#2459]: https://github.com/import-js/eslint-plugin-import/pull/2459
Expand Down Expand Up @@ -1760,6 +1762,7 @@ for info on changes for earlier releases.
[@singles]: https://github.com/singles
[@skozin]: https://github.com/skozin
[@skyrpex]: https://github.com/skyrpex
[@snewcomer]: https://github.com/snewcomer
[@sompylasar]: https://github.com/sompylasar
[@soryy708]: https://github.com/soryy708
[@sosukesuzuki]: https://github.com/sosukesuzuki
Expand Down
27 changes: 27 additions & 0 deletions docs/rules/no-duplicates.md
Expand Up @@ -67,6 +67,33 @@ import SomeDefaultClass from './mod?minify'
import * from './mod.js?minify'
```

### Inline Type imports

TypeScript 4.5 introduced a new [feature](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5/#type-on-import-names) that allows mixing of named value and type imports. In order to support fixing to an inline type import when duplicate imports are detected, `prefer-inline` can be set to true.

Config:

```json
"import/no-duplicates": ["error", {"prefer-inline": true}]
```

<!--tabs-->

❌ Invalid `["error", "prefer-inline"]`

```js
import { AValue, type AType } from './mama-mia'
import type { BType } from './mama-mia'
```

✅ Valid with `["error", "prefer-inline"]`

```js
import { AValue, type AType, type BType } from './mama-mia'
```

<!--tabs-->

## When Not To Use It

If the core ESLint version is good enough (i.e. you're _not_ using Flow and you _are_ using [`import/extensions`](./extensions.md)), keep it and don't use this.
Expand Down
25 changes: 21 additions & 4 deletions src/rules/no-duplicates.js
@@ -1,13 +1,15 @@
import resolve from 'eslint-module-utils/resolve';
import docsUrl from '../docsUrl';
import semver from 'semver';
import typescriptPkg from 'typescript/package.json';

function checkImports(imported, context) {
for (const [module, nodes] of imported.entries()) {
if (nodes.length > 1) {
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,
Expand All @@ -25,7 +27,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
Expand Down Expand Up @@ -108,10 +110,19 @@ function getFix(first, rest, sourceCode) {

const [specifiersText] = specifiers.reduce(
([result, needsComma], specifier) => {
const isTypeSpecifier = specifier.importNode.importKind === 'type';

const preferInline = context.options[0] && context.options[0]['prefer-inline'];
// a user might set prefer-inline but not have a supporting TypeScript version. Flow does not support inline types so this should fail in that case as well.
if (preferInline && !semver.satisfies(typescriptPkg.version, '>= 4.5')) {
throw new Error('Your version of TypeScript does not support inline type imports.');
}

const insertText = `${preferInline && isTypeSpecifier ? 'type ' : ''}${specifier.text}`;
return [
needsComma && !specifier.isEmpty
? `${result},${specifier.text}`
: `${result}${specifier.text}`,
? `${result},${insertText}`
: `${result}${insertText}`,
specifier.isEmpty ? needsComma : true,
];
},
Expand Down Expand Up @@ -257,6 +268,9 @@ module.exports = {
considerQueryString: {
type: 'boolean',
},
'prefer-inline': {
type: 'boolean',
},
},
additionalProperties: false,
},
Expand Down Expand Up @@ -291,6 +305,9 @@ module.exports = {
if (n.importKind === 'type') {
return n.specifiers.length > 0 && n.specifiers[0].type === 'ImportDefaultSpecifier' ? map.defaultTypesImported : map.namedTypesImported;
}
if (n.specifiers.some((spec) => spec.importKind === 'type')) {
return map.namedTypesImported;
}

return hasNamespace(n) ? map.nsImported : map.imported;
}
Expand Down

0 comments on commit 6304ddc

Please sign in to comment.