From c439402e8a3ed653d89d1fc8f3530cc08a25c54c Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 7 Nov 2020 22:32:41 +0100 Subject: [PATCH] Fix directory structure sort order Closes #50 --- README.md | 4 +- examples/readme-order.prettier.js | 2 +- src/sort.js | 31 ++++++++++---- test/__snapshots__/examples.test.js.snap | 6 +-- test/sort.test.js | 54 +++++++++++++++++++++--- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ef05341..c55f8ea 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ function compare(a, b) { In other words, the imports 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. -Since “.” sorts before “/”, relative imports of files higher up in the directory structure come before closer ones – `"../../utils"` comes before `"../utils"`. Perhaps surprisingly though, `".."` would come before `"../../utils"` (since shorter substrings sort before longer strings). For that reason there’s one addition to the alphabetical rule: `"."` and `".."` are treated as `"./"` and `"../"`. +There’s one addition to the alphabetical rule: Directory structure. Relative imports 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).) If both `import type` _and_ regular imports are used for the same source, the type imports come first. @@ -236,9 +236,9 @@ import fs 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 "../.."; diff --git a/examples/readme-order.prettier.js b/examples/readme-order.prettier.js index 5a528b9..f9dfc34 100644 --- a/examples/readme-order.prettier.js +++ b/examples/readme-order.prettier.js @@ -10,9 +10,9 @@ import fs 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 "../.."; diff --git a/src/sort.js b/src/sort.js index 93bf3e8..402080c 100644 --- a/src/sort.js +++ b/src/sort.js @@ -874,15 +874,28 @@ function getSource(importNode) { const source = importNode.source.value; return { - source: - // Due to "." sorting before "/" by default, relative imports are - // automatically sorted in a logical manner for us: Imports from files - // further up come first, with deeper imports last. There’s one - // exception, though: When the `from` part ends with one or two dots: - // "." and "..". Those are supposed to sort just like "./", "../". So - // add in the slash for them. (No special handling is done for cases - // like "./a/.." because nobody writes that anyway.) - source === "." || source === ".." ? `${source}/` : source, + // 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 "/"; + } + }), originalSource: source, importKind: getImportKind(importNode), }; diff --git a/test/__snapshots__/examples.test.js.snap b/test/__snapshots__/examples.test.js.snap index 1dc68d3..9645596 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 App from "@/App"; import config from "/config"; +import App from "@/App"; import { storiesOf } from "@storybook/react"; import react from "react"; @@ -128,9 +128,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 react from "react"; `; @@ -293,9 +293,9 @@ import fs 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 "../.."; diff --git a/test/sort.test.js b/test/sort.test.js index 90cf253..6c28617 100644 --- a/test/sort.test.js +++ b/test/sort.test.js @@ -865,11 +865,19 @@ const baseTests = (expect) => ({ |import {} from "./B"; // B2 |import {} from "./A"; |import {} from "./a"; + |import {} from "./_a"; + |import {} from "./-a"; + |import {} from "./[id]"; + |import {} from "./,"; |import {} from "./ä"; |import {} from "./ä"; // “a” followed by “\u0308̈” (COMBINING DIAERESIS). |import {} from ".."; |import {} from "../"; |import {} from "../a"; + |import {} from "../_a"; + |import {} from "../-a"; + |import {} from "../[id]"; + |import {} from "../,"; |import {} from "../a/.."; |import {} from "../a/../"; |import {} from "../a/..."; @@ -877,6 +885,18 @@ const baseTests = (expect) => ({ |import {} from "../../"; |import {} from "../.."; |import {} from "../../a"; + |import {} from "../../_a"; + |import {} from "../../-a"; + |import {} from "../../[id]"; + |import {} from "../../,"; + |import {} from "../../utils"; + |import {} from "../../.."; + |import {} from "../../../"; + |import {} from "../../../a"; + |import {} from "../../../_a"; + |import {} from "../../../[id]"; + |import {} from "../../../,"; + |import {} from "../../../utils"; |import {} from "..."; |import {} from ".../"; |import {} from ".a"; @@ -924,35 +944,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 "../../../"; + |import {} from "../../../,"; + |import {} from "../../../_a"; + |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 "../../a"; + |import {} from "../../utils"; + |import {} from ".."; + |import {} from "../"; + |import {} from "../,"; + |import {} from "../_a"; + |import {} from "../[id]"; + |import {} from "../-a"; |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 "./_a"; + |import {} from "./[id]"; + |import {} from "./-a"; |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 @@ -1662,8 +1702,8 @@ const flowTests = { |import type {X} from "X"; |import type {Z} from "Z"; | - |import type E from "@/B"; |import type C from "/B"; + |import type E from "@/B"; | |import type B from "./B"; |import typeof D from "./D";