Skip to content

Commit 2cf2a68

Browse files
Xunnamiusljharb
authored andcommitted
[New] order: enable advanced spacing and sorting of type-only imports
1 parent f0727a6 commit 2cf2a68

File tree

2 files changed

+3050
-188
lines changed

2 files changed

+3050
-188
lines changed

src/rules/order.js

Lines changed: 182 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -513,33 +513,59 @@ 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
}
529-
if (typeof rank === 'undefined') {
535+
536+
if (rank === undefined) {
530537
rank = ranks.groups[impType];
538+
539+
if (rank === undefined) {
540+
return -1;
541+
}
531542
}
543+
544+
if (isTypeOnlyImport && isSortingTypesAmongThemselves) {
545+
rank = ranks.groups.type + rank / 10;
546+
}
547+
532548
if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) {
533549
rank += 100;
534550
}
535551

536552
return rank;
537553
}
538554

539-
function registerNode(context, importEntry, ranks, imported, excludedImportTypes) {
540-
const rank = computeRank(context, ranks, importEntry, excludedImportTypes);
555+
function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesAmongThemselves) {
556+
const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves);
541557
if (rank !== -1) {
542-
imported.push({ ...importEntry, rank });
558+
let importNode = importEntry.node;
559+
560+
if (importEntry.type === 'require' && importNode.parent.parent.type === 'VariableDeclaration') {
561+
importNode = importNode.parent.parent;
562+
}
563+
564+
imported.push({
565+
...importEntry,
566+
rank,
567+
isMultiline: importNode.loc.end.line !== importNode.loc.start.line,
568+
});
543569
}
544570
}
545571

@@ -665,7 +691,7 @@ function removeNewLineAfterImport(context, currentImport, previousImport) {
665691
return undefined;
666692
}
667693

