Skip to content

Commit d225176

Browse files
phryneasljharb
authored andcommitted
[New] extensions: add the checkTypeImports option
1 parent 5a51b9a commit d225176

File tree

4 files changed

+95
-2
lines changed

4 files changed

+95
-2
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
99
### Added
1010
- support eslint v9 ([#2996], thanks [@G-Rath] [@michaelfaith])
1111
- [`order`]: allow validating named imports ([#3043], thanks [@manuth])
12+
- [`extensions`]: add the `checkTypeImports` option ([#2817], thanks [@phryneas])
1213

1314
### Fixed
1415
- `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith])
@@ -1187,6 +1188,7 @@ for info on changes for earlier releases.
11871188
[#2842]: https://github.com/import-js/eslint-plugin-import/pull/2842
11881189
[#2835]: https://github.com/import-js/eslint-plugin-import/pull/2835
11891190
[#2832]: https://github.com/import-js/eslint-plugin-import/pull/2832
1191+
[#2817]: https://github.com/import-js/eslint-plugin-import/pull/2817
11901192
[#2778]: https://github.com/import-js/eslint-plugin-import/pull/2778
11911193
[#2756]: https://github.com/import-js/eslint-plugin-import/pull/2756
11921194
[#2754]: https://github.com/import-js/eslint-plugin-import/pull/2754
@@ -1942,6 +1944,7 @@ for info on changes for earlier releases.
19421944
[@pcorpet]: https://github.com/pcorpet
19431945
[@Pearce-Ropion]: https://github.com/Pearce-Ropion
19441946
[@Pessimistress]: https://github.com/Pessimistress
1947+
[@phryneas]: https://github.com/phryneas
19451948
[@pmcelhaney]: https://github.com/pmcelhaney
19461949
[@preco21]: https://github.com/preco21
19471950
[@pri1311]: https://github.com/pri1311

docs/rules/extensions.md

+18
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ For example, `["error", "never", { "svg": "always" }]` would require that all ex
5656
In that case, if you still want to specify extensions, you can do so inside the **pattern** property.
5757
Default value of `ignorePackages` is `false`.
5858

59+
By default, `import type` and `export type` style imports/exports are ignored. If you want to check them as well, you can set the `checkTypeImports` option to `true`.
60+
5961
### Exception
6062

6163
When disallowing the use of certain extensions this rule makes an exception and allows the use of extension when the file would not be resolvable without extension.
@@ -104,6 +106,14 @@ import express from 'express/index';
104106
import * as path from 'path';
105107
```
106108

109+
The following patterns are considered problems when the configuration is set to "never" and the option "checkTypeImports" is set to `true`:
110+
111+
```js
112+
import type { Foo } from './foo.ts';
113+
114+
export type { Foo } from './foo.ts';
115+
```
116+
107117
The following patterns are considered problems when configuration set to "always":
108118

109119
```js
@@ -167,6 +177,14 @@ import express from 'express';
167177
import foo from '@/foo';
168178
```
169179

180+
The following patterns are considered problems when the configuration is set to "always" and the option "checkTypeImports" is set to `true`:
181+
182+
```js
183+
import type { Foo } from './foo';
184+
185+
export type { Foo } from './foo';
186+
```
187+
170188
## When Not To Use It
171189

172190
If you are not concerned about a consistent usage of file extension.

src/rules/extensions.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const properties = {
1414
type: 'object',
1515
properties: {
1616
pattern: patternProperties,
17+
checkTypeImports: { type: 'boolean' },
1718
ignorePackages: { type: 'boolean' },
1819
},
1920
};
@@ -35,7 +36,7 @@ function buildProperties(context) {
3536
}
3637

3738
// If this is not the new structure, transfer all props to result.pattern
38-
if (obj.pattern === undefined && obj.ignorePackages === undefined) {
39+
if (obj.pattern === undefined && obj.ignorePackages === undefined && obj.checkTypeImports === undefined) {
3940
Object.assign(result.pattern, obj);
4041
return;
4142
}
@@ -49,6 +50,10 @@ function buildProperties(context) {
4950
if (obj.ignorePackages !== undefined) {
5051
result.ignorePackages = obj.ignorePackages;
5152
}
53+
54+
if (obj.checkTypeImports !== undefined) {
55+
result.checkTypeImports = obj.checkTypeImports;
56+
}
5257
});
5358

5459
if (result.defaultConfig === 'ignorePackages') {
@@ -168,7 +173,7 @@ module.exports = {
168173

169174
if (!extension || !importPath.endsWith(`.${extension}`)) {
170175
// ignore type-only imports and exports
171-
if (node.importKind === 'type' || node.exportKind === 'type') { return; }
176+
if (!props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type')) { return; }
172177
const extensionRequired = isUseOfExtensionRequired(extension, isPackage);
173178
const extensionForbidden = isUseOfExtensionForbidden(extension);
174179
if (extensionRequired && !extensionForbidden) {

tests/src/rules/extensions.js

+67
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ import rule from 'rules/extensions';
33
import { getTSParsers, test, testFilePath, parsers } from '../utils';
44

55
const ruleTester = new RuleTester();
6+
const ruleTesterWithTypeScriptImports = new RuleTester({
7+
settings: {
8+
'import/resolver': {
9+
typescript: {
10+
alwaysTryTypes: true,
11+
},
12+
},
13+
},
14+
});
615

716
ruleTester.run('extensions', rule, {
817
valid: [
@@ -689,6 +698,64 @@ describe('TypeScript', () => {
689698
],
690699
parser,
691700
}),
701+
test({
702+
code: 'import type T from "./typescript-declare";',
703+
errors: ['Missing file extension for "./typescript-declare"'],
704+
options: [
705+
'always',
706+
{ ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true },
707+
],
708+
parser,
709+
}),
710+
test({
711+
code: 'export type { MyType } from "./typescript-declare";',
712+
errors: ['Missing file extension for "./typescript-declare"'],
713+
options: [
714+
'always',
715+
{ ts: 'never', tsx: 'never', js: 'never', jsx: 'never', checkTypeImports: true },
716+
],
717+
parser,
718+
}),
719+
],
720+
});
721+
ruleTesterWithTypeScriptImports.run(`${parser}: (with TS resolver) extensions are enforced for type imports/export when checkTypeImports is set`, rule, {
722+
valid: [
723+
test({
724+
code: 'import type { MyType } from "./typescript-declare.ts";',
725+
options: [
726+
'always',
727+
{ checkTypeImports: true },
728+
],
729+
parser,
730+
}),
731+
test({
732+
code: 'export type { MyType } from "./typescript-declare.ts";',
733+
options: [
734+
'always',
735+
{ checkTypeImports: true },
736+
],
737+
parser,
738+
}),
739+
],
740+
invalid: [
741+
test({
742+
code: 'import type { MyType } from "./typescript-declare";',
743+
errors: ['Missing file extension "ts" for "./typescript-declare"'],
744+
options: [
745+
'always',
746+
{ checkTypeImports: true },
747+
],
748+
parser,
749+
}),
750+
test({
751+
code: 'export type { MyType } from "./typescript-declare";',
752+
errors: ['Missing file extension "ts" for "./typescript-declare"'],
753+
options: [
754+
'always',
755+
{ checkTypeImports: true },
756+
],
757+
parser,
758+
}),
692759
],
693760
});
694761
});

0 commit comments

Comments
 (0)