Skip to content

Commit 4f145a2

Browse files
Xunnamiusljharb
authored andcommitted
[New] order: add consolidateIslands option to collapse excess spacing for aesthetically pleasing imports
1 parent 5e7ea1d commit 4f145a2

File tree

4 files changed

+3083
-202
lines changed

4 files changed

+3083
-202
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1212
- [`extensions`]: add `pathGroupOverrides to allow enforcement decision overrides based on specifier ([#3105], thanks [@Xunnamius])
1313
- [`order`]: add `sortTypesGroup` option to allow intragroup sorting of type-only imports ([#3104], thanks [@Xunnamius])
1414
- [`order`]: add `newlines-between-types` option to control intragroup sorting of type-only imports ([#3127], thanks [@Xunnamius])
15+
- [`order`]: add `consolidateIslands` option to collapse excess spacing for aesthetically pleasing imports ([#3129], thanks [@Xunnamius])
1516

1617
### Fixed
1718
- [`no-unused-modules`]: provide more meaningful error message when no .eslintrc is present ([#3116], thanks [@michaelfaith])
@@ -1174,6 +1175,7 @@ for info on changes for earlier releases.
11741175

11751176
[#3151]: https://github.com/import-js/eslint-plugin-import/pull/3151
11761177
[#3138]: https://github.com/import-js/eslint-plugin-import/pull/3138
1178+
[#3129]: https://github.com/import-js/eslint-plugin-import/pull/3129
11771179
[#3128]: https://github.com/import-js/eslint-plugin-import/pull/3128
11781180
[#3127]: https://github.com/import-js/eslint-plugin-import/pull/3127
11791181
[#3125]: https://github.com/import-js/eslint-plugin-import/pull/3125

docs/rules/order.md

+277-9
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ This rule supports the following options (none of which are required):
108108
- [`warnOnUnassignedImports`][5]
109109
- [`sortTypesGroup`][7]
110110
- [`newlines-between-types`][27]
111+
- [`consolidateIslands`][25]
111112

112113
---
113114

@@ -184,7 +185,7 @@ Sometimes [the predefined groups][18] are not fine-grained enough, especially wh
184185
`pathGroups` defines one or more [`PathGroup`][13]s relative to a predefined group.
185186
Imports are associated with a [`PathGroup`][13] based on path matching against the import specifier (using [minimatch][14]).
186187

187-
> \[!IMPORTANT]
188+
> [!IMPORTANT]
188189
>
189190
> Note that, by default, imports grouped as `"builtin"`, `"external"`, or `"object"` will not be considered for further `pathGroups` matching unless they are removed from [`pathGroupsExcludedImportTypes`][9].
190191
@@ -224,7 +225,7 @@ Default: `["builtin", "external", "object"]`
224225
By default, imports in certain [groups][18] are excluded from being matched against [`pathGroups`][8] to prevent overeager sorting.
225226
Use `pathGroupsExcludedImportTypes` to modify which groups are excluded.
226227

227-
> \[!TIP]
228+
> [!TIP]
228229
>
229230
> If using imports with custom specifier aliases (e.g.
230231
> you're using `eslint-import-resolver-alias`, `paths` in `tsconfig.json`, etc) that [end up
@@ -254,7 +255,7 @@ Use `pathGroupsExcludedImportTypes` to modify which groups are excluded.
254255
Valid values: `boolean` \
255256
Default: `true`
256257

257-
> \[!CAUTION]
258+
> [!CAUTION]
258259
>
259260
> Currently, `distinctGroup` defaults to `true`. However, in a later update, the
260261
> default will change to `false`.
@@ -371,7 +372,7 @@ Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false }
371372

372373
Determine the sort order of imports within each [predefined group][18] or [`PathGroup`][8] alphabetically based on specifier.
373374

374-
> \[!NOTE]
375+
> [!NOTE]
375376
>
376377
> Imports will be alphabetized based on their _specifiers_, not by their
377378
> identifiers. For example, `const a = require('z');` will come _after_ `const z = require('a');` when `alphabetize` is set to `{ order: "asc" }`.
@@ -502,7 +503,7 @@ Default: `false`
502503
Warn when "unassigned" imports are out of order.
503504
Unassigned imports are imports with no corresponding identifiers (e.g. `import './my/thing.js'` or `require('./side-effects.js')`).
504505

505-
> \[!NOTE]
506+
> [!NOTE]
506507
>
507508
> These warnings are not fixable with `--fix` since unassigned imports might be used for their [side-effects][31],
508509
> and changing the order of such imports cannot be done safely.
@@ -540,7 +541,7 @@ import './styles.css';
540541
Valid values: `boolean` \
541542
Default: `false`
542543

543-
> \[!NOTE]
544+
> [!NOTE]
544545
>
545546
> This setting is only meaningful when `"type"` is included in [`groups`][18].
546547
@@ -598,7 +599,7 @@ The same example will pass.
598599
Valid values: `"ignore" | "always" | "always-and-inside-groups" | "never"` \
599600
Default: the value of [`newlines-between`][20]
600601

601-
> \[!NOTE]
602+
> [!NOTE]
602603
>
603604
> This setting is only meaningful when [`sortTypesGroup`][7] is enabled.
604605
@@ -656,9 +657,9 @@ The same example will pass.
656657

657658
Note the new line after `import type E from './';` but before `import a from "fs";`. This new line separates the type-only imports from the normal imports. Its existence is governed by [`newlines-between-types`][27] and _not `newlines-between`_.
658659

659-
> \[!IMPORTANT]
660+
> [!IMPORTANT]
660661
>
661-
> In certain situations, `consolidateIslands: true` will take precedence over `newlines-between-types: "never"`, if used, when it comes to the new line separating type-only imports from normal imports.
662+
> In certain situations, [`consolidateIslands: true`][25] will take precedence over `newlines-between-types: "never"`, if used, when it comes to the new line separating type-only imports from normal imports.
662663
663664
The next example will pass even though there's a new line preceding the normal import and [`newlines-between`][20] is set to `"never"`:
664665

@@ -722,6 +723,272 @@ import d from "./bar.js";
722723
import e from "./";
723724
```
724725