668-
function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup) {
694+
function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, newlinesBetweenTypeOnlyImports, distinctGroup, isSortingTypesAmongThemselves, isConsolidatingSpaceBetweenImports) {
669695
const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
670696
const linesBetweenImports = getSourceCode(context).lines.slice(
671697
previousImport.node.loc.end.line,
@@ -678,35 +704,126 @@ function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, di
678704
let previousImport = imported[0];
679705

680706
imported.slice(1).forEach(function (currentImport) {
681-
const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport);
682-
const isStartOfDistinctGroup = getIsStartOfDistinctGroup(currentImport, previousImport);
707+
const emptyLinesBetween = getNumberOfEmptyLinesBetween(
708+
currentImport,
709+
previousImport,
710+
);
711+
712+
const isStartOfDistinctGroup = getIsStartOfDistinctGroup(
713+
currentImport,
714+
previousImport,
715+
);
716+
717+
const isTypeOnlyImport = currentImport.node.importKind === 'type';
718+
const isPreviousImportTypeOnlyImport = previousImport.node.importKind === 'type';
719+
720+
const isNormalImportNextToTypeOnlyImportAndRelevant = isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesAmongThemselves;
721+
722+
const isTypeOnlyImportAndRelevant = isTypeOnlyImport && isSortingTypesAmongThemselves;
723+
724+
// In the special case where newlinesBetweenTypeOnlyImports and
725+
// consolidateIslands want the opposite thing, consolidateIslands wins
726+
const newlinesBetweenTypeOnly = newlinesBetweenTypeOnlyImports === 'never'
727+
&& isConsolidatingSpaceBetweenImports
728+
&& isSortingTypesAmongThemselves
729+
&& (isNormalImportNextToTypeOnlyImportAndRelevant
730+
|| previousImport.isMultiline
731+
|| currentImport.isMultiline)
732+
? 'always-and-inside-groups'
733+
: newlinesBetweenTypeOnlyImports;
734+
735+
const isNotIgnored = isTypeOnlyImportAndRelevant
736+
&& newlinesBetweenTypeOnly !== 'ignore'
737+
|| !isTypeOnlyImportAndRelevant
738+
&& newlinesBetweenImports !== 'ignore';
739+
740+
if (isNotIgnored) {
741+
const shouldAssertNewlineBetweenGroups = (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant)
742+
&& (
743+
newlinesBetweenTypeOnly === 'always'
744+
|| newlinesBetweenTypeOnly === 'always-and-inside-groups'
745+
)
746+
|| !isTypeOnlyImportAndRelevant
747+
&& !isNormalImportNextToTypeOnlyImportAndRelevant
748+
&& (
749+
newlinesBetweenImports === 'always'
750+
|| newlinesBetweenImports === 'always-and-inside-groups'
751+
);
752+
753+
const shouldAssertNoNewlineWithinGroup = (isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant)
754+
&& newlinesBetweenTypeOnly !== 'always-and-inside-groups'
755+
|| !isTypeOnlyImportAndRelevant
756+
&& !isNormalImportNextToTypeOnlyImportAndRelevant
757+
&& newlinesBetweenImports !== 'always-and-inside-groups';
758+
759+
const shouldAssertNoNewlineBetweenGroup = !isSortingTypesAmongThemselves
760+
|| !isNormalImportNextToTypeOnlyImportAndRelevant
761+
|| newlinesBetweenTypeOnly === 'never';
762+
763+
const isTheNewlineBetweenImportsInTheSameGroup = distinctGroup
764+
&& currentImport.rank === previousImport.rank
765+
|| !distinctGroup
766+
&& !isStartOfDistinctGroup;
767+
768+
// Let's try to cut down on linting errors sent to the user
769+
let alreadyReported = false;
770+
771+
if (shouldAssertNewlineBetweenGroups) {
772+
if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
773+
if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) {
774+
alreadyReported = true;
775+
context.report({
776+
node: previousImport.node,
777+
message: 'There should be at least one empty line between import groups',
778+
fix: fixNewLineAfterImport(context, previousImport),
779+
});
780+
}
781+
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup) {
782+
if (isTheNewlineBetweenImportsInTheSameGroup) {
783+
alreadyReported = true;
784+
context.report({
785+
node: previousImport.node,
786+
message: 'There should be no empty line within import group',
787+
fix: removeNewLineAfterImport(context, currentImport, previousImport),
788+
});
789+
}
790+
}
791+
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup) {
792+
alreadyReported = true;
793+
context.report({
794+
node: previousImport.node,
795+
message: 'There should be no empty line between import groups',
796+
fix: removeNewLineAfterImport(context, currentImport, previousImport),
797+
});
798+
}
683799

684-
if (newlinesBetweenImports === 'always'
685-
|| newlinesBetweenImports === 'always-and-inside-groups') {
686-
if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
687-
if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) {
800+
if (!alreadyReported && isConsolidatingSpaceBetweenImports) {
801+
if (emptyLinesBetween === 0 && currentImport.isMultiline) {
688802
context.report({
689803
node: previousImport.node,
690-
message: 'There should be at least one empty line between import groups',
804+
message: 'There should be at least one empty line between this import and the multi-line import that follows it',
691805
fix: fixNewLineAfterImport(context, previousImport),
692806
});
693-
}
694-
} else if (emptyLinesBetween > 0
695-
&& newlinesBetweenImports !== 'always-and-inside-groups') {
696-
if (distinctGroup && currentImport.rank === previousImport.rank || !distinctGroup && !isStartOfDistinctGroup) {
807+
} else if (emptyLinesBetween === 0 && previousImport.isMultiline) {
808+
context.report({
809+
node: previousImport.node,
810+
message: 'There should be at least one empty line between this multi-line import and the import that follows it',
811+
fix: fixNewLineAfterImport(context, previousImport),
812+
});
813+
} else if (
814+
emptyLinesBetween > 0
815+
&& !previousImport.isMultiline
816+
&& !currentImport.isMultiline
817+
&& isTheNewlineBetweenImportsInTheSameGroup
818+
) {
697819
context.report({
698820
node: previousImport.node,
699-
message: 'There should be no empty line within import group',
821+
message:
822+
'There should be no empty lines between this single-line import and the single-line import that follows it',
700823
fix: removeNewLineAfterImport(context, currentImport, previousImport),
701824
});
702825
}
703826
}
704-
} else if (emptyLinesBetween > 0) {
705-
context.report({
706-
node: previousImport.node,
707-
message: 'There should be no empty line between import groups',
708-
fix: removeNewLineAfterImport(context, currentImport, previousImport),
709-
});
710827
}
711828

712829
previousImport = currentImport;
@@ -781,6 +898,24 @@ module.exports = {
781898
'never',
782899
],
783900
},
901+
'newlines-between-types': {
902+
enum: [
903+
'ignore',
904+
'always',
905+
'always-and-inside-groups',
906+
'never',
907+
],
908+
},
909+
consolidateIslands: {
910+
enum: [
911+
'inside-groups',
912+
'never',
913+
],
914+
},
915+
sortTypesAmongThemselves: {
916+
type: 'boolean',
917+
default: false,
918+
},
784919
named: {
785920
default: false,
786921
oneOf: [{
@@ -836,7 +971,10 @@ module.exports = {
836971
create(context) {
837972
const options = context.options[0] || {};
838973
const newlinesBetweenImports = options['newlines-between'] || 'ignore';
974+
const newlinesBetweenTypeOnlyImports = options['newlines-between-types'] || newlinesBetweenImports;
839975
const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']);
976+
const sortTypesAmongThemselves = options.sortTypesAmongThemselves;
977+
const consolidateIslands = options.consolidateIslands || 'never';
840978

841979
const named = {
842980
types: 'mixed',
@@ -879,6 +1017,9 @@ module.exports = {
8791017
const importMap = new Map();
8801018
const exportMap = new Map();
8811019

1020+
const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
1021+
const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves;
1022+
8821023
function getBlockImports(node) {
8831024
if (!importMap.has(node)) {
8841025
importMap.set(node, []);
@@ -932,6 +1073,7 @@ module.exports = {
9321073
ranks,
9331074
getBlockImports(node.parent),
9341075
pathGroupsExcludedImportTypes,
1076+
isSortingTypesAmongThemselves,
9351077
);
9361078

9371079
if (named.import) {
@@ -983,6 +1125,7 @@ module.exports = {
9831125
ranks,
9841126
getBlockImports(node.parent),
9851127
pathGroupsExcludedImportTypes,
1128+
isSortingTypesAmongThemselves,
9861129
);
9871130
},
9881131
CallExpression(node) {
@@ -1005,6 +1148,7 @@ module.exports = {
10051148
ranks,
10061149
getBlockImports(block),
10071150
pathGroupsExcludedImportTypes,
1151+
isSortingTypesAmongThemselves,
10081152
);
10091153
},
10101154
...named.require && {
@@ -1092,8 +1236,18 @@ module.exports = {
10921236
},
10931237
'Program:exit'() {
10941238
importMap.forEach((imported) => {
1095-
if (newlinesBetweenImports !== 'ignore') {
1096-
makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup);
1239+
if (newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore') {
1240+
makeNewlinesBetweenReport(
1241+
context,
1242+
imported,
1243+
newlinesBetweenImports,
1244+
newlinesBetweenTypeOnlyImports,
1245+
distinctGroup,
1246+
isSortingTypesAmongThemselves,
1247+
consolidateIslands === 'inside-groups'
1248+
&& (newlinesBetweenImports === 'always-and-inside-groups'
1249+
|| newlinesBetweenTypeOnlyImports === 'always-and-inside-groups'),
1250+
);
10971251
}
10981252

10991253
if (alphabetize.order !== 'ignore') {

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