From 77e6cd301feeb0972f4e9e170958183c618e9dab Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Thu, 10 Apr 2025 16:14:43 +0200 Subject: [PATCH 01/21] fix: ts organize imports mismatch for hashtag imports --- .changeset/clear-bikes-jog.md | 5 +++++ src/rules/order.ts | 2 ++ src/utils/import-type.ts | 7 +++++++ test/rules/order.spec.ts | 10 ++++++++++ 4 files changed, 24 insertions(+) create mode 100644 .changeset/clear-bikes-jog.md diff --git a/.changeset/clear-bikes-jog.md b/.changeset/clear-bikes-jog.md new file mode 100644 index 00000000..1a926547 --- /dev/null +++ b/.changeset/clear-bikes-jog.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-import-x": patch +--- + +fix: align with TypeScript Organize Imports for [mapped imports](https://nodejs.org/api/packages.html#imports) diff --git a/src/rules/order.ts b/src/rules/order.ts index 567907af..d43b7ef0 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -46,6 +46,7 @@ const categories = { type Category = keyof typeof categories const defaultGroups = [ + 'mapped', 'builtin', 'external', 'parent', @@ -824,6 +825,7 @@ function getRequireBlock(node: TSESTree.Node) { const types: ImportType[] = [ 'builtin', 'external', + 'mapped', 'internal', 'unknown', 'parent', diff --git a/src/utils/import-type.ts b/src/utils/import-type.ts index c3b7b088..8471e164 100644 --- a/src/utils/import-type.ts +++ b/src/utils/import-type.ts @@ -88,6 +88,10 @@ export function isScopedMain(name: string) { return !!name && scopedMainRegExp.test(name) } +function isMapped(name: string) { + return name.startsWith('#') +} + function isRelativeToParent(name: string) { return /^\.\.$|^\.\.[/\\]/.test(name) } @@ -158,6 +162,9 @@ function typeTest( if (isBuiltIn(name, settings, path)) { return 'builtin' } + if (isMapped(name)) { + return 'mapped' + } if (isRelativeToParent(name)) { return 'parent' } diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index a38a9ebe..b1857516 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4809,6 +4809,16 @@ describe('TypeScript', () => { }, ], }), + // By default it should order same as TS LSP (issue-286) + tValid({ + code: `import { internA } from "#a"; +import { scopeA } from "@a/a"; +import { localA } from "./a"; + +console.log({ internA, scopeA, localA }); +`, + ...parserConfig, + }), ], invalid: [ // Option alphabetize: {order: 'asc'} From 12c17a9ae00900e1643b7a346efbf098d76aa3e7 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Fri, 11 Apr 2025 10:52:34 +0200 Subject: [PATCH 02/21] refactor: rename mapped to private-imports --- src/rules/order.ts | 4 ++-- src/utils/import-type.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index d43b7ef0..d8eebe43 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -46,7 +46,7 @@ const categories = { type Category = keyof typeof categories const defaultGroups = [ - 'mapped', + 'private-imports', 'builtin', 'external', 'parent', @@ -825,7 +825,7 @@ function getRequireBlock(node: TSESTree.Node) { const types: ImportType[] = [ 'builtin', 'external', - 'mapped', + 'private-imports', 'internal', 'unknown', 'parent', diff --git a/src/utils/import-type.ts b/src/utils/import-type.ts index 8471e164..55ce8642 100644 --- a/src/utils/import-type.ts +++ b/src/utils/import-type.ts @@ -88,7 +88,7 @@ export function isScopedMain(name: string) { return !!name && scopedMainRegExp.test(name) } -function isMapped(name: string) { +function isPrivateImport(name: string) { return name.startsWith('#') } @@ -162,8 +162,8 @@ function typeTest( if (isBuiltIn(name, settings, path)) { return 'builtin' } - if (isMapped(name)) { - return 'mapped' + if (isPrivateImport(name)) { + return 'private-imports' } if (isRelativeToParent(name)) { return 'parent' From 31854ca2aac0d8688a1e361d0c683a3e5aff25ee Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 11 Apr 2025 17:05:04 +0800 Subject: [PATCH 03/21] refactor: rename `private-imports` to `private-import` --- src/rules/order.ts | 4 ++-- src/utils/import-type.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index d8eebe43..f6d36949 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -46,7 +46,7 @@ const categories = { type Category = keyof typeof categories const defaultGroups = [ - 'private-imports', + 'private-import', 'builtin', 'external', 'parent', @@ -825,7 +825,7 @@ function getRequireBlock(node: TSESTree.Node) { const types: ImportType[] = [ 'builtin', 'external', - 'private-imports', + 'private-import', 'internal', 'unknown', 'parent', diff --git a/src/utils/import-type.ts b/src/utils/import-type.ts index 55ce8642..4bef48cd 100644 --- a/src/utils/import-type.ts +++ b/src/utils/import-type.ts @@ -163,7 +163,7 @@ function typeTest( return 'builtin' } if (isPrivateImport(name)) { - return 'private-imports' + return 'private-import' } if (isRelativeToParent(name)) { return 'parent' From dd1f0120552aed5aed1c5648222bde1158d00039 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Fri, 11 Apr 2025 11:05:33 +0200 Subject: [PATCH 04/21] chore: add privateImportsFeatureFlag option to order rule --- src/rules/order.ts | 10 +++++++++- test/rules/order.spec.ts | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index f6d36949..7da923fb 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -1151,6 +1151,7 @@ export interface Options { pathGroups?: PathGroup[] sortTypesGroup?: boolean warnOnUnassignedImports?: boolean + privateImportsFeatureFlag?: boolean } type MessageId = @@ -1273,6 +1274,10 @@ export default createRule<[Options?], MessageId>({ type: 'boolean', default: false, }, + privateImportsFeatureFlag: { + type: 'boolean', + default: false, + }, }, additionalProperties: false, dependencies: { @@ -1391,7 +1396,10 @@ export default createRule<[Options?], MessageId>({ options.pathGroups || [], ) const { groups, omittedTypes } = convertGroupsToRanks( - options.groups || defaultGroups, + options.groups || + (options.privateImportsFeatureFlag + ? defaultGroups + : defaultGroups.slice(1)), ) ranks = { groups, diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index b1857516..091903a2 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4818,6 +4818,11 @@ import { localA } from "./a"; console.log({ internA, scopeA, localA }); `, ...parserConfig, + options: [ + { + privateImportsFeatureFlag: true, + }, + ], }), ], invalid: [ From 7b42ee062d888091b23603adbbab9fed130df0ba Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Fri, 11 Apr 2025 15:03:24 +0200 Subject: [PATCH 05/21] chore: enhance --- src/rules/order.ts | 14 +++++++++++--- test/rules/order.spec.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index 7da923fb..9c0a5cfb 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -46,7 +46,6 @@ const categories = { type Category = keyof typeof categories const defaultGroups = [ - 'private-import', 'builtin', 'external', 'parent', @@ -54,6 +53,15 @@ const defaultGroups = [ 'index', ] as const +const defaultGroupsOfficialOrganizeImports = [ + 'private-import', + 'external', + 'builtin', + 'parent', + 'sibling', + 'index', +] as const + // REPORTING AND FIXING function reverse(array: ImportEntryWithRank[]): ImportEntryWithRank[] { @@ -1398,8 +1406,8 @@ export default createRule<[Options?], MessageId>({ const { groups, omittedTypes } = convertGroupsToRanks( options.groups || (options.privateImportsFeatureFlag - ? defaultGroups - : defaultGroups.slice(1)), + ? defaultGroupsOfficialOrganizeImports + : defaultGroups), ) ranks = { groups, diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index 091903a2..d82c9837 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4813,9 +4813,9 @@ describe('TypeScript', () => { tValid({ code: `import { internA } from "#a"; import { scopeA } from "@a/a"; +import fs from 'node:fs'; +import path from "path"; import { localA } from "./a"; - -console.log({ internA, scopeA, localA }); `, ...parserConfig, options: [ @@ -5207,6 +5207,30 @@ console.log({ internA, scopeA, localA }); // { message: '`node:fs/promises` import should occur before import of `express`' }, ], }), + // By default it should order same as TS LSP (issue-286) + tInvalid({ + code: `import { scopeA } from "@a/a"; +import fs from 'node:fs'; +import path from "path"; +import { localA } from "./a"; +import { internA } from "#a"; +`, + output: `import { internA } from "#a"; +import { scopeA } from "@a/a"; +import fs from 'node:fs'; +import path from "path"; +import { localA } from "./a"; +`, + ...parserConfig, + options: [ + { + privateImportsFeatureFlag: true, + }, + ], + errors: [ + createOrderError(['`#a` import', 'before', 'import of `@a/a`']), + ], + }), ], }) } From 2790318cc1b26370bf79d7a6a06a074d62f6f6e8 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Fri, 11 Apr 2025 16:13:44 +0200 Subject: [PATCH 06/21] chore: enhance --- .changeset/clear-bikes-jog.md | 4 ++-- docs/rules/order.md | 14 ++++++++++++++ src/rules/order.ts | 13 +++++++------ test/rules/order.spec.ts | 8 ++++++-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/.changeset/clear-bikes-jog.md b/.changeset/clear-bikes-jog.md index 1a926547..32845014 100644 --- a/.changeset/clear-bikes-jog.md +++ b/.changeset/clear-bikes-jog.md @@ -1,5 +1,5 @@ --- -"eslint-plugin-import-x": patch +"eslint-plugin-import-x": minor --- -fix: align with TypeScript Organize Imports for [mapped imports](https://nodejs.org/api/packages.html#imports) +feat: add option `followTsOrganizeImports` for `order` rule diff --git a/docs/rules/order.md b/docs/rules/order.md index df8d7ad1..524111ca 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -109,6 +109,7 @@ This rule supports the following options (none of which are required): - [`sortTypesGroup`][7] - [`newlines-between-types`][27] - [`consolidateIslands`][25] +- [`followTsOrganizeImports`][26] --- @@ -958,6 +959,17 @@ import type { G } from './aaa.js' import type { H } from './bbb' ``` +### `followTsOrganizeImports` + +Valid values: `boolean` \ +Default: `false` + +> [!CAUTION] +> +> Currently, `followTsOrganizeImports` defaults to `false`. However, in a later update, the default might change to `true`. + +When set to `true`, this option will align the behavior with [TypeScript's LSP Organize Imports][34] feature. This only has an effect if no manual `groups` are defined. + ## Related - [`import-x/external-module-folders`][29] @@ -984,6 +996,7 @@ import type { H } from './bbb' [22]: https://prettier.io [23]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#type-modifiers-on-import-names [25]: #consolidateislands +[26]: #followtsorganizeimports [27]: #newlines-between-types [28]: ../../README.md#importinternal-regex [29]: ../../README.md#importexternal-module-folders @@ -991,3 +1004,4 @@ import type { H } from './bbb' [31]: https://webpack.js.org/guides/tree-shaking#mark-the-file-as-side-effect-free [32]: #distinctgroup [33]: #named +[34]: https://code.visualstudio.com/docs/typescript/typescript-refactoring#_organize-imports diff --git a/src/rules/order.ts b/src/rules/order.ts index 9c0a5cfb..b1208fdb 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -53,13 +53,13 @@ const defaultGroups = [ 'index', ] as const -const defaultGroupsOfficialOrganizeImports = [ +const defaultGroupsTsOrganizeImports = [ 'private-import', 'external', 'builtin', 'parent', - 'sibling', 'index', + 'sibling', ] as const // REPORTING AND FIXING @@ -1159,7 +1159,7 @@ export interface Options { pathGroups?: PathGroup[] sortTypesGroup?: boolean warnOnUnassignedImports?: boolean - privateImportsFeatureFlag?: boolean + followTsOrganizeImports?: boolean } type MessageId = @@ -1282,8 +1282,9 @@ export default createRule<[Options?], MessageId>({ type: 'boolean', default: false, }, - privateImportsFeatureFlag: { + followTsOrganizeImports: { type: 'boolean', + // TODO: switch default to true in next major default: false, }, }, @@ -1405,8 +1406,8 @@ export default createRule<[Options?], MessageId>({ ) const { groups, omittedTypes } = convertGroupsToRanks( options.groups || - (options.privateImportsFeatureFlag - ? defaultGroupsOfficialOrganizeImports + (options.followTsOrganizeImports + ? defaultGroupsTsOrganizeImports : defaultGroups), ) ranks = { diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index d82c9837..ef39e0cb 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4813,14 +4813,18 @@ describe('TypeScript', () => { tValid({ code: `import { internA } from "#a"; import { scopeA } from "@a/a"; +import a from 'a'; +import 'format.css'; import fs from 'node:fs'; import path from "path"; +import index from './'; import { localA } from "./a"; +import sibling from './foo'; `, ...parserConfig, options: [ { - privateImportsFeatureFlag: true, + followTsOrganizeImports: true, }, ], }), @@ -5224,7 +5228,7 @@ import { localA } from "./a"; ...parserConfig, options: [ { - privateImportsFeatureFlag: true, + followTsOrganizeImports: true, }, ], errors: [ From 9a0edb763861fc6fca22b159ba29abc0eb5bf322 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 11:08:03 +0200 Subject: [PATCH 07/21] chore: rename to just private --- src/rules/order.ts | 4 ++-- src/utils/import-type.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index b1208fdb..077c931e 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -54,7 +54,7 @@ const defaultGroups = [ ] as const const defaultGroupsTsOrganizeImports = [ - 'private-import', + 'private', 'external', 'builtin', 'parent', @@ -833,7 +833,7 @@ function getRequireBlock(node: TSESTree.Node) { const types: ImportType[] = [ 'builtin', 'external', - 'private-import', + 'private', 'internal', 'unknown', 'parent', diff --git a/src/utils/import-type.ts b/src/utils/import-type.ts index 4bef48cd..89c406d4 100644 --- a/src/utils/import-type.ts +++ b/src/utils/import-type.ts @@ -88,7 +88,8 @@ export function isScopedMain(name: string) { return !!name && scopedMainRegExp.test(name) } -function isPrivateImport(name: string) { +function isPrivate(name: string) { + // see https://nodejs.org/api/packages.html#imports return name.startsWith('#') } @@ -162,8 +163,8 @@ function typeTest( if (isBuiltIn(name, settings, path)) { return 'builtin' } - if (isPrivateImport(name)) { - return 'private-import' + if (isPrivate(name)) { + return 'private' } if (isRelativeToParent(name)) { return 'parent' From e47f0addf3fefd591430ae89d167df6da7f6e3a1 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 11:19:04 +0200 Subject: [PATCH 08/21] chore: add warning --- src/rules/order.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rules/order.ts b/src/rules/order.ts index 077c931e..d66b7569 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -1404,6 +1404,14 @@ export default createRule<[Options?], MessageId>({ const { pathGroups, maxPosition } = convertPathGroupsForRanks( options.pathGroups || [], ) + if (options.followTsOrganizeImports && options.groups) { + // TODO: remove warning when default of followTsOrganizeImports switched to true + // because then the user potentially knows what they are doing + // and the warning is not needed anymore + console.warn( + 'When `followTsOrganizeImports` set to `true` in `order` and you defined your own `groups`, `followTsOrganizeImports` wont have an effect.', + ) + } const { groups, omittedTypes } = convertGroupsToRanks( options.groups || (options.followTsOrganizeImports From f2b60113914fd8e5a99f4ccfe553fdf447247781 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 11:24:45 +0200 Subject: [PATCH 09/21] test: add explicit followTsOrganizeImports=false case --- test/rules/order.spec.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index ef39e0cb..393bc4d5 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4828,6 +4828,25 @@ import sibling from './foo'; }, ], }), + // test for explicit followTsOrganizeImports=false + tValid({ + code: `import 'format.css'; +import fs from 'node:fs'; +import path from "path"; +import a from 'a'; +import { scopeA } from "@a/a"; +import { localA } from "./a"; +import sibling from './foo'; +import index from './'; +import { internA } from "#a"; +`, + ...parserConfig, + options: [ + { + followTsOrganizeImports: false, + }, + ], + }), ], invalid: [ // Option alphabetize: {order: 'asc'} From 605a9f88c5f548f4af30f45cf00a5f348da1b03c Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 11:35:50 +0200 Subject: [PATCH 10/21] test: groups take precedence over followTsOrganizeImports --- test/rules/order.spec.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index 393bc4d5..136dab4e 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4847,6 +4847,26 @@ import { internA } from "#a"; }, ], }), + // manual `groups` always take precedence over `followTsOrganizeImports` + tValid({ + code: `import 'format.css'; +import a from 'a'; +import { scopeA } from "@a/a"; +import index from './'; +import fs from 'node:fs'; +import path from "path"; +import { localA } from "./a"; +import sibling from './foo'; +import { internA } from "#a"; +`, + ...parserConfig, + options: [ + { + groups: ['external', 'internal', 'index'], + followTsOrganizeImports: true, + }, + ], + }), ], invalid: [ // Option alphabetize: {order: 'asc'} From 708b5cc1868bc964e68b033a6b0d05d29c92474c Mon Sep 17 00:00:00 2001 From: Shinigami Date: Mon, 14 Apr 2025 11:49:56 +0200 Subject: [PATCH 11/21] chore: improve warning message --- src/rules/order.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index d66b7569..e5945fb5 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -1409,7 +1409,7 @@ export default createRule<[Options?], MessageId>({ // because then the user potentially knows what they are doing // and the warning is not needed anymore console.warn( - 'When `followTsOrganizeImports` set to `true` in `order` and you defined your own `groups`, `followTsOrganizeImports` wont have an effect.', + 'If you have defined your own `groups` in `order`, `followTsOrganizeImports: true` has no effect.', ) } const { groups, omittedTypes } = convertGroupsToRanks( From 3ea0b5bef1d9201c6535dc1191acd94968faee46 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 12:07:17 +0200 Subject: [PATCH 12/21] chore: use internal log --- src/rules/order.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index e5945fb5..9f09f40e 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -1408,8 +1408,8 @@ export default createRule<[Options?], MessageId>({ // TODO: remove warning when default of followTsOrganizeImports switched to true // because then the user potentially knows what they are doing // and the warning is not needed anymore - console.warn( - 'If you have defined your own `groups` in `order`, `followTsOrganizeImports: true` has no effect.', + log( + '`followTsOrganizeImports: true` has no effect when custom `groups` are defined.', ) } const { groups, omittedTypes } = convertGroupsToRanks( From 6a1397f86cc56ff0acf725c8e728e3f7101109b1 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 12:10:42 +0200 Subject: [PATCH 13/21] chore: add changeset --- .changeset/proud-comics-trade.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/proud-comics-trade.md diff --git a/.changeset/proud-comics-trade.md b/.changeset/proud-comics-trade.md new file mode 100644 index 00000000..12111d1a --- /dev/null +++ b/.changeset/proud-comics-trade.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-import-x": minor +--- + +feat: new usable group `private` for `order` rule From 213eb2178f48a985a29672825308bbc8a3261fda Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 12:55:25 +0200 Subject: [PATCH 14/21] docs: update --- docs/rules/order.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/rules/order.md b/docs/rules/order.md index 524111ca..8189fe54 100644 --- a/docs/rules/order.md +++ b/docs/rules/order.md @@ -119,7 +119,7 @@ Valid values: `("builtin" | "external" | "internal" | "unknown" | "parent" | "si Default: `["builtin", "external", "parent", "sibling", "index"]` Determines which imports are subject to ordering, and how to order -them. The predefined groups are: `"builtin"`, `"external"`, `"internal"`, +them. The predefined groups are: `"builtin"`, `"external"`, `"internal"`, `"private"`, `"unknown"`, `"parent"`, `"sibling"`, `"index"`, `"object"`, and `"type"`. The import order enforced by this rule is the same as the order of each group @@ -166,17 +166,21 @@ Roughly speaking, the grouping algorithm is as follows: 3. If the import is [type-only][6], `"type"` is in `groups`, and [`sortTypesGroup`][7] is disabled, it will be considered **type** (with additional implications if using [`pathGroups`][8] and `"type"` is in [`pathGroupsExcludedImportTypes`][9]) 4. If the import's specifier matches [`import-x/internal-regex`][28], it will be considered **internal** 5. If the import's specifier is an absolute path, it will be considered **unknown** -6. If the import's specifier has the name of a Node.js core module (using [is-core-module][10]), it will be considered **builtin** -7. If the import's specifier matches [`import-x/core-modules`][11], it will be considered **builtin** -8. If the import's specifier is a path relative to the parent directory of its containing file (e.g. starts with `../`), it will be considered **parent** -9. If the import's specifier is one of `['.', './', './index', './index.js']`, it will be considered **index** -10. If the import's specifier is a path relative to its containing file (e.g. starts with `./`), it will be considered **sibling** -11. If the import's specifier is a path pointing to a file outside the current package's root directory (determined using [package-up][12]), it will be considered **external** -12. If the import's specifier matches [`import-x/external-module-folders`][29] (defaults to matching anything pointing to files within the current package's `node_modules` directory), it will be considered **external** -13. If the import's specifier is a path pointing to a file within the current package's root directory (determined using [package-up][12]), it will be considered **internal** -14. If the import's specifier has a name that looks like a scoped package (e.g. `@scoped/package-name`), it will be considered **external** -15. If the import's specifier has a name that starts with a word character, it will be considered **external** -16. If this point is reached, the import will be ignored entirely +6. If the import's specifier is starting with `#`, it will be considered **private** +7. If the import's specifier has the name of a Node.js core module (using [is-core-module][10]), it will be considered **builtin** +8. If the import's specifier matches [`import-x/core-modules`][11], it will be considered **builtin** +9. If the import's specifier is a path relative to the parent directory of its containing file (e.g. starts with `../`), it will be considered **parent** +10. If the import's specifier is one of `['.', './', './index', './index.js']`, it will be considered **index** +11. If the import's specifier is a path relative to its containing file (e.g. starts with `./`), it will be considered **sibling** +12. If the import's specifier is a path pointing to a file outside the current package's root directory (determined using [package-up][12]), it will be considered **external** +13. If the import's specifier matches [`import-x/external-module-folders`][29] (defaults to matching anything pointing to files within the current package's `node_modules` directory), it will be considered **external** +14. If the import's specifier is a path pointing to a file within the current package's root directory (determined using [package-up][12]), it will be considered **internal** +15. If the import's specifier has a name that looks like a scoped package (e.g. `@scoped/package-name`), it will be considered **external** +16. If the import's specifier has a name that starts with a word character, it will be considered **external** +17. If this point is reached, the import will be ignored entirely + +If `followTsOrganizeImports` is enabled, the default grouping algorithm is following [TypeScript's LSP Organize Imports][34] feature. \ +However, the `followTsOrganizeImports` will be ignored if custom `groups` are defined. At the end of the process, if they co-exist in the same file, all top-level `require()` statements that haven't been ignored are shifted (with respect to their order) below any ES6 `import` or similar declarations. Finally, any type-only declarations are potentially reorganized according to [`sortTypesGroup`][7]. From 923ffb49d223be002b0c51f10534e74b9db7a31b Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Mon, 14 Apr 2025 14:49:46 +0200 Subject: [PATCH 15/21] chore: remove unnecessary log --- src/rules/order.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/rules/order.ts b/src/rules/order.ts index 9f09f40e..077c931e 100644 --- a/src/rules/order.ts +++ b/src/rules/order.ts @@ -1404,14 +1404,6 @@ export default createRule<[Options?], MessageId>({ const { pathGroups, maxPosition } = convertPathGroupsForRanks( options.pathGroups || [], ) - if (options.followTsOrganizeImports && options.groups) { - // TODO: remove warning when default of followTsOrganizeImports switched to true - // because then the user potentially knows what they are doing - // and the warning is not needed anymore - log( - '`followTsOrganizeImports: true` has no effect when custom `groups` are defined.', - ) - } const { groups, omittedTypes } = convertGroupsToRanks( options.groups || (options.followTsOrganizeImports From 48fc80546632f9ff74d4e8481bff256b8cf4960f Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Tue, 15 Apr 2025 10:18:44 +0200 Subject: [PATCH 16/21] chore: test for private as last --- src/utils/import-type.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/import-type.ts b/src/utils/import-type.ts index 89c406d4..8fffcfb5 100644 --- a/src/utils/import-type.ts +++ b/src/utils/import-type.ts @@ -163,9 +163,6 @@ function typeTest( if (isBuiltIn(name, settings, path)) { return 'builtin' } - if (isPrivate(name)) { - return 'private' - } if (isRelativeToParent(name)) { return 'parent' } @@ -185,6 +182,9 @@ function typeTest( if (typeof name === 'string' && isExternalLookingName(name)) { return 'external' } + if (typeof name === 'string' && isPrivate(name)) { + return 'private' + } return 'unknown' } From 3eaeedce842e633483e9512c2f6951c57553f4e7 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Tue, 15 Apr 2025 11:01:30 +0200 Subject: [PATCH 17/21] test: add TypeScript path mapping tests --- .../tsconfig-with-path-mapping.json | 15 +++++ test/rules/order.spec.ts | 62 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json diff --git a/test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json b/test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json new file mode 100644 index 00000000..41e14c3c --- /dev/null +++ b/test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "paths": { + "@/*": [ + "./src/*" + ], + "@components/*": [ + "./src/components/*" + ], + "#private": [ + "./src/private/*" + ] + } + } +} diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index 136dab4e..48f7f49f 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -1,3 +1,5 @@ +import path from 'node:path' + import { cjsRequire, cjsRequire as require } from '@pkgr/core' import { RuleTester as TSESLintRuleTester } from '@typescript-eslint/rule-tester' import type { TestCaseError as TSESLintTestCaseError } from '@typescript-eslint/rule-tester' @@ -4867,6 +4869,66 @@ import { internA } from "#a"; }, ], }), + // test with tsconfig paths mappings and followTsOrganizeImports: true + tValid({ + code: `import { internA } from "#a"; +import { privateA } from "#private/a"; +import { scopeA } from "@a/a"; +import a from 'a'; +import 'format.css'; +import fs from 'node:fs'; +import path from "path"; +import index from './'; +import { localA } from "./a"; +import sibling from './foo'; +`, + ...parserConfig, + settings: { + ...parserConfig.settings, + 'import-x/resolver': { + typescript: { + project: path.resolve( + 'test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', + ), + }, + }, + }, + options: [ + { + followTsOrganizeImports: true, + }, + ], + }), + // test with tsconfig paths mappings and followTsOrganizeImports: false + tValid({ + code: `import 'format.css'; +import fs from 'node:fs'; +import path from "path"; +import a from 'a'; +import { scopeA } from "@a/a"; +import { localA } from "./a"; +import sibling from './foo'; +import index from './'; +import { internA } from "#a"; +import { privateA } from "#private/a"; +`, + ...parserConfig, + settings: { + ...parserConfig.settings, + 'import-x/resolver': { + typescript: { + project: path.resolve( + 'test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', + ), + }, + }, + }, + options: [ + { + followTsOrganizeImports: false, + }, + ], + }), ], invalid: [ // Option alphabetize: {order: 'asc'} From a80faf7042103b9b96a926895220dd478b4fe846 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Tue, 15 Apr 2025 11:28:46 +0200 Subject: [PATCH 18/21] chore: use testFilePath --- test/rules/order.spec.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index 48f7f49f..fcda4b23 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -1,5 +1,3 @@ -import path from 'node:path' - import { cjsRequire, cjsRequire as require } from '@pkgr/core' import { RuleTester as TSESLintRuleTester } from '@typescript-eslint/rule-tester' import type { TestCaseError as TSESLintTestCaseError } from '@typescript-eslint/rule-tester' @@ -4887,8 +4885,8 @@ import sibling from './foo'; ...parserConfig.settings, 'import-x/resolver': { typescript: { - project: path.resolve( - 'test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', + project: testFilePath( + 'typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', ), }, }, @@ -4917,8 +4915,8 @@ import { privateA } from "#private/a"; ...parserConfig.settings, 'import-x/resolver': { typescript: { - project: path.resolve( - 'test/fixtures/typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', + project: testFilePath( + 'typescript-order-custom-paths-mapping/tsconfig-with-path-mapping.json', ), }, }, From e4ff4497a9e1fe8fc983ac0bbf4633d6fb96f1e1 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 16 Apr 2025 08:50:28 +0200 Subject: [PATCH 19/21] test: add mapped data --- test/fixtures/typescript-order-custom-paths-mapping/a.ts | 0 test/fixtures/typescript-order-custom-paths-mapping/src/a.ts | 0 .../typescript-order-custom-paths-mapping/src/components/index.ts | 0 test/fixtures/typescript-order-custom-paths-mapping/src/index.ts | 0 .../typescript-order-custom-paths-mapping/src/private/a.ts | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/a.ts create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/src/a.ts create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/src/components/index.ts create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/src/index.ts create mode 100644 test/fixtures/typescript-order-custom-paths-mapping/src/private/a.ts diff --git a/test/fixtures/typescript-order-custom-paths-mapping/a.ts b/test/fixtures/typescript-order-custom-paths-mapping/a.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/typescript-order-custom-paths-mapping/src/a.ts b/test/fixtures/typescript-order-custom-paths-mapping/src/a.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/typescript-order-custom-paths-mapping/src/components/index.ts b/test/fixtures/typescript-order-custom-paths-mapping/src/components/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/typescript-order-custom-paths-mapping/src/index.ts b/test/fixtures/typescript-order-custom-paths-mapping/src/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/typescript-order-custom-paths-mapping/src/private/a.ts b/test/fixtures/typescript-order-custom-paths-mapping/src/private/a.ts new file mode 100644 index 00000000..e69de29b From e9d728b3aa95c29321f2ffe5b9a395550499dece Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 16 Apr 2025 11:14:45 +0200 Subject: [PATCH 20/21] test: add external looking import case --- test/rules/order.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index 271ae5cf..cc8b4445 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4815,6 +4815,7 @@ describe('TypeScript', () => { import { scopeA } from "@a/a"; import a from 'a'; import 'format.css'; +import { glob } from 'glob'; import fs from 'node:fs'; import path from "path"; import index from './'; @@ -4833,6 +4834,7 @@ import sibling from './foo'; code: `import 'format.css'; import fs from 'node:fs'; import path from "path"; +import { glob } from 'glob'; import a from 'a'; import { scopeA } from "@a/a"; import { localA } from "./a"; @@ -4852,6 +4854,7 @@ import { internA } from "#a"; code: `import 'format.css'; import a from 'a'; import { scopeA } from "@a/a"; +import { glob } from 'glob'; import index from './'; import fs from 'node:fs'; import path from "path"; @@ -4874,6 +4877,7 @@ import { privateA } from "#private/a"; import { scopeA } from "@a/a"; import a from 'a'; import 'format.css'; +import { glob } from 'glob'; import fs from 'node:fs'; import path from "path"; import index from './'; @@ -4904,6 +4908,7 @@ import fs from 'node:fs'; import path from "path"; import a from 'a'; import { scopeA } from "@a/a"; +import { glob } from 'glob'; import { localA } from "./a"; import sibling from './foo'; import index from './'; From ab4ec1de9979da6568c2c025dcd393123597e5cd Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 16 Apr 2025 12:07:36 +0200 Subject: [PATCH 21/21] test: add case with separate group --- test/rules/order.spec.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/rules/order.spec.ts b/test/rules/order.spec.ts index cc8b4445..368dfb96 100644 --- a/test/rules/order.spec.ts +++ b/test/rules/order.spec.ts @@ -4932,6 +4932,28 @@ import { privateA } from "#private/a"; }, ], }), + // test followTsOrganizeImports: true and splitted node:* group + tValid({ + code: `import fs from 'node:fs'; +import path from "path"; + +import { internA } from "#a"; +import { privateA } from "#private/a"; +import { scopeA } from "@a/a"; +import a from 'a'; +import 'format.css'; +import { glob } from 'glob'; +import index from './'; +import { localA } from "./a"; +import sibling from './foo'; +`, + ...parserConfig, + options: [ + { + followTsOrganizeImports: true, + }, + ], + }), ], invalid: [ // Option alphabetize: {order: 'asc'}