726+
### `consolidateIslands`
727+
728+
Valid values: `"inside-groups" | "never"` \
729+
Default: `"never"`
730+
731+
> [!NOTE]
732+
>
733+
> This setting is only meaningful when [`newlines-between`][20] and/or [`newlines-between-types`][27] is set to `"always-and-inside-groups"`.
734+
735+
When set to `"inside-groups"`, this ensures imports spanning multiple lines are separated from other imports with a new line while single-line imports are grouped together (and the space between them consolidated) if they belong to the same [group][18] or [`pathGroups`][8].
736+
737+
> [!IMPORTANT]
738+
>
739+
> When all of the following are true:
740+
>
741+
> - [`sortTypesGroup`][7] is set to `true`
742+
> - `consolidateIslands` is set to `"inside-groups"`
743+
> - [`newlines-between`][20] is set to `"always-and-inside-groups"` when [`newlines-between-types`][27] is set to `"never"` (or vice-versa)
744+
>
745+
> Then [`newlines-between`][20]/[`newlines-between-types`][27] will yield to
746+
> `consolidateIslands` and allow new lines to separate multi-line imports
747+
> regardless of the `"never"` setting.
748+
>
749+
> This configuration is useful, for instance, to keep single-line type-only
750+
> imports stacked tightly together at the bottom of your import block to
751+
> preserve space while still logically organizing normal imports for quick and
752+
> pleasant reference.
753+
754+
#### Example
755+
756+
Given the following settings:
757+
758+
```jsonc
759+
{
760+
"import/order": ["error", {
761+
"newlines-between": "always-and-inside-groups",
762+
"consolidateIslands": "inside-groups"
763+
}]
764+
}
765+
```
766+
767+
This will fail the rule check:
768+
769+
```ts
770+
var fs = require('fs');
771+
var path = require('path');
772+
var { util1, util2, util3 } = require('util');
773+
var async = require('async');
774+
var relParent1 = require('../foo');
775+
var {
776+
relParent21,
777+
relParent22,
778+
relParent23,
779+
relParent24,
780+
} = require('../');
781+
var relParent3 = require('../bar');
782+
var { sibling1,
783+
sibling2, sibling3 } = require('./foo');
784+
var sibling2 = require('./bar');
785+
var sibling3 = require('./foobar');
786+
```
787+
788+
While this will succeed (and is what `--fix` would yield):
789+
790+
```ts
791+
var fs = require('fs');
792+
var path = require('path');
793+
var { util1, util2, util3 } = require('util');
794+
795+
var async = require('async');
796+
797+
var relParent1 = require('../foo');
798+
799+
var {
800+
relParent21,
801+
relParent22,
802+
relParent23,
803+
relParent24,
804+
} = require('../');
805+
806+
var relParent3 = require('../bar');
807+
808+
var { sibling1,
809+
sibling2, sibling3 } = require('./foo');
810+
811+
var sibling2 = require('./bar');
812+
var sibling3 = require('./foobar');
813+
```
814+
815+
Note the intragroup "islands" of grouped single-line imports, as well as multi-line imports, are surrounded by new lines. At the same time, note the typical new lines separating different groups are still maintained thanks to [`newlines-between`][20].
816+
817+
The same holds true for the next example; when given the following settings:
818+
819+
```jsonc
820+
{
821+
"import/order": ["error", {
822+
"alphabetize": { "order": "asc" },
823+
"groups": ["external", "internal", "index", "type"],
824+
"pathGroups": [
825+
{
826+
"pattern": "dirA/**",
827+
"group": "internal",
828+
"position": "after"
829+
},
830+
{
831+
"pattern": "dirB/**",
832+
"group": "internal",
833+
"position": "before"
834+
},
835+
{
836+
"pattern": "dirC/**",
837+
"group": "internal"
838+
}
839+
],
840+
"newlines-between": "always-and-inside-groups",
841+
"newlines-between-types": "never",
842+
"pathGroupsExcludedImportTypes": [],
843+
"sortTypesGroup": true,
844+
"consolidateIslands": "inside-groups"
845+
}]
846+
}
847+
```
848+
849+
> [!IMPORTANT]
850+
>
851+
> **Pay special attention to the value of [`pathGroupsExcludedImportTypes`][9]** in this example's settings.
852+
> Without it, the successful example below would fail.
853+
> This is because the imports with specifiers starting with "dirA/", "dirB/", and "dirC/" are all [considered part of the `"external"` group](#how-imports-are-grouped), and imports in that group are excluded from [`pathGroups`][8] matching by default.
854+
>
855+
> The fix is to remove `"external"` (and, in this example, the others) from [`pathGroupsExcludedImportTypes`][9].
856+
857+
This will fail the rule check:
858+
859+
```ts
860+
import c from 'Bar';
861+
import d from 'bar';
862+
import {
863+
aa,
864+
bb,
865+
cc,
866+
dd,
867+
ee,
868+
ff,
869+
gg
870+
} from 'baz';
871+
import {
872+
hh,
873+
ii,
874+
jj,
875+
kk,
876+
ll,
877+
mm,
878+
nn
879+
} from 'fizz';
880+
import a from 'foo';
881+
import b from 'dirA/bar';
882+
import index from './';
883+
import type { AA,
884+
BB, CC } from 'abc';
885+
import type { Z } from 'fizz';
886+
import type {
887+
A,
888+
B
889+
} from 'foo';
890+
import type { C2 } from 'dirB/Bar';
891+
import type {
892+
D2,
893+
X2,
894+
Y2
895+
} from 'dirB/bar';
896+
import type { E2 } from 'dirB/baz';
897+
import type { C3 } from 'dirC/Bar';
898+
import type {
899+
D3,
900+
X3,
901+
Y3
902+
} from 'dirC/bar';
903+
import type { E3 } from 'dirC/baz';
904+
import type { F3 } from 'dirC/caz';
905+
import type { C1 } from 'dirA/Bar';
906+
import type {
907+
D1,
908+
X1,
909+
Y1
910+
} from 'dirA/bar';
911+
import type { E1 } from 'dirA/baz';
912+
import type { F } from './index.js';
913+
import type { G } from './aaa.js';
914+
import type { H } from './bbb';
915+
```
916+
917+
While this will succeed (and is what `--fix` would yield):
918+
919+
```ts
920+
import c from 'Bar';
921+
import d from 'bar';
922+
923+
import {
924+
aa,
925+
bb,
926+
cc,
927+
dd,
928+
ee,
929+
ff,
930+
gg
931+
} from 'baz';
932+
933+
import {
934+
hh,
935+
ii,
936+
jj,
937+
kk,
938+
ll,
939+
mm,
940+
nn
941+
} from 'fizz';
942+
943+
import a from 'foo';
944+
945+
import b from 'dirA/bar';
946+
947+
import index from './';
948+
949+
import type { AA,
950+
BB, CC } from 'abc';
951+
952+
import type { Z } from 'fizz';
953+
954+
import type {
955+
A,
956+
B
957+
} from 'foo';
958+
959+
import type { C2 } from 'dirB/Bar';
960+
961+
import type {
962+
D2,
963+
X2,
964+
Y2
965+
} from 'dirB/bar';
966+
967+
import type { E2 } from 'dirB/baz';
968+
import type { C3 } from 'dirC/Bar';
969+
970+
import type {
971+
D3,
972+
X3,
973+
Y3
974+
} from 'dirC/bar';
975+
976+
import type { E3 } from 'dirC/baz';
977+
import type { F3 } from 'dirC/caz';
978+
import type { C1 } from 'dirA/Bar';
979+
980+
import type {
981+
D1,
982+
X1,
983+
Y1
984+
} from 'dirA/bar';
985+
986+
import type { E1 } from 'dirA/baz';
987+
import type { F } from './index.js';
988+
import type { G } from './aaa.js';
989+
import type { H } from './bbb';
990+
```
991+
725992
## Related
726993

727994
- [`import/external-module-folders`][29]
@@ -747,6 +1014,7 @@ import e from "./";
7471014
[21]: https://eslint.org/docs/latest/rules/no-multiple-empty-lines
7481015
[22]: https://prettier.io
7491016
[23]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#type-modifiers-on-import-names
1017+
[25]: #consolidateislands
7501018
[27]: #newlines-between-types
7511019
[28]: ../../README.md#importinternal-regex
7521020
[29]: ../../README.md#importexternal-module-folders

0 commit comments

Comments
 (0)