Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] no-duplicates: support inline type import with prefer-inline option #2475

Merged
merged 1 commit into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be the source of error for a few folks


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