Skip to content

Commit 239d487

Browse files
author
Randall Reed, Jr
committed
Allow secondary alphabetical sorting
* After sorting imports by group, allow sorting alphabetically within a group * import/order rule now accepts sort-order option that may be 'ignore' (default) or 'alphabetical' * Fixes import-js#389
1 parent d9605a0 commit 239d487

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

docs/rules/order.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,42 @@ import index from './';
141141
import sibling from './foo';
142142
```
143143

144+
### `sort-order: [ignore|alphabetical]`:
145+
146+
147+
Enforces alphabetical sorting within import groups:
148+
149+
- If set to `ignore`, no errors related to order within import groups will be reported (default).
150+
- If set to `alphabetical`, imports within a group must be alphabetized. Imports across groups will not be compared.
151+
152+
With the default group setting, the following will be invalid:
153+
154+
```js
155+
/* eslint import/order: ["error", {"sort-order": "alphabetical"}] */
156+
import path from 'path';
157+
import fs from 'fs';
158+
import index from './';
159+
import sibling from './foo';
160+
```
161+
162+
while this will be valid:
163+
164+
```js
165+
/* eslint import/order: ["error", {"sort-order": "alphabetical"}] */
166+
import fs from 'fs';
167+
import path from 'path';
168+
import index from './';
169+
import sibling from './foo';
170+
```
171+
172+
```js
173+
/* eslint import/order: ["error", {"sort-order": "ignore"}] */
174+
import path from 'path';
175+
import fs from 'fs';
176+
import index from './';
177+
import sibling from './foo';
178+
```
179+
144180
## Related
145181

146182
- [`import/external-module-folders`] setting

src/rules/order.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ function findOutOfOrder(imported) {
3131
})
3232
}
3333

34+
function findOutOfAlphabeticalOrder(imported, sortOrder = 'ignore') {
35+
if (imported.length === 0 || sortOrder === 'ignore') {
36+
return []
37+
}
38+
let maxSeenRankNode = imported[0]
39+
let maxSeenAlphabeticalNode = imported[0]
40+
return imported.filter(function (importedModule) {
41+
if (maxSeenRankNode.rank < importedModule.rank) {
42+
maxSeenRankNode = importedModule
43+
// New group, reset max alphabetical node
44+
maxSeenAlphabeticalNode = importedModule
45+
}
46+
const resAlphabetical = (importedModule.name < maxSeenAlphabeticalNode.name)
47+
if (maxSeenAlphabeticalNode.name < importedModule.name) {
48+
maxSeenAlphabeticalNode = importedModule
49+
}
50+
return resAlphabetical
51+
})
52+
}
53+
3454
function reportOutOfOrder(context, imported, outOfOrder, order) {
3555
outOfOrder.forEach(function (imp) {
3656
const found = imported.find(function hasHigherRank(importedItem) {
@@ -41,6 +61,16 @@ function reportOutOfOrder(context, imported, outOfOrder, order) {
4161
})
4262
}
4363

64+
function reportOutOfAlphabeticalOrder(context, imported, outOfOrder, order) {
65+
outOfOrder.forEach(function (imp) {
66+
const found = imported.find(function hasHigherAlphabeticalOrder(importedItem) {
67+
return importedItem.name > imp.name && importedItem.rank === imp.rank
68+
})
69+
context.report(imp.node, '`' + imp.name + '` import should occur ' + order +
70+
' import of `' + found.name + '`')
71+
})
72+
}
73+
4474
function makeOutOfOrderReport(context, imported) {
4575
const outOfOrder = findOutOfOrder(imported)
4676
if (!outOfOrder.length) {
@@ -56,6 +86,21 @@ function makeOutOfOrderReport(context, imported) {
5686
reportOutOfOrder(context, imported, outOfOrder, 'before')
5787
}
5888

89+
function makeOutOfAlphabeticalOrderReport(context, imported, sortOrder) {
90+
const outOfOrder = findOutOfAlphabeticalOrder(imported, sortOrder)
91+
if (!outOfOrder.length) {
92+
return
93+
}
94+
// There are things to report. Try to minimize the number of reported errors.
95+
const reversedImported = reverse(imported)
96+
const reversedOrder = findOutOfAlphabeticalOrder(reversedImported, sortOrder)
97+
if (reversedOrder.length < outOfOrder.length && reversedOrder.length > 0) {
98+
reportOutOfAlphabeticalOrder(context, reversedImported, reversedOrder, 'after')
99+
return
100+
}
101+
reportOutOfAlphabeticalOrder(context, imported, outOfOrder, 'before')
102+
}
103+
59104
// DETECTING
60105

61106
function computeRank(context, ranks, name, type) {
@@ -158,6 +203,9 @@ module.exports = {
158203
'newlines-between': {
159204
enum: [ 'ignore', 'always', 'never' ],
160205
},
206+
'sort-order': {
207+
enum: [ 'alphabetical', 'ignore' ],
208+
},
161209
},
162210
additionalProperties: false,
163211
},
@@ -167,6 +215,7 @@ module.exports = {
167215
create: function importOrderRule (context) {
168216
const options = context.options[0] || {}
169217
const newlinesBetweenImports = options['newlines-between'] || 'ignore'
218+
const sortOrder = options['sort-order'] || 'ignore'
170219
let ranks
171220

172221
try {
@@ -205,6 +254,7 @@ module.exports = {
205254
},
206255
'Program:exit': function reportAndReset() {
207256
makeOutOfOrderReport(context, imported)
257+
makeOutOfAlphabeticalOrderReport(context, imported, sortOrder)
208258

209259
if (newlinesBetweenImports !== 'ignore') {
210260
makeNewlinesBetweenReport(context, imported, newlinesBetweenImports)

tests/src/rules/order.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,39 @@ ruleTester.run('order', rule, {
376376
`,
377377
options: [{ 'newlines-between': 'always' }]
378378
}),
379+
// Alphabetical order
380+
test({
381+
code: `
382+
import fs from 'fs';
383+
import path from 'path';
384+
`,
385+
options: [{ 'sort-order': 'alphabetical' }],
386+
}),
387+
// Alphabetical order with multiple groups
388+
test({
389+
code: `
390+
import fs from 'fs';
391+
import path from 'path';
392+
import async from 'async';
393+
`,
394+
options: [{ 'sort-order': 'alphabetical' }],
395+
}),
396+
// Ignore alphabetical order
397+
test({
398+
code: `
399+
import path from 'path';
400+
import fs from 'fs';
401+
`,
402+
options: [{ 'sort-order': 'ignore' }],
403+
}),
404+
// Ignore alphabetical order across groups
405+
test({
406+
code: `
407+
import fs from 'fs';
408+
import async from 'async';
409+
`,
410+
options: [{ 'sort-order': 'alphabetical' }],
411+
}),
379412
],
380413
invalid: [
381414
// builtin before external module (require)
@@ -760,5 +793,53 @@ ruleTester.run('order', rule, {
760793
},
761794
],
762795
}),
796+
// Bad alphabetical order
797+
test({
798+
code: `
799+
import path from 'path';
800+
import fs from 'fs';
801+
`,
802+
options: [{ 'sort-order': 'alphabetical' }],
803+
errors: [
804+
{
805+
ruleId: 'order',
806+
message: '`fs` import should occur before import of `path`',
807+
},
808+
],
809+
}),
810+
// Good alphabetical order with incorrect group order
811+
test({
812+
code: `
813+
import async from 'async'
814+
import fs from 'fs';
815+
import path from 'path';
816+
`,
817+
options: [{ 'sort-order': 'alphabetical' }],
818+
errors: [
819+
{
820+
ruleId: 'order',
821+
message: '`async` import should occur after import of `path`',
822+
},
823+
],
824+
}),
825+
// Bad alphabetical order with incorrect group order
826+
test({
827+
code: `
828+
import async from 'async'
829+
import path from 'path';
830+
import fs from 'fs';
831+
`,
832+
options: [{ 'sort-order': 'alphabetical' }],
833+
errors: [
834+
{
835+
ruleId: 'order',
836+
message: '`async` import should occur after import of `fs`',
837+
},
838+
{
839+
ruleId: 'order',
840+
message: '`fs` import should occur before import of `path`',
841+
},
842+
],
843+
}),
763844
],
764845
})

0 commit comments

Comments
 (0)