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] order: collapse excess spacing for aesthetically pleasing imports via consolidateIslands #3129

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
210 changes: 181 additions & 29 deletions src/rules/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,33 +513,59 @@
}
}

function computeRank(context, ranks, importEntry, excludedImportTypes) {
function computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves) {

Check warning on line 516 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L516

Added line #L516 was not covered by tests
let impType;
let rank;

const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
const isTypeOnlyImport = importEntry.node.importKind === 'type';
const isExcludedFromPathRank = isTypeOnlyImport && isTypeGroupInGroups && excludedImportTypes.has('type')

Check warning on line 522 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L520-L522

Added lines #L520 - L522 were not covered by tests

if (importEntry.type === 'import:object') {
impType = 'object';
} else if (importEntry.node.importKind === 'type' && ranks.omittedTypes.indexOf('type') === -1) {
} else if (isTypeOnlyImport && isTypeGroupInGroups && !isSortingTypesAmongThemselves) {

Check warning on line 526 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L526

Added line #L526 was not covered by tests
impType = 'type';
} else {
impType = importType(importEntry.value, context);
}
if (!excludedImportTypes.has(impType)) {

if (!excludedImportTypes.has(impType) && !isExcludedFromPathRank) {

Check warning on line 532 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L532

Added line #L532 was not covered by tests
rank = computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition);
}
if (typeof rank === 'undefined') {

if (rank === undefined) {

Check warning on line 536 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L536

Added line #L536 was not covered by tests
rank = ranks.groups[impType];

if(rank === undefined) {
return -1;

Check warning on line 540 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L539-L540

Added lines #L539 - L540 were not covered by tests
}
}

if (isTypeOnlyImport && isSortingTypesAmongThemselves) {
rank = ranks.groups['type'] + rank / 10;

Check warning on line 545 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L544-L545

Added lines #L544 - L545 were not covered by tests
}

if (importEntry.type !== 'import' && !importEntry.type.startsWith('import:')) {
rank += 100;
}

return rank;
}

function registerNode(context, importEntry, ranks, imported, excludedImportTypes) {
const rank = computeRank(context, ranks, importEntry, excludedImportTypes);
function registerNode(context, importEntry, ranks, imported, excludedImportTypes, isSortingTypesAmongThemselves) {
const rank = computeRank(context, ranks, importEntry, excludedImportTypes, isSortingTypesAmongThemselves);

Check warning on line 556 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L555-L556

Added lines #L555 - L556 were not covered by tests
if (rank !== -1) {
imported.push({ ...importEntry, rank });
let importNode = importEntry.node;

Check warning on line 558 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L558

Added line #L558 was not covered by tests

if(importEntry.type === 'require' && importNode.parent.parent.type === 'VariableDeclaration') {
importNode = importNode.parent.parent;

Check warning on line 561 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L560-L561

Added lines #L560 - L561 were not covered by tests
}

imported.push({

Check warning on line 564 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L564

Added line #L564 was not covered by tests
...importEntry,
rank,
isMultiline: importNode.loc.end.line !== importNode.loc.start.line
});
}
}

Expand Down Expand Up @@ -665,7 +691,7 @@
return undefined;
}

function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup) {
function makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, newlinesBetweenTypeOnlyImports_, distinctGroup, isSortingTypesAmongThemselves, isConsolidatingSpaceBetweenImports) {

Check warning on line 694 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L694

Added line #L694 was not covered by tests
const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
const linesBetweenImports = getSourceCode(context).lines.slice(
previousImport.node.loc.end.line,
Expand All @@ -678,35 +704,124 @@
let previousImport = imported[0];

imported.slice(1).forEach(function (currentImport) {
const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport);
const isStartOfDistinctGroup = getIsStartOfDistinctGroup(currentImport, previousImport);
const emptyLinesBetween = getNumberOfEmptyLinesBetween(

Check warning on line 707 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L707

Added line #L707 was not covered by tests
currentImport,
previousImport
);

const isStartOfDistinctGroup = getIsStartOfDistinctGroup(

Check warning on line 712 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L712

Added line #L712 was not covered by tests
currentImport,
previousImport
);

if (newlinesBetweenImports === 'always'
|| newlinesBetweenImports === 'always-and-inside-groups') {
if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) {
const isTypeOnlyImport = currentImport.node.importKind === 'type';
const isPreviousImportTypeOnlyImport = previousImport.node.importKind === 'type';

Check warning on line 718 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L717-L718

Added lines #L717 - L718 were not covered by tests

const isNormalImportNextToTypeOnlyImportAndRelevant =
isTypeOnlyImport !== isPreviousImportTypeOnlyImport && isSortingTypesAmongThemselves;

Check warning on line 721 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L721

Added line #L721 was not covered by tests

const isTypeOnlyImportAndRelevant =
isTypeOnlyImport && isSortingTypesAmongThemselves;

Check warning on line 724 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L724

Added line #L724 was not covered by tests

// In the special case where newlinesBetweenTypeOnlyImports and
// consolidateIslands want the opposite thing, consolidateIslands wins
const newlinesBetweenTypeOnlyImports =
newlinesBetweenTypeOnlyImports_ === 'never' &&
isConsolidatingSpaceBetweenImports &&
isSortingTypesAmongThemselves &&
(isNormalImportNextToTypeOnlyImportAndRelevant ||
previousImport.isMultiline ||
currentImport.isMultiline)
? 'always-and-inside-groups'
: newlinesBetweenTypeOnlyImports_;

Check warning on line 736 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L729-L736

Added lines #L729 - L736 were not covered by tests

const isNotIgnored =
(isTypeOnlyImportAndRelevant &&
newlinesBetweenTypeOnlyImports !== 'ignore') ||
(!isTypeOnlyImportAndRelevant && newlinesBetweenImports !== 'ignore');

Check warning on line 741 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L739-L741

Added lines #L739 - L741 were not covered by tests

if(isNotIgnored) {

Check warning on line 743 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L743

Added line #L743 was not covered by tests
const shouldAssertNewlineBetweenGroups =
((isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) &&
(newlinesBetweenTypeOnlyImports === 'always' ||
newlinesBetweenTypeOnlyImports === 'always-and-inside-groups')) ||
((!isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant) &&
(newlinesBetweenImports === 'always' ||
newlinesBetweenImports === 'always-and-inside-groups'));

Check warning on line 750 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L745-L750

Added lines #L745 - L750 were not covered by tests

const shouldAssertNoNewlineWithinGroup =
((isTypeOnlyImportAndRelevant || isNormalImportNextToTypeOnlyImportAndRelevant) &&
(newlinesBetweenTypeOnlyImports !== 'always-and-inside-groups')) ||
((!isTypeOnlyImportAndRelevant && !isNormalImportNextToTypeOnlyImportAndRelevant) &&
(newlinesBetweenImports !== 'always-and-inside-groups'));

Check warning on line 756 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L753-L756

Added lines #L753 - L756 were not covered by tests

const shouldAssertNoNewlineBetweenGroup =
!isSortingTypesAmongThemselves ||
!isNormalImportNextToTypeOnlyImportAndRelevant ||
newlinesBetweenTypeOnlyImports === 'never';

Check warning on line 761 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L759-L761

Added lines #L759 - L761 were not covered by tests

const isTheNewlineBetweenImportsInTheSameGroup = (distinctGroup && currentImport.rank === previousImport.rank) ||
(!distinctGroup && !isStartOfDistinctGroup);

Check warning on line 764 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L763-L764

Added lines #L763 - L764 were not covered by tests

// Let's try to cut down on linting errors sent to the user
let alreadyReported = false;

Check warning on line 767 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L767

Added line #L767 was not covered by tests

if (shouldAssertNewlineBetweenGroups) {
if (currentImport.rank !== previousImport.rank && emptyLinesBetween === 0) {
if (distinctGroup || !distinctGroup && isStartOfDistinctGroup) {
alreadyReported = true;
context.report({

Check warning on line 773 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L769-L773

Added lines #L769 - L773 were not covered by tests
node: previousImport.node,
message: 'There should be at least one empty line between import groups',
fix: fixNewLineAfterImport(context, previousImport),
});
}
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineWithinGroup) {
if (isTheNewlineBetweenImportsInTheSameGroup) {
alreadyReported = true;
context.report({

Check warning on line 782 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L779-L782

Added lines #L779 - L782 were not covered by tests
node: previousImport.node,
message: 'There should be no empty line within import group',
fix: removeNewLineAfterImport(context, currentImport, previousImport)
});
}
}
} else if (emptyLinesBetween > 0 && shouldAssertNoNewlineBetweenGroup) {
alreadyReported = true;
context.report({

Check warning on line 791 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L789-L791

Added lines #L789 - L791 were not covered by tests
node: previousImport.node,
message: 'There should be no empty line between import groups',
fix: removeNewLineAfterImport(context, currentImport, previousImport),
});
}

if(!alreadyReported && isConsolidatingSpaceBetweenImports) {
if(emptyLinesBetween === 0 && currentImport.isMultiline) {

Check warning on line 799 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L798-L799

Added lines #L798 - L799 were not covered by tests
context.report({
node: previousImport.node,
message: 'There should be at least one empty line between import groups',
message: 'There should be at least one empty line between this import and the multi-line import that follows it',
fix: fixNewLineAfterImport(context, previousImport),
});
}
} else if (emptyLinesBetween > 0
&& newlinesBetweenImports !== 'always-and-inside-groups') {
if (distinctGroup && currentImport.rank === previousImport.rank || !distinctGroup && !isStartOfDistinctGroup) {
} else if(emptyLinesBetween === 0 && previousImport.isMultiline) {

Check warning on line 805 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L805

Added line #L805 was not covered by tests
context.report({
node: previousImport.node,
message: 'There should be no empty line within import group',
fix: removeNewLineAfterImport(context, currentImport, previousImport),
message: 'There should be at least one empty line between this multi-line import and the import that follows it',
fix: fixNewLineAfterImport(context, previousImport),
});
} else if (
emptyLinesBetween > 0 &&
!previousImport.isMultiline &&
!currentImport.isMultiline &&
isTheNewlineBetweenImportsInTheSameGroup

Check warning on line 815 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L811-L815

Added lines #L811 - L815 were not covered by tests
) {
context.report({

Check warning on line 817 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L817

Added line #L817 was not covered by tests
node: previousImport.node,
message:
'There should be no empty lines between this single-line import and the single-line import that follows it',
fix: removeNewLineAfterImport(context, currentImport, previousImport)
});
}
}
} else if (emptyLinesBetween > 0) {
context.report({
node: previousImport.node,
message: 'There should be no empty line between import groups',
fix: removeNewLineAfterImport(context, currentImport, previousImport),
});
}

previousImport = currentImport;
Expand Down Expand Up @@ -781,6 +896,24 @@
'never',
],
},
'newlines-between-types': {
enum: [
'ignore',
'always',
'always-and-inside-groups',
'never',
],
},
consolidateIslands: {
enum: [
'inside-groups',
'never',
],
},
sortTypesAmongThemselves: {
type: 'boolean',
default: false,
},
named: {
default: false,
oneOf: [{
Expand Down Expand Up @@ -836,7 +969,10 @@
create(context) {
const options = context.options[0] || {};
const newlinesBetweenImports = options['newlines-between'] || 'ignore';
const newlinesBetweenTypeOnlyImports = options['newlines-between-types'] || newlinesBetweenImports;

Check warning on line 972 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L972

Added line #L972 was not covered by tests
const pathGroupsExcludedImportTypes = new Set(options.pathGroupsExcludedImportTypes || ['builtin', 'external', 'object']);
const sortTypesAmongThemselves = options.sortTypesAmongThemselves;
const consolidateIslands = options.consolidateIslands || 'never';

Check warning on line 975 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L974-L975

Added lines #L974 - L975 were not covered by tests

const named = {
types: 'mixed',
Expand Down Expand Up @@ -879,6 +1015,9 @@
const importMap = new Map();
const exportMap = new Map();

const isTypeGroupInGroups = ranks.omittedTypes.indexOf('type') === -1;
const isSortingTypesAmongThemselves = isTypeGroupInGroups && sortTypesAmongThemselves;

Check warning on line 1019 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L1018-L1019

Added lines #L1018 - L1019 were not covered by tests

function getBlockImports(node) {
if (!importMap.has(node)) {
importMap.set(node, []);
Expand Down Expand Up @@ -932,6 +1071,7 @@
ranks,
getBlockImports(node.parent),
pathGroupsExcludedImportTypes,
isSortingTypesAmongThemselves
);

if (named.import) {
Expand Down Expand Up @@ -983,6 +1123,7 @@
ranks,
getBlockImports(node.parent),
pathGroupsExcludedImportTypes,
isSortingTypesAmongThemselves
);
},
CallExpression(node) {
Expand All @@ -1005,6 +1146,7 @@
ranks,
getBlockImports(block),
pathGroupsExcludedImportTypes,
isSortingTypesAmongThemselves
);
},
...named.require && {
Expand Down Expand Up @@ -1092,8 +1234,18 @@
},
'Program:exit'() {
importMap.forEach((imported) => {
if (newlinesBetweenImports !== 'ignore') {
makeNewlinesBetweenReport(context, imported, newlinesBetweenImports, distinctGroup);
if (newlinesBetweenImports !== 'ignore' || newlinesBetweenTypeOnlyImports !== 'ignore') {
makeNewlinesBetweenReport(

Check warning on line 1238 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L1237-L1238

Added lines #L1237 - L1238 were not covered by tests
context,
imported,
newlinesBetweenImports,
newlinesBetweenTypeOnlyImports,
distinctGroup,
isSortingTypesAmongThemselves,
consolidateIslands === 'inside-groups' &&
(newlinesBetweenImports === 'always-and-inside-groups' ||
newlinesBetweenTypeOnlyImports === 'always-and-inside-groups')

Check warning on line 1247 in src/rules/order.js

View check run for this annotation

Codecov / codecov/patch

src/rules/order.js#L1245-L1247

Added lines #L1245 - L1247 were not covered by tests
);
}

if (alphabetize.order !== 'ignore') {
Expand Down
Loading
Loading