Skip to content

Commit 997b5b2

Browse files
committed
feat: [import/order] allow intragroup sorting of type-only imports via sortTypesAmongThemselves
Closes #2912 #2347 #2441 Subsumes #2615
1 parent 8b2d570 commit 997b5b2

File tree

3 files changed

+253
-5
lines changed

3 files changed

+253
-5
lines changed

docs/rules/order.md

+43
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,49 @@ import type { G } from './aaa.js';
996996
import type { H } from './bbb';
997997
```
998998

999+
### `sortTypesAmongThemselves: true|false`
1000+
1001+
Sort type-only imports separately from normal non-type imports.
1002+
1003+
When enabled, the intragroup sort order of type-only imports will mirror the intergroup ordering of normal imports as defined by `group`, `pathGroups`, etc.
1004+
1005+
> This setting is only meaningful when `"type"` is included in `groups`.
1006+
1007+
Given the following settings:
1008+
1009+
```ts
1010+
{
1011+
groups: ['type', 'builtin', 'parent', 'sibling', 'index']
1012+
}
1013+
```
1014+
1015+
This example will fail the rule check:
1016+
1017+
```ts
1018+
import type A from "fs";
1019+
import type B from "path";
1020+
import type C from "../foo.js";
1021+
import type D from "./bar.js";
1022+
import type E from './';
1023+
1024+
import a from "fs";
1025+
import b from "path";
1026+
import c from "../foo.js";
1027+
import d from "./bar.js";
1028+
import e from "./";
1029+
```
1030+
1031+
However, if we add `sortTypesAmongThemselves: true`:
1032+
1033+
```ts
1034+
{
1035+
groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
1036+
sortTypesAmongThemselves: true
1037+
}
1038+
```
1039+
1040+
The same example will pass.
1041+
9991042
## Related
10001043

10011044
- [`import/external-module-folders`][29]

src/rules/order.js

+28-5
Original file line numberDiff line numberDiff line change
@@ -513,31 +513,43 @@ function computePathRank(ranks, pathGroups, path, maxPosition) {
513513
}
514514
}
515515

516-
function computeRank(context, ranks, importEntry, excludedImportTypes) {
516+
function computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves) {
517517
let impType;
518518
let rank;
519+
520+
const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
521+
const isTypeOnlyImport = importEntry.node.importKind === 'type';
522+
const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes.has('type')
523+
519524
if (importEntry.type === 'import:object') {
520525
impType = 'object';
521-
} else if (importEntry.node.importKind === 'type' && ranks.omittedTypes.indexOf('type') === -1) {
526+
} else if (isTypeOnlyImport && isTypeGroupInGroups && !isSortingTypesAmongThemselves) {
522527
impType = 'type';
523528
} else {
524529
impType = importType(importEntry.value, context);
525530
}
526-
if (!excludedImportTypes.has(impType)) {
531+
532+
if (!excludedImportTypes.has(impType) && !isExcludedFromPathRank) {
527533
rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition);
528534
}
535+
529536
if (typeof rank === 'undefined') {
530537
rank = ranks.groups[impType];
531538
}
539+
540+
if (isTypeOnlyImport && isSortingTypesAmongThemselves) {
541+
rank = ranks.groups['type'] + rank / 10;
542+
}
543+
532544
if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) {
533545
rank += 100;
534546
}
535547

536548
return rank;
537549
}
538550

539-
function registerNode(context, importEntry, ranks, imported, excludedImportTypes) {
540-
const rank = computeRank(context, ranks, importEntry, excludedImportTypes);
551+
function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesAmongThemselves) {
552+
const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves);
541553
if (rank !== -1) {
542554
imported.push({ ...importEntry, rank });
543555
}
@@ -781,6 +793,10 @@ module.exports = {
781793
'never',
782794
],
783795
},
796+
sortTypesAmongThemselves: {
797+
type: 'boolean',
798+
default: false,
799+
},
784800
named: {
785801
default: false,
786802
oneOf: [{
@@ -837,6 +853,7 @@ module.exports = {
837853
const options = context.options[0] || {};
838854
const newlinesBetweenImports = options['newlines-between'] || 'ignore';
839855
const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']);
856+
const sortTypesAmongThemselves = options.sortTypesAmongThemselves;
840857

841858
const named = {
842859
types: 'mixed',
@@ -879,6 +896,9 @@ module.exports = {
879896
const importMap = new Map();
880897
const exportMap = new Map();
881898

899+
const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
900+
const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves;
901+
882902
function getBlockImports(node) {
883903
if (!importMap.has(node)) {
884904
importMap.set(node, []);
@@ -932,6 +952,7 @@ module.exports = {
932952
ranks,
933953
getBlockImports(node.parent),
934954
pathGroupsExcludedImportTypes,
955+
isSortingTypesAmongThemselves
935956
);
936957

937958
if (named.import) {
@@ -983,6 +1004,7 @@ module.exports = {
9831004
ranks,
9841005
getBlockImports(node.parent),
9851006
pathGroupsExcludedImportTypes,
1007+
isSortingTypesAmongThemselves
9861008
);
9871009
},
9881010
CallExpression(node) {
@@ -1005,6 +1027,7 @@ module.exports = {
10051027
ranks,
10061028
getBlockImports(block),
10071029
pathGroupsExcludedImportTypes,
1030+
isSortingTypesAmongThemselves
10081031
);
10091032
},
10101033
...named.require && {

tests/src/rules/order.js

+182
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,188 @@ context('TypeScript', function () {
32853285
}],
32863286
}),
32873287
] : [],
3288+
// Option sortTypesAmongThemselves: false (default)
3289+
test({
3290+
code: `
3291+
import c from 'Bar';
3292+
import a from 'foo';
3293+
3294+
import type { C } from 'dirA/Bar';
3295+
import b from 'dirA/bar';
3296+
import type { D } from 'dirA/bar';
3297+
3298+
import index from './';
3299+
3300+
import type { AA } from 'abc';
3301+
import type { A } from 'foo';
3302+
`,
3303+
...parserConfig,
3304+
options: [
3305+
{
3306+
alphabetize: { order: 'asc' },
3307+
groups: ['external', 'internal', 'index', 'type'],
3308+
pathGroups: [
3309+
{
3310+
pattern: 'dirA/**',
3311+
group: 'internal',
3312+
},
3313+
],
3314+
'newlines-between': 'always',
3315+
pathGroupsExcludedImportTypes: [],
3316+
},
3317+
],
3318+
}),
3319+
test({
3320+
code: `
3321+
import c from 'Bar';
3322+
import a from 'foo';
3323+
3324+
import type { C } from 'dirA/Bar';
3325+
import b from 'dirA/bar';
3326+
import type { D } from 'dirA/bar';
3327+
3328+
import index from './';
3329+
3330+
import type { AA } from 'abc';
3331+
import type { A } from 'foo';
3332+
`,
3333+
...parserConfig,
3334+
options: [
3335+
{
3336+
alphabetize: { order: 'asc' },
3337+
groups: ['external', 'internal', 'index', 'type'],
3338+
pathGroups: [
3339+
{
3340+
pattern: 'dirA/**',
3341+
group: 'internal',
3342+
},
3343+
],
3344+
'newlines-between': 'always',
3345+
pathGroupsExcludedImportTypes: [],
3346+
sortTypesAmongThemselves: false,
3347+
},
3348+
],
3349+
}),
3350+
// Option sortTypesAmongThemselves: true and 'type' in pathGroupsExcludedImportTypes
3351+
test({
3352+
code: `
3353+
import c from 'Bar';
3354+
import a from 'foo';
3355+
3356+
import b from 'dirA/bar';
3357+
3358+
import index from './';
3359+
3360+
import type { AA } from 'abc';
3361+
import type { C } from 'dirA/Bar';
3362+
import type { D } from 'dirA/bar';
3363+
import type { A } from 'foo';
3364+
`,
3365+
...parserConfig,
3366+
options: [
3367+
{
3368+
alphabetize: { order: 'asc' },
3369+
groups: ['external', 'internal', 'index', 'type'],
3370+
pathGroups: [
3371+
{
3372+
pattern: 'dirA/**',
3373+
group: 'internal',
3374+
},
3375+
],
3376+
'newlines-between': 'always',
3377+
pathGroupsExcludedImportTypes: ['type'],
3378+
sortTypesAmongThemselves: true,
3379+
},
3380+
],
3381+
}),
3382+
// Option sortTypesAmongThemselves: true and 'type' omitted from groups
3383+
test({
3384+
code: `
3385+
import c from 'Bar';
3386+
import type { AA } from 'abc';
3387+
import a from 'foo';
3388+
import type { A } from 'foo';
3389+
3390+
import type { C } from 'dirA/Bar';
3391+
import b from 'dirA/bar';
3392+
import type { D } from 'dirA/bar';
3393+
3394+
import index from './';
3395+
`,
3396+
...parserConfig,
3397+
options: [
3398+
{
3399+
alphabetize: { order: 'asc' },
3400+
groups: ['external', 'internal', 'index'],
3401+
pathGroups: [
3402+
{
3403+
pattern: 'dirA/**',
3404+
group: 'internal',
3405+
},
3406+
],
3407+
'newlines-between': 'always',
3408+
pathGroupsExcludedImportTypes: [],
3409+
// Becomes a no-op without "type" in groups
3410+
sortTypesAmongThemselves: true,
3411+
},
3412+
],
3413+
}),
3414+
test({
3415+
code: `
3416+
import c from 'Bar';
3417+
import type { AA } from 'abc';
3418+
import a from 'foo';
3419+
import type { A } from 'foo';
3420+
3421+
import type { C } from 'dirA/Bar';
3422+
import b from 'dirA/bar';
3423+
import type { D } from 'dirA/bar';
3424+
3425+
import index from './';
3426+
`,
3427+
...parserConfig,
3428+
options: [
3429+
{
3430+
alphabetize: { order: 'asc' },
3431+
groups: ['external', 'internal', 'index'],
3432+
pathGroups: [
3433+
{
3434+
pattern: 'dirA/**',
3435+
group: 'internal',
3436+
},
3437+
],
3438+
'newlines-between': 'always',
3439+
pathGroupsExcludedImportTypes: [],
3440+
},
3441+
],
3442+
}),
3443+
// Option: sortTypesAmongThemselves: true puts type imports in the same order as regular imports (from issue #2441, PR #2615)
3444+
test({
3445+
code: `
3446+
import type A from "fs";
3447+
import type B from "path";
3448+
import type C from "../foo.js";
3449+
import type D from "./bar.js";
3450+
import type E from './';
3451+
3452+
import a from "fs";
3453+
import b from "path";
3454+
import c from "../foo.js";
3455+
import d from "./bar.js";
3456+
import e from "./";
3457+
`,
3458+
...parserConfig,
3459+
options: [
3460+
{
3461+
groups: ['type', 'builtin', 'parent', 'sibling', 'index'],
3462+
alphabetize: {
3463+
order: 'asc',
3464+
caseInsensitive: true,
3465+
},
3466+
sortTypesAmongThemselves: true,
3467+
},
3468+
],
3469+
}),
32883470
),
32893471
invalid: [].concat(
32903472
// Option alphabetize: {order: 'asc'}

0 commit comments

Comments
 (0)