diff --git a/README.md b/README.md index 72289a1..8c2c83d 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ function compare(a, b) { In other words, the imports/exports within groups are sorted alphabetically, case-insensitively and treating numbers like a human would, falling back to good old character code sorting in case of ties. See [Intl.Collator] for more information. Note: `Intl.Collator` sorts punctuation in _some_ defined order. I have no idea what order punctuation sorts in, and I don’t care. There’s no ordered “alphabet” for punctuation that I know of. -There’s one addition to the alphabetical rule: Directory structure. Relative imports/exports of files higher up in the directory structure come before closer ones – `"../../utils"` comes before `"../utils"`, which comes before `"."`. (In short, `.` and `/` sort before any other (non-whitespace, non-control) character. `".."` and similar sort like `"../,"` (to avoid the “shorter prefix comes first” sorting concept).) +There’s one addition to the alphabetical rule: Directory structure. Relative imports/exports of files higher up in the directory structure come before closer ones – `"../../utils"` comes before `"../utils"`, which comes before `"."`. Note: No path or URL normalization is performed – the plugin simply looks for how many times `../` is repeated at the start of the `from` string. If you put a `./` in there (like `.././../` or `./../../`) then the directory structure sorting won’t work. Simply don’t do that. Also, you _have_ to use forward slashes – backslashes are not supported. If both `import type` _and_ regular imports are used for the same source, the type imports come first. Same thing for `export type`. (You can move type imports to their own group, as mentioned in [custom grouping].) @@ -241,9 +241,9 @@ import fs2 from "fs"; import b from "https://example.com/script.js"; // Absolute imports and other imports. +import Error from "@/components/error.vue"; import c from "/"; import d from "/home/user/foo"; -import Error from "@/components/error.vue"; // Relative imports. import e from "../.."; @@ -255,8 +255,8 @@ import i from "./styles"; // Different types of exports: export { a } from "../.."; -export { b } from "/"; export { Error } from "@/components/error.vue"; +export { b } from "/"; export * from "an-npm-package"; export { readFile } from "fs"; export * as ns from "https://example.com/script.js"; @@ -655,7 +655,7 @@ Some other differences: The first question to ask yourself is if dprint is good enough. If so, you’ve got one tool less to worry about! -If you’d like to enforce grouping, though, you could still use `eslint-plugin-simple-import-sort`. However, the two might disagree slightly on some sorting edge cases. So it’s better to turn off sorting in your dprint config file: +If you’d like to enforce grouping, though, you could still use `eslint-plugin-simple-import-sort`. There is a risk that the two might disagree slightly on some sorting edge cases. So might want to turn off sorting in your dprint config file: ```json { diff --git a/examples/readme-order.prettier.ts b/examples/readme-order.prettier.ts index f5e9c73..8d76ddd 100644 --- a/examples/readme-order.prettier.ts +++ b/examples/readme-order.prettier.ts @@ -13,9 +13,9 @@ import fs2 from "fs"; import b from "https://example.com/script.js"; // Absolute imports and other imports. +import Error from "@/components/error.vue"; import c from "/"; import d from "/home/user/foo"; -import Error from "@/components/error.vue"; // Relative imports. import e from "../.."; @@ -27,8 +27,8 @@ import i from "./styles"; // Different types of exports: export { a } from "../.."; -export { b } from "/"; export { Error } from "@/components/error.vue"; +export { b } from "/"; export * from "an-npm-package"; export { readFile } from "fs"; export * as ns from "https://example.com/script.js"; diff --git a/src/shared.js b/src/shared.js index 60c92d9..b3cb1c3 100644 --- a/src/shared.js +++ b/src/shared.js @@ -798,32 +798,13 @@ function isNewline(node) { function getSource(node) { const source = node.source.value; + const match = /^(?:\.\.\/)*\.\.(?=\/|$)/.exec(source); + const level = + Number.MAX_SAFE_INTEGER - (match === null ? 0 : match[0].length); + return { // Sort by directory level rather than by string length. - source: source - // Treat `.` as `./`, `..` as `../`, `../..` as `../../` etc. - .replace(/^[./]*\.$/, "$&/") - // Make `../` sort after `../../` but before `../a` etc. - // Why a comma? See the next comment. - .replace(/^[./]*\/$/, "$&,") - // Make `.` and `/` sort before any other punctation. - // The default order is: _ - , x x x . x x x / x x x - // We’re changing it to: . / , x x x _ x x x - x x x - .replace(/[./_-]/g, (char) => { - switch (char) { - case ".": - return "_"; - case "/": - return "-"; - case "_": - return "."; - case "-": - return "/"; - // istanbul ignore next - default: - throw new Error(`Unknown source substitution character: ${char}`); - } - }), + source: `${level}:${source}`, originalSource: source, kind: getImportExportKind(node), }; diff --git a/test/__snapshots__/examples.test.js.snap b/test/__snapshots__/examples.test.js.snap index c706ca1..a393531 100644 --- a/test/__snapshots__/examples.test.js.snap +++ b/test/__snapshots__/examples.test.js.snap @@ -104,8 +104,8 @@ import styles from "./styles.scss"; exports[`examples groups.default-reverse.js 1`] = ` import styles from "./styles"; -import config from "/config"; import App from "@/App"; +import config from "/config"; import { storiesOf } from "@storybook/react"; import fs2 from "fs"; @@ -132,9 +132,9 @@ import styles from "./styles.css"; exports[`examples groups.none.js 1`] = ` import styles from "./styles"; -import config from "/config"; import App from "@/App"; import { storiesOf } from "@storybook/react"; +import config from "/config"; import * as fs from "node:fs"; import react from "react"; @@ -143,9 +143,9 @@ import react from "react"; exports[`examples groups.type-imports-first.ts 1`] = ` import type { Page } from "./page"; import type { Css } from "./styles"; -import type { Config } from "/config"; import type { AppRouter } from "@/App"; import type { Story } from "@storybook/react"; +import type { Config } from "/config"; import type { Dirent } from "node:fs"; import type { ParsedPath } from "node:path"; import type { Component } from "react"; @@ -159,8 +159,8 @@ import * as path from "node:path"; import { storiesOf } from "@storybook/react"; import react from "react"; -import config from "/config"; import App from "@/App"; +import config from "/config"; import page from "./page"; import styles from "./styles"; @@ -181,10 +181,10 @@ import type { Store } from "redux"; import { storiesOf } from "@storybook/react"; import react from "react"; -import type { Config } from "/config"; import type { AppRouter } from "@/App"; -import config from "/config"; +import type { Config } from "/config"; import App from "@/App"; +import config from "/config"; import type { Page } from "./page"; import type { Css } from "./styles"; @@ -199,8 +199,8 @@ import type { ParsedPath } from "node:path"; import type { Story } from "@storybook/react"; import type { Component } from "react"; import type { Store } from "redux"; -import type { Config } from "/config"; import type { AppRouter } from "@/App"; +import type { Config } from "/config"; import type { Page } from "./page"; import type { Css } from "./styles"; @@ -212,8 +212,8 @@ import * as path from "node:path"; import { storiesOf } from "@storybook/react"; import react from "react"; -import config from "/config"; import App from "@/App"; +import config from "/config"; import page from "./page"; import styles from "./styles"; @@ -229,17 +229,17 @@ import * as path from "node:path"; import { storiesOf } from "@storybook/react"; import react from "react"; -import config from "/config"; import App from "@/App"; +import config from "/config"; import page from "./page"; import styles from "./styles"; import type { Page } from "./page"; import type { Css } from "./styles"; -import type { Config } from "/config"; import type { AppRouter } from "@/App"; import type { Story } from "@storybook/react"; +import type { Config } from "/config"; import type { Dirent } from "node:fs"; import type { ParsedPath } from "node:path"; import type { Component } from "react"; @@ -261,10 +261,10 @@ import type { Story } from "@storybook/react"; import type { Component } from "react"; import type { Store } from "redux"; -import config from "/config"; import App from "@/App"; -import type { Config } from "/config"; +import config from "/config"; import type { AppRouter } from "@/App"; +import type { Config } from "/config"; import page from "./page"; import styles from "./styles"; @@ -282,8 +282,8 @@ import * as path from "node:path"; import { storiesOf } from "@storybook/react"; import react from "react"; -import config from "/config"; import App from "@/App"; +import config from "/config"; import page from "./page"; import styles from "./styles"; @@ -293,8 +293,8 @@ import type { ParsedPath } from "node:path"; import type { Story } from "@storybook/react"; import type { Component } from "react"; import type { Store } from "redux"; -import type { Config } from "/config"; import type { AppRouter } from "@/App"; +import type { Config } from "/config"; import type { Page } from "./page"; import type { Css } from "./styles"; @@ -484,9 +484,9 @@ import fs2 from "fs"; import b from "https://example.com/script.js"; // Absolute imports and other imports. +import Error from "@/components/error.vue"; import c from "/"; import d from "/home/user/foo"; -import Error from "@/components/error.vue"; // Relative imports. import e from "../.."; @@ -498,8 +498,8 @@ import i from "./styles"; // Different types of exports: export { a } from "../.."; -export { b } from "/"; export { Error } from "@/components/error.vue"; +export { b } from "/"; export * from "an-npm-package"; export { readFile } from "fs"; export * as ns from "https://example.com/script.js"; diff --git a/test/exports.test.js b/test/exports.test.js index 7680c7d..6c1ebae 100644 --- a/test/exports.test.js +++ b/test/exports.test.js @@ -815,8 +815,8 @@ const flowTests = { |export type {Z} from "Z"; |export type Y = 5; |export type {B} from "./B"; - |export type {C} from "/B"; |export type {E} from "@/B"; + |export type {C} from "/B"; |export type {X} from "X"; `); }, @@ -1099,8 +1099,8 @@ const typescriptTests = { |export type Y = 5; |export {a, type type as type, z} from "../type"; |export type {B} from "./B"; - |export type {C} from "/B"; |export type {E} from "@/B"; + |export type {C} from "/B"; |export type {X} from "X"; `); }, diff --git a/test/imports.test.js b/test/imports.test.js index 4ddd5fb..b81a4a3 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -874,55 +874,55 @@ const baseTests = (expect) => ({ |import {} from "react"; | |import {} from ""; + |import {} from "@/components/Alert" + |import {} from "@/components/error.vue" |import {} from "/"; |import {} from "/a"; |import {} from "/a/b"; - |import {} from "@/components/Alert" - |import {} from "@/components/error.vue" |import {} from "#/test" |import {} from "~/test" | - |import {} from "..."; - |import {} from ".../"; |import {} from "../../.."; |import {} from "../../../"; - |import {} from "../../../,"; |import {} from "../../../_a"; + |import {} from "../../../,"; |import {} from "../../../[id]"; |import {} from "../../../a"; |import {} from "../../../utils"; |import {} from "../.."; |import {} from "../../"; - |import {} from "../../,"; |import {} from "../../_a"; - |import {} from "../../[id]"; |import {} from "../../-a"; + |import {} from "../../,"; + |import {} from "../../[id]"; |import {} from "../../a"; |import {} from "../../utils"; |import {} from ".."; |import {} from "../"; - |import {} from "../,"; |import {} from "../_a"; - |import {} from "../[id]"; |import {} from "../-a"; + |import {} from "../,"; + |import {} from "../[id]"; |import {} from "../a"; |import {} from "../a/.."; |import {} from "../a/..."; |import {} from "../a/../"; |import {} from "../a/../b"; - |import {} from ".//"; |import {} from "."; + |import {} from "..."; + |import {} from ".../"; |import {} from "./"; - |import {} from "./,"; |import {} from "./_a"; - |import {} from "./[id]"; |import {} from "./-a"; + |import {} from "./,"; + |import {} from "./[id]"; + |import {} from ".//"; |import {} from "./A"; |import {} from "./a"; |import {} from "./ä"; // “a” followed by “̈̈” (COMBINING DIAERESIS). |import {} from "./ä"; - |import {} from "./a/."; |import {} from "./a/-"; + |import {} from "./a/."; |import {} from "./a/0"; |import {} from "./B"; // B1 |import {} from "./B"; // B2 @@ -1570,6 +1570,30 @@ const baseTests = (expect) => ({ }, errors: 1, }, + + // groups – all in one group. + { + options: [{ groups: [] }], + code: input` + |import styles from "./styles"; + |import App from "@/App"; + |import { storiesOf } from "@storybook/react"; + |import config from "/config"; + |import react from "react"; + |import * as fs from "node:fs"; + `, + output: (actual) => { + expect(actual).toMatchInlineSnapshot(` + |import styles from "./styles"; + |import App from "@/App"; + |import { storiesOf } from "@storybook/react"; + |import config from "/config"; + |import * as fs from "node:fs"; + |import react from "react"; + `); + }, + errors: 1, + }, ], }); @@ -1632,8 +1656,8 @@ const flowTests = { |import type {X} from "X"; |import type {Z} from "Z"; | - |import type C from "/B"; |import type E from "@/B"; + |import type C from "/B"; | |import type B from "./B"; |import typeof D from "./D";