diff --git a/CHANGELOG.md b/CHANGELOG.md index fb12deb24..bfb436cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Added +- [`order`]: add `orderByFullPathString` option + ## [2.32.0] - 2025-06-20 ### Added diff --git a/docs/rules/order.md b/docs/rules/order.md index 4a52b823e..05a7fe2c1 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -367,8 +367,8 @@ import index from './'; ### `alphabetize` -Valid values: `{ order?: "asc" | "desc" | "ignore", orderImportKind?: "asc" | "desc" | "ignore", caseInsensitive?: boolean }` \ -Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false }` +Valid values: `{ order?: "asc" | "desc" | "ignore", orderImportKind?: "asc" | "desc" | "ignore", caseInsensitive?: boolean, orderByFullPathString?: boolean }` \ +Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false, orderByFullPathString: false }` Determine the sort order of imports within each [predefined group][18] or [`PathGroup`][8] alphabetically based on specifier. @@ -385,6 +385,11 @@ Valid properties and their values include: - **`caseInsensitive`**: use `true` to ignore case and `false` to consider case when sorting + - **`orderByFullPathString`**: use `true` to sort by the full un-split path string and `false` to split by paths and sort by each one + - Enabling this flag may better align with the sort algorithm used by certain IDE's and applications, e.g. + - The Organize Imports action in JetBrains IDE's sorts by the full path string + - Microsoft's accessibility-insights-web application is [reported](https://github.com/microsoft/accessibility-insights-web/pull/6846) to sort by the full path string as well + #### Example Given the following settings: @@ -394,7 +399,8 @@ Given the following settings: "import/order": ["error", { "alphabetize": { "order": "asc", - "caseInsensitive": true + "caseInsensitive": true, + "orderByFullPathString": true, } }] } @@ -408,6 +414,8 @@ import aTypes from 'prop-types'; import { compose, apply } from 'xcompose'; import * as classnames from 'classnames'; import blist from 'BList'; +import timeUtils1 from '../utils/time' +import timeUtils2 from '../utils-time' ``` While this will pass: @@ -418,6 +426,11 @@ import * as classnames from 'classnames'; import aTypes from 'prop-types'; import React, { PureComponent } from 'react'; import { compose, apply } from 'xcompose'; +// Below the '../utils-time' and '../utils/time' path are not split before ordering ("orderByFullPathString": true). +// Instead, during comparison the "-" in "../utils-time" occurs lexicographically before the +// second "/" in "../utils/time" +import timeUtils2 from '../utils-time' +import timeUtils1 from '../utils/time' ``` ### `named` diff --git a/src/rules/order.js b/src/rules/order.js index 579dbb044..1d8e35a6a 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -433,7 +433,10 @@ function getSorter(alphabetizeOptions) { const importB = getNormalizedValue(nodeB, alphabetizeOptions.caseInsensitive); let result = 0; - if (!includes(importA, '/') && !includes(importB, '/')) { + if ( + alphabetizeOptions.orderByFullPathString === true + || !includes(importA, '/') && !includes(importB, '/') + ) { result = compareString(importA, importB); } else { const A = importA.split('/'); @@ -831,8 +834,9 @@ function getAlphabetizeConfig(options) { const order = alphabetize.order || 'ignore'; const orderImportKind = alphabetize.orderImportKind || 'ignore'; const caseInsensitive = alphabetize.caseInsensitive || false; + const orderByFullPathString = alphabetize.orderByFullPathString || false; - return { order, orderImportKind, caseInsensitive }; + return { order, orderImportKind, caseInsensitive, orderByFullPathString }; } // TODO, semver-major: Change the default of "distinctGroup" from true to false @@ -962,6 +966,10 @@ module.exports = { enum: ['ignore', 'asc', 'desc'], default: 'ignore', }, + orderByFullPathString: { + type: 'boolean', + default: false, + }, }, additionalProperties: false, }, diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 9b0a2a63f..e156f21c8 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -780,6 +780,48 @@ ruleTester.run('order', rule, { alphabetize: { order: 'desc' }, }], }), + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true` + test({ + code: ` + import a from "foo"; + import b from "foo-bar"; + import c from "foo/bar"; + import d from "foo/barfoo"; + `, + options: [{ alphabetize: { order: 'asc' }, orderByFullPathString: true }], + }), + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true + test({ + code: ` + import a from "foo"; + import b from "foo-bar"; + import c from "foo/foobar/bar"; + import d from "foo/foobar/barfoo"; + `, + options: [{ alphabetize: { order: 'asc' }, orderByFullPathString: true }], + }), + // Option alphabetize: {order: 'desc'} with orderByFullPathString: true + test({ + code: ` + import d from "foo/barfoo"; + import c from "foo/bar"; + import b from "foo-bar"; + import a from "foo"; + `, + options: [{ alphabetize: { order: 'desc' }, orderByFullPathString: true }], + }), + // Option alphabetize: {order: 'desc'} with orderByFullPathString: true and file names having non-alphanumeric characters. + test({ + code: ` + import d from "foo/barfoo"; + import b from "foo-bar"; + import c from "foo,bar"; + import a from "foo";`, + options: [{ + alphabetize: { order: 'desc' }, + orderByFullPathString: true, + }], + }), // Option alphabetize with newlines-between: {order: 'asc', newlines-between: 'always'} test({ code: ` @@ -2646,6 +2688,28 @@ ruleTester.run('order', rule, { message: '`foo-bar` import should occur after import of `foo/barfoo`', }], }), + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true + test({ + code: ` + import a from "foo"; + import c from "foo/bar"; + import d from "foo/barfoo"; + import b from "foo-bar"; + `, + options: [{ + alphabetize: { order: 'asc' }, + orderByFullPathString: true, + }], + output: ` + import a from "foo"; + import b from "foo-bar"; + import c from "foo/bar"; + import d from "foo/barfoo"; + `, + errors: [{ + message: '`foo/barfoo` import should occur after import of `foo-bar`', + }], + }), // Option alphabetize {order: 'asc': caseInsensitive: true} test({ code: `