Skip to content

Commit aae9da7

Browse files
duncanbeeversdannysindraRadim Svobodaluczsomaljharb
committed
[New] order/no-extraneous-dependencies: Alphabetize imports within groups
Fixes #1406. Co-Authored-By: dannysindra <[email protected]> Co-Authored-By: Radim Svoboda <[email protected]> Co-Authored-By: Soma Lucz <[email protected]> Co-Authored-By: Jordan Harband <[email protected]>
1 parent f12ae59 commit aae9da7

File tree

4 files changed

+227
-37
lines changed

4 files changed

+227
-37
lines changed

Diff for: CHANGELOG.md

+21-33
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Change Log
2+
23
All notable changes to this project will be documented in this file.
34
This project adheres to [Semantic Versioning](http://semver.org/).
45
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
56

67
## [Unreleased]
7-
88
### Added
99
- [`internal-regex`]: regex pattern for marking packages "internal" ([#1491], thanks [@Librazy])
1010
- [`group-exports`]: make aggregate module exports valid ([#1472], thanks [@atikenny])
@@ -14,6 +14,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1414
- [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann])
1515
- [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu])
1616
- [`no-duplicates`]: Add `considerQueryString` option : allow duplicate imports with different query strings ([#1107], thanks [@pcorpet]).
17+
- [`order`]: Add support for alphabetical sorting of import paths within import groups ([#1360], thanks [@duncanbeevers] & [@stropho] & [@luczsoma])
1718

1819
### Fixed
1920
- [`default`]: make error message less confusing ([#1470], thanks [@golopot])
@@ -34,21 +35,20 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
3435
- [`no-unused-modules`]/`eslint-module-utils`: Avoid superfluous calls and code ([#1551], thanks [@brettz9])
3536

3637
## [2.18.2] - 2019-07-19
38+
### Fixed
3739
- Skip warning on type interfaces ([#1425], thanks [@lencioni])
3840

3941
## [2.18.1] - 2019-07-18
40-
4142
### Fixed
4243
- Improve parse perf when using `@typescript-eslint/parser` ([#1409], thanks [@bradzacher])
4344
- [`prefer-default-export`]: don't warn on TypeAlias & TSTypeAliasDeclaration ([#1377], thanks [@sharmilajesupaul])
4445
- [`no-unused-modules`]: Exclude package "main"/"bin"/"browser" entry points ([#1404], thanks [@rfermann])
4546
- [`export`]: false positive for TypeScript overloads ([#1412], thanks [@golopot])
4647

4748
### Refactors
48-
- [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb])
49+
- [`no-extraneous-dependencies`], `importType`: remove lodash ([#1419], thanks [@ljharb])
4950

5051
## [2.18.0] - 2019-06-24
51-
5252
### Added
5353
- Support eslint v6 ([#1393], thanks [@sheepsteak])
5454
- [`order`]: Adds support for correctly sorting unknown types into a single group ([#1375], thanks [@swernerx])
@@ -63,7 +63,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
6363
- [`no-named-as-default-member`]: update broken link ([#1389], thanks [@fooloomanzoo])
6464

6565
## [2.17.3] - 2019-05-23
66-
6766
### Fixed
6867
- [`no-common-js`]: Also throw an error when assigning ([#1354], thanks [@charlessuh])
6968
- [`no-unused-modules`]: don't crash when lint file outside src-folder ([#1347], thanks [@rfermann])
@@ -76,22 +75,18 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
7675
### Docs
7776
- add missing `no-unused-modules` in README ([#1358], thanks [@golopot])
7877
- [`no-unused-modules`]: Indicates usage, plugin defaults to no-op, and add description to main README.md ([#1352], thanks [@johndevedu])
79-
[@christophercurrie]: https://github.com/christophercurrie
8078
- Document `env` option for `eslint-import-resolver-webpack` ([#1363], thanks [@kgregory])
8179

8280
## [2.17.2] - 2019-04-16
83-
8481
### Fixed
8582
- [`no-unused-modules`]: avoid crash when using `ignoreExports`-option ([#1331], [#1323], thanks [@rfermann])
8683
- [`no-unused-modules`]: make sure that rule with no options will not fail ([#1330], [#1334], thanks [@kiwka])
8784

8885
## [2.17.1] - 2019-04-13
89-
9086
### Fixed
9187
- require v2.4 of `eslint-module-utils` ([#1322])
9288

9389
## [2.17.0] - 2019-04-13
94-
9590
### Added
9691
- [`no-useless-path-segments`]: Add `noUselessIndex` option ([#1290], thanks [@timkraut])
9792
- [`no-duplicates`]: Add autofix ([#1312], thanks [@lydell])
@@ -116,7 +111,6 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
116111
- fix broken tests on master ([#1295], thanks [@jeffshaver] and [@ljharb])
117112
- [`no-commonjs`]: add tests that show corner cases ([#1308], thanks [@TakeScoop])
118113

119-
120114
## [2.16.0] - 2019-01-29
121115
### Added
122116
- `typescript` config ([#1257], thanks [@kirill-konshin])
@@ -133,13 +127,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
133127
- [`dynamic-import-chunkname`]: Add proper webpack comment parsing ([#1163], thanks [@st-sloth])
134128
- [`named`]: fix destructuring assignment ([#1232], thanks [@ljqx])
135129

136-
137130
## [2.14.0] - 2018-08-13
138-
* 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx
139-
|\
140-
| * e30a757 (source/pr/1151, fork/jsx) Add JSX check to namespace rule
141-
|/
142-
* 8252344 (source/pr/1148) Add error to output when module loaded as resolver has invalid API
143131
### Added
144132
- [`no-useless-path-segments`]: add commonJS (CJS) support ([#1128], thanks [@1pete])
145133
- [`namespace`]: add JSX check ([#1151], thanks [@jf248])
@@ -497,11 +485,10 @@ I'm seeing 62% improvement over my normal test codebase when executing only
497485

498486
## [1.1.0] - 2016-03-15
499487
### Added
500-
- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no
501-
resolver can find. (still prefer enhancing the Webpack and Node resolvers to
502-
using it, though). See [#89] for details.
488+
- Added an [`ignore`](./docs/rules/no-unresolved.md#ignore) option to [`no-unresolved`] for those pesky files that no resolver can find. (still prefer enhancing the Webpack and Node resolvers to using it, though). See [#89] for details.
503489

504490
## [1.0.4] - 2016-03-11
491+
505492
### Changed
506493
- respect hoisting for deep namespaces ([`namespace`]/[`no-deprecated`]) ([#211])
507494

@@ -510,39 +497,41 @@ using it, though). See [#89] for details.
510497
- correct cache behavior in `eslint_d` for deep namespaces ([#200])
511498

512499
## [1.0.3] - 2016-02-26
500+
513501
### Changed
514502
- no-deprecated follows deep namespaces ([#191])
515503

516504
### Fixed
517-
- [`namespace`] no longer flags modules with only a default export as having no
518-
names. (ns.default is valid ES6)
505+
- [`namespace`] no longer flags modules with only a default export as having no names. (ns.default is valid ES6)
519506

520507
## [1.0.2] - 2016-02-26
508+
521509
### Fixed
522510
- don't parse imports with no specifiers ([#192])
523511

524512
## [1.0.1] - 2016-02-25
513+
525514
### Fixed
526515
- export `stage-0` shared config
527516
- documented [`no-deprecated`]
528517
- deep namespaces are traversed regardless of how they get imported ([#189])
529518

530519
## [1.0.0] - 2016-02-24
520+
531521
### Added
532-
- [`no-deprecated`]: WIP rule to let you know at lint time if you're using
533-
deprecated functions, constants, classes, or modules.
522+
- [`no-deprecated`]: WIP rule to let you know at lint time if you're using deprecated functions, constants, classes, or modules.
534523

535524
### Changed
536525
- [`namespace`]: support deep namespaces ([#119] via [#157])
537526

538527
## [1.0.0-beta.0] - 2016-02-13
528+
539529
### Changed
540530
- support for (only) ESLint 2.x
541-
- no longer needs/refers to `import/parser` or `import/parse-options`. Instead,
542-
ESLint provides the configured parser + options to the rules, and they use that
543-
to parse dependencies.
531+
- no longer needs/refers to `import/parser` or `import/parse-options`. Instead, ESLint provides the configured parser + options to the rules, and they use that to parse dependencies.
544532

545533
### Removed
534+
546535
- `babylon` as default import parser (see Breaking)
547536

548537
## [0.13.0] - 2016-02-08
@@ -562,14 +551,11 @@ Unpublished from npm and re-released as 0.13.0. See [#170].
562551

563552
## [0.12.0] - 2015-12-14
564553
### Changed
565-
- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does
566-
this to support use of `jsnext:main` in `node_modules` without the pain of
567-
managing an allow list or a nuanced deny list.
554+
- Ignore [`import/ignore` setting] if exports are actually found in the parsed module. Does this to support use of `jsnext:main` in `node_modules` without the pain of managing an allow list or a nuanced deny list.
568555

569556
## [0.11.0] - 2015-11-27
570557
### Added
571-
- Resolver plugins. Now the linter can read Webpack config, properly follow
572-
aliases and ignore externals, dismisses inline loaders, etc. etc.!
558+
- Resolver plugins. Now the linter can read Webpack config, properly follow aliases and ignore externals, dismisses inline loaders, etc. etc.!
573559

574560
## Earlier releases (0.10.1 and younger)
575561
See [GitHub release notes](https://github.com/benmosher/eslint-plugin-import/releases?after=v0.11.0)
@@ -655,6 +641,7 @@ for info on changes for earlier releases.
655641
[#1371]: https://github.com/benmosher/eslint-plugin-import/pull/1371
656642
[#1370]: https://github.com/benmosher/eslint-plugin-import/pull/1370
657643
[#1363]: https://github.com/benmosher/eslint-plugin-import/pull/1363
644+
[#1360]: https://github.com/benmosher/eslint-plugin-import/pull/1360
658645
[#1358]: https://github.com/benmosher/eslint-plugin-import/pull/1358
659646
[#1356]: https://github.com/benmosher/eslint-plugin-import/pull/1356
660647
[#1354]: https://github.com/benmosher/eslint-plugin-import/pull/1354
@@ -774,7 +761,6 @@ for info on changes for earlier releases.
774761
[#211]: https://github.com/benmosher/eslint-plugin-import/pull/211
775762
[#164]: https://github.com/benmosher/eslint-plugin-import/pull/164
776763
[#157]: https://github.com/benmosher/eslint-plugin-import/pull/157
777-
778764
[#1366]: https://github.com/benmosher/eslint-plugin-import/issues/1366
779765
[#1334]: https://github.com/benmosher/eslint-plugin-import/issues/1334
780766
[#1323]: https://github.com/benmosher/eslint-plugin-import/issues/1323
@@ -921,7 +907,6 @@ for info on changes for earlier releases.
921907
[0.12.1]: https://github.com/benmosher/eslint-plugin-import/compare/v0.12.0...v0.12.1
922908
[0.12.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.11.0...v0.12.0
923909
[0.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v0.10.1...v0.11.0
924-
925910
[@mathieudutour]: https://github.com/mathieudutour
926911
[@gausie]: https://github.com/gausie
927912
[@singles]: https://github.com/singles
@@ -1035,3 +1020,6 @@ for info on changes for earlier releases.
10351020
[@Mairu]: https://github.com/Mairu
10361021
[@aamulumi]: https://github.com/aamulumi
10371022
[@pcorpet]: https://github.com/pcorpet
1023+
[@stropho]: https://github.com/stropho
1024+
[@luczsoma]: https://github.com/luczsoma
1025+
[@christophercurrie]: https://github.com/christophercurrie

Diff for: docs/rules/order.md

+33-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ Properties of the objects
122122

123123
### `newlines-between: [ignore|always|always-and-inside-groups|never]`:
124124

125-
126125
Enforces or forbids new lines between import groups:
127126

128127
- If set to `ignore`, no errors related to new lines between import groups will be reported (default).
@@ -190,6 +189,39 @@ import index from './';
190189
import sibling from './foo';
191190
```
192191

192+
### `alphabetize: {order: asc|desc|ignore}`:
193+
194+
Sort the order within each group in alphabetical manner based on **import path**:
195+
196+
- `order`: use `asc` to sort in ascending order, and `desc` to sort in descending order (default: `ignore`).
197+
198+
Example setting:
199+
```js
200+
alphabetize: {
201+
order: 'asc', /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */
202+
}
203+
```
204+
205+
This will fail the rule check:
206+
207+
```js
208+
/* eslint import/order: ["error", {"alphabetize": true}] */
209+
import React, { PureComponent } from 'react';
210+
import aTypes from 'prop-types';
211+
import { compose, apply } from 'xcompose';
212+
import * as classnames from 'classnames';
213+
```
214+
215+
While this will pass:
216+
217+
```js
218+
/* eslint import/order: ["error", {"alphabetize": true}] */
219+
import * as classnames from 'classnames';
220+
import aTypes from 'prop-types';
221+
import React, { PureComponent } from 'react';
222+
import { compose, apply } from 'xcompose';
223+
```
224+
193225
## Related
194226

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

Diff for: src/rules/order.js

+82-3
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,12 @@ function fixOutOfOrder(context, firstNode, secondNode, order) {
180180
const sourceCode = context.getSourceCode()
181181

182182
const firstRoot = findRootNode(firstNode.node)
183-
let firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot)
183+
const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot)
184184
const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot)
185185

186186
const secondRoot = findRootNode(secondNode.node)
187-
let secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot)
188-
let secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot)
187+
const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot)
188+
const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot)
189189
const canFix = canReorderItems(firstRoot, secondRoot)
190190

191191
let newCode = sourceCode.text.substring(secondRootStart, secondRootEnd)
@@ -243,6 +243,63 @@ function makeOutOfOrderReport(context, imported) {
243243
reportOutOfOrder(context, imported, outOfOrder, 'before')
244244
}
245245

246+
function importsSorterAsc(importA, importB) {
247+
if (importA < importB) {
248+
return -1
249+
}
250+
251+
if (importA > importB) {
252+
return 1
253+
}
254+
255+
return 0
256+
}
257+
258+
function importsSorterDesc(importA, importB) {
259+
if (importA < importB) {
260+
return 1
261+
}
262+
263+
if (importA > importB) {
264+
return -1
265+
}
266+
267+
return 0
268+
}
269+
270+
function mutateRanksToAlphabetize(imported, order) {
271+
const groupedByRanks = imported.reduce(function(acc, importedItem) {
272+
if (!Array.isArray(acc[importedItem.rank])) {
273+
acc[importedItem.rank] = []
274+
}
275+
acc[importedItem.rank].push(importedItem.name)
276+
return acc
277+
}, {})
278+
279+
const groupRanks = Object.keys(groupedByRanks)
280+
281+
const sorterFn = order === 'asc' ? importsSorterAsc : importsSorterDesc
282+
// sort imports locally within their group
283+
groupRanks.forEach(function(groupRank) {
284+
groupedByRanks[groupRank].sort(sorterFn)
285+
})
286+
287+
// assign globally unique rank to each import
288+
let newRank = 0
289+
const alphabetizedRanks = groupRanks.sort().reduce(function(acc, groupRank) {
290+
groupedByRanks[groupRank].forEach(function(importedItemName) {
291+
acc[importedItemName] = newRank
292+
newRank += 1
293+
})
294+
return acc
295+
}, {})
296+
297+
// mutate the original group-rank with alphabetized-rank
298+
imported.forEach(function(importedItem) {
299+
importedItem.rank = alphabetizedRanks[importedItem.name]
300+
})
301+
}
302+
246303
// DETECTING
247304

248305
function computePathRank(ranks, pathGroups, path, maxPosition) {
@@ -427,6 +484,13 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) {
427484
})
428485
}
429486

487+
function getAlphabetizeConfig(options) {
488+
const alphabetize = options.alphabetize || {}
489+
const order = alphabetize.order || 'ignore'
490+
491+
return {order}
492+
}
493+
430494
module.exports = {
431495
meta: {
432496
type: 'suggestion',
@@ -473,6 +537,16 @@ module.exports = {
473537
'never',
474538
],
475539
},
540+
alphabetize: {
541+
type: 'object',
542+
properties: {
543+
order: {
544+
enum: ['ignore', 'asc', 'desc'],
545+
default: 'ignore',
546+
},
547+
},
548+
additionalProperties: false,
549+
},
476550
},
477551
additionalProperties: false,
478552
},
@@ -482,6 +556,7 @@ module.exports = {
482556
create: function importOrderRule (context) {
483557
const options = context.options[0] || {}
484558
const newlinesBetweenImports = options['newlines-between'] || 'ignore'
559+
const alphabetize = getAlphabetizeConfig(options)
485560
let ranks
486561

487562
try {
@@ -524,6 +599,10 @@ module.exports = {
524599
registerNode(context, node, name, 'require', ranks, imported)
525600
},
526601
'Program:exit': function reportAndReset() {
602+
if (alphabetize.order !== 'ignore') {
603+
mutateRanksToAlphabetize(imported, alphabetize.order)
604+
}
605+
527606
makeOutOfOrderReport(context, imported)
528607

529608
if (newlinesBetweenImports !== 'ignore') {

0 commit comments

Comments
 (0)