diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f3c66cf66..8bb031e98c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]
- [`order`]: Adds support for correctly sorting unknown types into a single group (thanks [@swernerx])
+- [`order`]: Adds support for pathGroups to allow ordering by defined patterns ([#795], thanks [@Mairu])
## [2.17.3] - 2019-05-23
diff --git a/docs/rules/order.md b/docs/rules/order.md
index 88ddca46fb..d23f3c682f 100644
--- a/docs/rules/order.md
+++ b/docs/rules/order.md
@@ -94,6 +94,31 @@ 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) |
+| group | x | string | one of the allowed groups, the pathGroup will be positioned relative to this group |
+| position | | integer | defines where around the group the pathGroup will be positioned
negative -> before, 0 = like the group, positive -> after
default: 1
maximal magnitude: 1000 |
+
+```json
+{
+ "import/order": ["error", {
+ "pathGroups": [
+ {
+ "pattern": "~/**",
+ "external": "external"
+ }
+ ]
+ }]
+}
+```
+
### `newlines-between: [ignore|always|always-and-inside-groups|never]`:
diff --git a/src/rules/order.js b/src/rules/order.js
index 3d3e1b96b7..a89bf26741 100644
--- a/src/rules/order.js
+++ b/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'
@@ -242,9 +243,28 @@ function makeOutOfOrderReport(context, imported) {
// DETECTING
+function computePathRank(ranks, pathGroups, path) {
+ for (const { pattern, patternOptions, group, position = 1 } of pathGroups) {
+ if (minimatch(path, pattern, patternOptions ||{ nocomment: true })) {
+ return ranks[group] + (position / 1000)
+ }
+ }
+}
+
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)
+ }
+ if (!rank) {
+ rank = ranks.groups[impType]
+ }
+ if (type !== 'import') {
+ rank += 100
+ }
+
+ return rank
}
function registerNode(context, node, name, type, ranks, imported) {
@@ -376,6 +396,30 @@ 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: 'integer',
+ minimum: -1000,
+ maximum: 1000,
+ },
+ },
+ required: ['pattern', 'group'],
+ },
+ },
'newlines-between': {
enum: [
'ignore',
@@ -396,7 +440,10 @@ module.exports = {
let ranks
try {
- ranks = convertGroupsToRanks(options.groups || defaultGroups)
+ ranks = {
+ groups: convertGroupsToRanks(options.groups || defaultGroups),
+ pathGroups: options.pathGroups || [],
+ }
} catch (error) {
// Malformed configuration
return {
diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js
index b310dd07ff..3f63f44a2e 100644
--- a/tests/src/rules/order.js
+++ b/tests/src/rules/order.js
@@ -164,7 +164,7 @@ ruleTester.run('order', rule, {
var index = require('./');
`,
}),
- // Addijg unknown import types (e.g. using an resolver alias via babel) to the groups.
+ // Add unknown import types (e.g. using an resolver alias via babel) to the groups.
test({
code: `
import fs from 'fs';
@@ -204,6 +204,59 @@ ruleTester.run('order', rule, {
},
],
}),
+
+ // Using pathGroups to customize ordering, positive position means after
+ test({
+ code: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { Input } from '~/components/Input';
+ import { Button } from '#/components/Button';
+ import { add } from './helper';`,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 1 },
+ { pattern: '#/**', group: 'external', position: 2 },
+ ],
+ }],
+ }),
+ // pathGroup with position 0 means "equal" with group
+ test({
+ code: `
+ import fs from 'fs';
+ import { Input } from '~/components/Input';
+ import async from 'async';
+ import { Button } from '#/components/Button';
+ import _ from 'lodash';
+ import { add } from './helper';`,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 0 },
+ { pattern: '#/**', group: 'external', position: 0 },
+ ],
+ }],
+ }),
+ // Using pathGroups to customize ordering, negative position means before
+ test({
+ code: `
+ import fs from 'fs';
+
+ import { Input } from '~/components/Input';
+
+ import { Button } from '#/components/Button';
+
+ import _ from 'lodash';
+
+ import { add } from './helper';`,
+ options: [{
+ 'newlines-between': 'always',
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: -2 },
+ { pattern: '#/**', group: 'external', position: -1 },
+ ],
+ }],
+ }),
+
// Option: newlines-between: 'always'
test({
code: `
@@ -573,7 +626,7 @@ ruleTester.run('order', rule, {
message: '`fs` import should occur before import of `async`',
}],
}),
- // fix order of multile import
+ // fix order of multiline import
test({
code: `
var async = require('async');
@@ -1266,6 +1319,151 @@ ruleTester.run('order', rule, {
],
}),
+ // pathGroup with positive position
+ test({
+ code: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { add } from './helper';
+ import { Input } from '~/components/Input';
+ `,
+ output: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { Input } from '~/components/Input';
+ import { add } from './helper';
+ `,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 1 },
+ ],
+ }],
+ errors: [{
+ ruleId: 'order',
+ message: '`~/components/Input` import should occur before import of `./helper`',
+ }],
+ }),
+ // pathGroup with zero position
+ test({
+ code: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { add } from './helper';
+ import { Input } from '~/components/Input';
+ import async from 'async';
+ `,
+ output: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { Input } from '~/components/Input';
+ import async from 'async';
+ import { add } from './helper';
+ `,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 0 },
+ ],
+ }],
+ errors: [{
+ ruleId: 'order',
+ message: '`./helper` import should occur after import of `async`',
+ }],
+ }),
+ // pathGroup with negative position
+ test({
+ code: `
+ import fs from 'fs';
+ import _ from 'lodash';
+ import { add } from './helper';
+ import { Input } from '~/components/Input';
+ `,
+ output: `
+ import fs from 'fs';
+ import { Input } from '~/components/Input';
+ import _ from 'lodash';
+ import { add } from './helper';
+ `,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: -1 },
+ ],
+ }],
+ errors: [{
+ ruleId: 'order',
+ message: '`~/components/Input` import should occur before import of `lodash`',
+ }],
+ }),
+ // multiple pathGroup with different positions for same group (positive fix)
+ test({
+ code: `
+ import fs from 'fs';
+ import { Import } from '$/components/Import';
+ import _ from 'lodash';
+ import { Output } from '~/components/Output';
+ import { Input } from '#/components/Input';
+ import { add } from './helper';
+ import { Export } from '-/components/Export';
+ `,
+ output: `
+ import fs from 'fs';
+ import { Export } from '-/components/Export';
+ import { Import } from '$/components/Import';
+ import _ from 'lodash';
+ import { Output } from '~/components/Output';
+ import { Input } from '#/components/Input';
+ import { add } from './helper';
+ `,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 1 },
+ { pattern: '#/**', group: 'external', position: 2 },
+ { pattern: '$/**', group: 'external', position: -1 },
+ { pattern: '-/**', group: 'external', position: -2 },
+ ],
+ }],
+ errors: [
+ {
+ ruleId: 'order',
+ message: '`-/components/Export` import should occur before import of `$/components/Import`',
+ },
+ ],
+ }),
+ // multiple pathGroup with different positions for same group (negative fix)
+ test({
+ code: `
+ import fs from 'fs';
+ import { Export } from '-/components/Export';
+ import { Import } from '$/components/Import';
+ import _ from 'lodash';
+ import { Input } from '#/components/Input';
+ import { add } from './helper';
+ import { Output } from '~/components/Output';
+ `,
+ output: `
+ import fs from 'fs';
+ import { Export } from '-/components/Export';
+ import { Import } from '$/components/Import';
+ import _ from 'lodash';
+ import { Output } from '~/components/Output';
+ import { Input } from '#/components/Input';
+ import { add } from './helper';
+ `,
+ options: [{
+ pathGroups: [
+ { pattern: '~/**', group: 'external', position: 1 },
+ { pattern: '#/**', group: 'external', position: 2 },
+ { pattern: '$/**', group: 'external', position: -1 },
+ { pattern: '-/**', group: 'external', position: -2 },
+ ],
+ }],
+ errors: [
+ {
+ ruleId: 'order',
+ message: '`~/components/Output` import should occur before import of `#/components/Input`',
+ },
+ ],
+ }),
+
// reorder fix cannot cross non import or require
test(withoutAutofixOutput({
code: `
@@ -1312,7 +1510,7 @@ ruleTester.run('order', rule, {
message: '`fs` import should occur before import of `async`',
}],
})),
- // cannot require in case of not assignement require
+ // cannot require in case of not assignment require
test(withoutAutofixOutput({
code: `
var async = require('async');
@@ -1336,7 +1534,7 @@ ruleTester.run('order', rule, {
message: '`fs` import should occur before import of `async`',
}],
})),
- // reorder cannot cross variable assignemet (import statement)
+ // reorder cannot cross variable assignment (import statement)
test(withoutAutofixOutput({
code: `
import async from 'async';
@@ -1360,7 +1558,7 @@ ruleTester.run('order', rule, {
message: '`fs` import should occur before import of `async`',
}],
})),
- // cannot reorder in case of not assignement import
+ // cannot reorder in case of not assignment import
test(withoutAutofixOutput({
code: `
import async from 'async';