From 5700f0197836fcf33d22a96a03d595f66c2a0e54 Mon Sep 17 00:00:00 2001 From: Ted Weatherly Date: Fri, 27 Jun 2025 11:29:41 -0500 Subject: [PATCH 1/4] feat(order): add orderBySplitPaths option --- CHANGELOG.md | 3 ++ docs/rules/order.md | 6 ++-- src/rules/order.js | 12 ++++++-- tests/src/rules/order.js | 64 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb12deb24d..1cd6e4f1d1 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 `orderBySplitPaths` option + ## [2.32.0] - 2025-06-20 ### Added diff --git a/docs/rules/order.md b/docs/rules/order.md index 4a52b823e1..353f1b25c3 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, orderBySplitPaths?: boolean }` \ +Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false, orderBySplitPaths: true }` Determine the sort order of imports within each [predefined group][18] or [`PathGroup`][8] alphabetically based on specifier. @@ -385,6 +385,8 @@ Valid properties and their values include: - **`caseInsensitive`**: use `true` to ignore case and `false` to consider case when sorting + - **`orderBySplitPaths`**: use `true` to split by paths and sort by each one and `false` to sort by the full un-split path string + #### Example Given the following settings: diff --git a/src/rules/order.js b/src/rules/order.js index 579dbb0444..9fbdbcd4a1 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.orderBySplitPaths === false + || !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 orderBySplitPaths = alphabetize.orderBySplitPaths || true; - return { order, orderImportKind, caseInsensitive }; + return { order, orderImportKind, caseInsensitive, orderBySplitPaths }; } // TODO, semver-major: Change the default of "distinctGroup" from true to false @@ -962,6 +966,10 @@ module.exports = { enum: ['ignore', 'asc', 'desc'], default: 'ignore', }, + orderBySplitPaths: { + type: 'boolean', + default: true, + }, }, additionalProperties: false, }, diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index 9b0a2a63ff..b7b5f944b2 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 orderBySplitPaths: false` + 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' }, orderBySplitPaths: false }], + }), + // Option alphabetize: {order: 'asc'} with orderBySplitPaths: false + 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' }, orderBySplitPaths: false }], + }), + // Option alphabetize: {order: 'desc'} with orderBySplitPaths: false + 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' }, orderBySplitPaths: false }], + }), + // Option alphabetize: {order: 'desc'} with orderBySplitPaths: false 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' }, + orderBySplitPaths: false, + }], + }), // 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 orderBySplitPaths: false + 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' }, + orderBySplitPaths: false, + }], + 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: ` From d98121e4d00953897106d63586d4df10a520b646 Mon Sep 17 00:00:00 2001 From: Ted Weatherly Date: Tue, 1 Jul 2025 15:04:59 -0500 Subject: [PATCH 2/4] refactor(order): invert naming --- CHANGELOG.md | 2 +- docs/rules/order.md | 6 +++--- src/rules/order.js | 10 +++++----- tests/src/rules/order.js | 20 ++++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd6e4f1d1..bfb436cd61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] ### Added -- [`order`]: add `orderBySplitPaths` option +- [`order`]: add `orderByFullPathString` option ## [2.32.0] - 2025-06-20 diff --git a/docs/rules/order.md b/docs/rules/order.md index 353f1b25c3..3e1184fa7d 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, orderBySplitPaths?: boolean }` \ -Default: `{ order: "ignore", orderImportKind: "ignore", caseInsensitive: false, orderBySplitPaths: true }` +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,7 +385,7 @@ Valid properties and their values include: - **`caseInsensitive`**: use `true` to ignore case and `false` to consider case when sorting - - **`orderBySplitPaths`**: use `true` to split by paths and sort by each one and `false` to sort by the full un-split path string + - **`orderByFullPathString`**: use `false` to split by paths and sort by each one and `true` to sort by the full un-split path string #### Example diff --git a/src/rules/order.js b/src/rules/order.js index 9fbdbcd4a1..1d8e35a6a5 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -434,7 +434,7 @@ function getSorter(alphabetizeOptions) { let result = 0; if ( - alphabetizeOptions.orderBySplitPaths === false + alphabetizeOptions.orderByFullPathString === true || !includes(importA, '/') && !includes(importB, '/') ) { result = compareString(importA, importB); @@ -834,9 +834,9 @@ function getAlphabetizeConfig(options) { const order = alphabetize.order || 'ignore'; const orderImportKind = alphabetize.orderImportKind || 'ignore'; const caseInsensitive = alphabetize.caseInsensitive || false; - const orderBySplitPaths = alphabetize.orderBySplitPaths || true; + const orderByFullPathString = alphabetize.orderByFullPathString || false; - return { order, orderImportKind, caseInsensitive, orderBySplitPaths }; + return { order, orderImportKind, caseInsensitive, orderByFullPathString }; } // TODO, semver-major: Change the default of "distinctGroup" from true to false @@ -966,9 +966,9 @@ module.exports = { enum: ['ignore', 'asc', 'desc'], default: 'ignore', }, - orderBySplitPaths: { + orderByFullPathString: { type: 'boolean', - default: true, + default: false, }, }, additionalProperties: false, diff --git a/tests/src/rules/order.js b/tests/src/rules/order.js index b7b5f944b2..e156f21c8d 100644 --- a/tests/src/rules/order.js +++ b/tests/src/rules/order.js @@ -780,7 +780,7 @@ ruleTester.run('order', rule, { alphabetize: { order: 'desc' }, }], }), - // Option alphabetize: {order: 'asc'} with orderBySplitPaths: false` + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true` test({ code: ` import a from "foo"; @@ -788,9 +788,9 @@ ruleTester.run('order', rule, { import c from "foo/bar"; import d from "foo/barfoo"; `, - options: [{ alphabetize: { order: 'asc' }, orderBySplitPaths: false }], + options: [{ alphabetize: { order: 'asc' }, orderByFullPathString: true }], }), - // Option alphabetize: {order: 'asc'} with orderBySplitPaths: false + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true test({ code: ` import a from "foo"; @@ -798,9 +798,9 @@ ruleTester.run('order', rule, { import c from "foo/foobar/bar"; import d from "foo/foobar/barfoo"; `, - options: [{ alphabetize: { order: 'asc' }, orderBySplitPaths: false }], + options: [{ alphabetize: { order: 'asc' }, orderByFullPathString: true }], }), - // Option alphabetize: {order: 'desc'} with orderBySplitPaths: false + // Option alphabetize: {order: 'desc'} with orderByFullPathString: true test({ code: ` import d from "foo/barfoo"; @@ -808,9 +808,9 @@ ruleTester.run('order', rule, { import b from "foo-bar"; import a from "foo"; `, - options: [{ alphabetize: { order: 'desc' }, orderBySplitPaths: false }], + options: [{ alphabetize: { order: 'desc' }, orderByFullPathString: true }], }), - // Option alphabetize: {order: 'desc'} with orderBySplitPaths: false and file names having non-alphanumeric characters. + // Option alphabetize: {order: 'desc'} with orderByFullPathString: true and file names having non-alphanumeric characters. test({ code: ` import d from "foo/barfoo"; @@ -819,7 +819,7 @@ ruleTester.run('order', rule, { import a from "foo";`, options: [{ alphabetize: { order: 'desc' }, - orderBySplitPaths: false, + orderByFullPathString: true, }], }), // Option alphabetize with newlines-between: {order: 'asc', newlines-between: 'always'} @@ -2688,7 +2688,7 @@ ruleTester.run('order', rule, { message: '`foo-bar` import should occur after import of `foo/barfoo`', }], }), - // Option alphabetize: {order: 'asc'} with orderBySplitPaths: false + // Option alphabetize: {order: 'asc'} with orderByFullPathString: true test({ code: ` import a from "foo"; @@ -2698,7 +2698,7 @@ ruleTester.run('order', rule, { `, options: [{ alphabetize: { order: 'asc' }, - orderBySplitPaths: false, + orderByFullPathString: true, }], output: ` import a from "foo"; From fb1d3e2db5564082495413a470282e9628857cec Mon Sep 17 00:00:00 2001 From: Ted Weatherly Date: Tue, 1 Jul 2025 16:06:42 -0500 Subject: [PATCH 3/4] docs(order): improve wording and provide examples --- docs/rules/order.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/rules/order.md b/docs/rules/order.md index 3e1184fa7d..9890a5aae5 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -385,7 +385,8 @@ Valid properties and their values include: - **`caseInsensitive`**: use `true` to ignore case and `false` to consider case when sorting - - **`orderByFullPathString`**: use `false` to split by paths and sort by each one and `true` to sort by the full un-split path string + - **`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 operating systems and IDE's, e.g. the Organize Imports action in JetBrains IDE's #### Example @@ -396,7 +397,8 @@ Given the following settings: "import/order": ["error", { "alphabetize": { "order": "asc", - "caseInsensitive": true + "caseInsensitive": true, + "orderByFullPathString": true, } }] } @@ -410,6 +412,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: @@ -420,6 +424,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` From f5d3cd3de6424e2f66cf28c1ee836cddf4c3a71e Mon Sep 17 00:00:00 2001 From: Ted Weatherly Date: Tue, 1 Jul 2025 16:54:03 -0500 Subject: [PATCH 4/4] docs(order): list the two known usages for orderByFullPathString --- docs/rules/order.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rules/order.md b/docs/rules/order.md index 9890a5aae5..05a7fe2c18 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -386,7 +386,9 @@ 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 operating systems and IDE's, e.g. the Organize Imports action in JetBrains IDE's + - 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