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

order rule: add pathGroups option to add support to order by paths #1386

Merged
merged 1 commit into from Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
- support `parseForESLint` from custom parser ([#1435], thanks [@JounQin])
- [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi]))
- [`no-unused-modules`]: add flow type support ([#1542], thanks [@rfermann])
- [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], [#1386], thanks [@Mairu])

### Fixed
- [`default`]: make error message less confusing ([#1470], thanks [@golopot])
Expand Down Expand Up @@ -644,6 +645,7 @@ for info on changes for earlier releases.
[#1401]: https://github.com/benmosher/eslint-plugin-import/pull/1401
[#1393]: https://github.com/benmosher/eslint-plugin-import/pull/1393
[#1389]: https://github.com/benmosher/eslint-plugin-import/pull/1389
[#1386]: https://github.com/benmosher/eslint-plugin-import/pull/1386
[#1377]: https://github.com/benmosher/eslint-plugin-import/pull/1377
[#1375]: https://github.com/benmosher/eslint-plugin-import/pull/1375
[#1372]: https://github.com/benmosher/eslint-plugin-import/pull/1372
Expand Down Expand Up @@ -788,6 +790,7 @@ for info on changes for earlier releases.
[#863]: https://github.com/benmosher/eslint-plugin-import/issues/863
[#842]: https://github.com/benmosher/eslint-plugin-import/issues/842
[#839]: https://github.com/benmosher/eslint-plugin-import/issues/839
[#795]: https://github.com/benmosher/eslint-plugin-import/issues/795
[#793]: https://github.com/benmosher/eslint-plugin-import/issues/793
[#720]: https://github.com/benmosher/eslint-plugin-import/issues/720
[#717]: https://github.com/benmosher/eslint-plugin-import/issues/717
Expand Down Expand Up @@ -1025,3 +1028,4 @@ for info on changes for earlier releases.
[@Taranys]: https://github.com/Taranys
[@maxmalov]: https://github.com/maxmalov
[@marcusdarmstrong]: https://github.com/marcusdarmstrong
[@Mairu]: https://github.com/Mairu
26 changes: 26 additions & 0 deletions docs/rules/order.md
Expand Up @@ -94,6 +94,32 @@ You can set the options like this:
"import/order": ["error", {"groups": ["index", "sibling", "parent", "internal", "external", "builtin"]}]
```

### `pathGroups: [array of objects]`:

To be able so group by paths mostly needed with aliases pathGroups can be defined.

Properties of the objects

| property | required | type | description |
|----------------|:--------:|--------|---------------|
| pattern | x | string | minimatch pattern for the paths to be in this group (will not be used for builtins or externals) |
| patternOptions | | object | options for minimatch, default: { nocomment: true } |
ljharb marked this conversation as resolved.
Show resolved Hide resolved
| group | x | string | one of the allowed groups, the pathGroup will be positioned relative to this group |
| position | | string | defines where around the group the pathGroup will be positioned, can be 'after' or 'before', if not provided pathGroup will be positioned like the group |

```json
{
"import/order": ["error", {
"pathGroups": [
{
"pattern": "~/**",
"group": "external"
}
]
}]
}
```

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


Expand Down
98 changes: 95 additions & 3 deletions src/rules/order.js
@@ -1,5 +1,6 @@
'use strict'

import minimatch from 'minimatch'
import importType from '../core/importType'
import isStaticRequire from '../core/staticRequire'
import docsUrl from '../docsUrl'
Expand Down Expand Up @@ -244,9 +245,29 @@ function makeOutOfOrderReport(context, imported) {

// DETECTING

function computePathRank(ranks, pathGroups, path, maxPosition) {
for (let i = 0, l = pathGroups.length; i < l; i++) {
const { pattern, patternOptions, group, position = 1 } = pathGroups[i]
if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
return ranks[group] + (position / maxPosition)
}
}
}

function computeRank(context, ranks, name, type) {
return ranks[importType(name, context)] +
(type === 'import' ? 0 : 100)
const impType = importType(name, context)
let rank
if (impType !== 'builtin' && impType !== 'external') {
rank = computePathRank(ranks.groups, ranks.pathGroups, name, ranks.maxPosition)
}
if (!rank) {
rank = ranks.groups[impType]
}
if (type !== 'import') {
rank += 100
}

return rank
}

function registerNode(context, node, name, type, ranks, imported) {
Expand Down Expand Up @@ -294,6 +315,49 @@ function convertGroupsToRanks(groups) {
}, rankObject)
}

function convertPathGroupsForRanks(pathGroups) {
const after = {}
const before = {}

const transformed = pathGroups.map((pathGroup, index) => {
const { group, position: positionString } = pathGroup
let position = 0
if (positionString === 'after') {
if (!after[group]) {
after[group] = 1
}
position = after[group]++
} else if (positionString === 'before') {
if (!before[group]) {
before[group] = []
}
before[group].push(index)
}

return Object.assign({}, pathGroup, { position })
})

let maxPosition = 1

Object.keys(before).forEach((group) => {
const groupLength = before[group].length
before[group].forEach((groupIndex, index) => {
transformed[groupIndex].position = -1 * (groupLength - index)
})
maxPosition = Math.max(maxPosition, groupLength)
})

Object.keys(after).forEach((key) => {
const groupNextPosition = after[key]
maxPosition = Math.max(maxPosition, groupNextPosition - 1)
})

return {
pathGroups: transformed,
maxPosition: maxPosition > 10 ? Math.pow(10, Math.ceil(Math.log10(maxPosition))) : 10,
}
}

function fixNewLineAfterImport(context, previousImport) {
const prevRoot = findRootNode(previousImport.node)
const tokensToEndOfLine = takeTokensAfterWhile(
Expand Down Expand Up @@ -378,6 +442,29 @@ module.exports = {
groups: {
type: 'array',
},
pathGroups: {
type: 'array',
items: {
type: 'object',
properties: {
pattern: {
type: 'string',
},
patternOptions: {
type: 'object',
},
group: {
type: 'string',
enum: types,
},
position: {
type: 'string',
enum: ['after', 'before'],
},
},
required: ['pattern', 'group'],
},
},
'newlines-between': {
enum: [
'ignore',
Expand All @@ -398,7 +485,12 @@ module.exports = {
let ranks

try {
ranks = convertGroupsToRanks(options.groups || defaultGroups)
const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups || [])
ranks = {
groups: convertGroupsToRanks(options.groups || defaultGroups),
pathGroups,
maxPosition,
}
} catch (error) {
// Malformed configuration
return {
Expand Down