Skip to content

Commit 6304ddc

Browse files
snewcomerljharb
authored andcommitted
[New] no-duplicates: support inline type import with inlineTypeImport option
1 parent de895ac commit 6304ddc

File tree

4 files changed

+267
-95
lines changed

4 files changed

+267
-95
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1919
- Add [`no-empty-named-blocks`] rule ([#2568], thanks [@guilhermelimak])
2020
- [`prefer-default-export`]: add "target" option ([#2602], thanks [@azyzz228])
2121
- [`no-absolute-path`]: add fixer ([#2613], thanks [@adipascu])
22+
- [`no-duplicates`]: support inline type import with `inlineTypeImport` option ([#2475], thanks [@snewcomer])
2223

2324
### Fixed
2425
- [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311])
@@ -1045,6 +1046,7 @@ for info on changes for earlier releases.
10451046
[#2506]: https://github.com/import-js/eslint-plugin-import/pull/2506
10461047
[#2503]: https://github.com/import-js/eslint-plugin-import/pull/2503
10471048
[#2490]: https://github.com/import-js/eslint-plugin-import/pull/2490
1049+
[#2475]: https://github.com/import-js/eslint-plugin-import/pull/2475
10481050
[#2473]: https://github.com/import-js/eslint-plugin-import/pull/2473
10491051
[#2466]: https://github.com/import-js/eslint-plugin-import/pull/2466
10501052
[#2459]: https://github.com/import-js/eslint-plugin-import/pull/2459
@@ -1760,6 +1762,7 @@ for info on changes for earlier releases.
17601762
[@singles]: https://github.com/singles
17611763
[@skozin]: https://github.com/skozin
17621764
[@skyrpex]: https://github.com/skyrpex
1765+
[@snewcomer]: https://github.com/snewcomer
17631766
[@sompylasar]: https://github.com/sompylasar
17641767
[@soryy708]: https://github.com/soryy708
17651768
[@sosukesuzuki]: https://github.com/sosukesuzuki

docs/rules/no-duplicates.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,33 @@ import SomeDefaultClass from './mod?minify'
6767
import * from './mod.js?minify'
6868
```
6969

70+
### Inline Type imports
71+
72+
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.
73+
74+
Config:
75+
76+
```json
77+
"import/no-duplicates": ["error", {"prefer-inline": true}]
78+
```
79+
80+
<!--tabs-->
81+
82+
❌ Invalid `["error", "prefer-inline"]`
83+
84+
```js
85+
import { AValue, type AType } from './mama-mia'
86+
import type { BType } from './mama-mia'
87+
```
88+
89+
✅ Valid with `["error", "prefer-inline"]`
90+
91+
```js
92+
import { AValue, type AType, type BType } from './mama-mia'
93+
```
94+
95+
<!--tabs-->
96+
7097
## When Not To Use It
7198

7299
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.

src/rules/no-duplicates.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import resolve from 'eslint-module-utils/resolve';
22
import docsUrl from '../docsUrl';
3+
import semver from 'semver';
4+
import typescriptPkg from 'typescript/package.json';
35

46
function checkImports(imported, context) {
57
for (const [module, nodes] of imported.entries()) {
68
if (nodes.length > 1) {
79
const message = `'${module}' imported multiple times.`;
810
const [first, ...rest] = nodes;
911
const sourceCode = context.getSourceCode();
10-
const fix = getFix(first, rest, sourceCode);
12+
const fix = getFix(first, rest, sourceCode, context);
1113

1214
context.report({
1315
node: first.source,
@@ -25,7 +27,7 @@ function checkImports(imported, context) {
2527
}
2628
}
2729

28-
function getFix(first, rest, sourceCode) {
30+
function getFix(first, rest, sourceCode, context) {
2931
// Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports
3032
// requires multiple `fixer.whatever()` calls in the `fix`: We both need to
3133
// update the first one, and remove the rest. Support for multiple
@@ -108,10 +110,19 @@ function getFix(first, rest, sourceCode) {
108110

109111
const [specifiersText] = specifiers.reduce(
110112
([result, needsComma], specifier) => {
113+
const isTypeSpecifier = specifier.importNode.importKind === 'type';
114+
115+
const preferInline = context.options[0] && context.options[0]['prefer-inline'];
116+
// 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.
117+
if (preferInline && !semver.satisfies(typescriptPkg.version, '>= 4.5')) {
118+
throw new Error('Your version of TypeScript does not support inline type imports.');
119+
}
120+
121+
const insertText = `${preferInline && isTypeSpecifier ? 'type ' : ''}${specifier.text}`;
111122
return [
112123
needsComma && !specifier.isEmpty
113-
? `${result},${specifier.text}`
114-
: `${result}${specifier.text}`,
124+
? `${result},${insertText}`
125+
: `${result}${insertText}`,
115126
specifier.isEmpty ? needsComma : true,
116127
];
117128
},
@@ -257,6 +268,9 @@ module.exports = {
257268
considerQueryString: {
258269
type: 'boolean',
259270
},
271+
'prefer-inline': {
272+
type: 'boolean',
273+
},
260274
},
261275
additionalProperties: false,
262276
},
@@ -291,6 +305,9 @@ module.exports = {
291305
if (n.importKind === 'type') {
292306
return n.specifiers.length > 0 && n.specifiers[0].type === 'ImportDefaultSpecifier' ? map.defaultTypesImported : map.namedTypesImported;
293307
}
308+
if (n.specifiers.some((spec) => spec.importKind === 'type')) {
309+
return map.namedTypesImported;
310+
}
294311

295312
return hasNamespace(n) ? map.nsImported : map.imported;
296313
}

0 commit comments

Comments
 (0)