Skip to content

Commit bf9c5e8

Browse files
committed
feat: [import/order] allow intragroup sorting of type-only imports via sortTypesAmongThemselves
Closes import-js#2912 import-js#2347 import-js#2441 Subsumes import-js#2615
1 parent 341178d commit bf9c5e8

File tree

2 files changed

+210
-5
lines changed

2 files changed

+210
-5
lines changed

src/rules/order.js

Lines changed: 28 additions & 5 deletions
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

Lines changed: 182 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy