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';