From 6e98c60df60b77b0f1a1fd79017a7e338184f859 Mon Sep 17 00:00:00 2001 From: Mark Skelton Date: Sat, 15 Apr 2023 23:27:24 -0500 Subject: [PATCH] feat: Add `type` as a sort group --- docs/rules/exports.md | 2 + docs/rules/imports.md | 2 + src/__tests__/exports.spec.ts | 74 +++++++++++++++++++++++++++++++++++ src/__tests__/imports.spec.ts | 68 ++++++++++++++++++++++++++++++++ src/rules/exports.ts | 18 ++++++++- src/rules/imports.ts | 12 +++++- 6 files changed, 172 insertions(+), 4 deletions(-) diff --git a/docs/rules/exports.md b/docs/rules/exports.md index 468f458..47e2714 100644 --- a/docs/rules/exports.md +++ b/docs/rules/exports.md @@ -63,6 +63,8 @@ There are four built-in sort groups you can use: source. - Useful for differentiating between path aliases (e.g. `components/Hello`) and dependencies (e.g. `react`). +1. `type` + - TypeScript type only imports (e.g. `export type { Foo } from 'foo'`) 1. `other` - Catch all sort group for any exports which did not match other sort groups. diff --git a/docs/rules/imports.md b/docs/rules/imports.md index d23f9c7..8f54f82 100644 --- a/docs/rules/imports.md +++ b/docs/rules/imports.md @@ -57,6 +57,8 @@ There are three built-in sort groups you can use: - Imports which do not throw an error when calling `require.resolve`. - Useful for differentiating between path aliases (e.g. `components/Hello`) and dependencies (e.g. `react`) +1. `type` + - TypeScript type only imports (e.g. `import type { Foo } from 'foo'`) 1. `other` - Catch all sort group for any imports which did not match other sort groups. diff --git a/src/__tests__/exports.spec.ts b/src/__tests__/exports.spec.ts index 89d2493..9706083 100644 --- a/src/__tests__/exports.spec.ts +++ b/src/__tests__/exports.spec.ts @@ -320,6 +320,7 @@ createRuleTester({ }, }).run("sort/exports", rule, { valid: [ + // typeOrder { name: "typeOrder: keep", code: dedent` @@ -348,8 +349,38 @@ createRuleTester({ export { b } from 'b' `, }, + + // Sort groups + { + code: dedent` + const mark = '' + + export default React + export { relA } from './a' + export { relB } from './b' + export { depA } from 'dependency-a' + export { depB } from 'dependency-b' + export type { A } from 'dependency-a' + export * from 'a' + export { b } from 'b' + export { mark } + `.trim(), + options: [ + { + groups: [ + { type: "default", order: 10 }, + { type: "sourceless", order: 60 }, + { type: "type", order: 40 }, + { regex: "^\\.+\\/", order: 20 }, + { type: "dependency", order: 30 }, + { type: "other", order: 50 }, + ], + }, + ], + }, ], invalid: [ + // typeOrder { name: "typeOrder: keep", code: dedent` @@ -400,5 +431,48 @@ createRuleTester({ options: [{ typeOrder: "first" }], errors: [{ messageId: "unsorted" }], }, + + // Sort groups + { + code: dedent` + const mark = '' + + export type { A } from 'dependency-a' + export { depB } from 'dependency-b' + export { mark } + export default React + export { relB } from './b' + export * from 'a' + export { relA } from './a' + export { depA } from 'dependency-a' + export { b } from 'b' + `.trim(), + output: dedent` + const mark = '' + + export * from 'a' + export { b } from 'b' + export { depA } from 'dependency-a' + export { depB } from 'dependency-b' + export { relA } from './a' + export { relB } from './b' + export type { A } from 'dependency-a' + export { mark } + export default React + `.trim(), + options: [ + { + groups: [ + { type: "default", order: 60 }, + { type: "sourceless", order: 50 }, + { type: "type", order: 40 }, + { regex: "^\\.+\\/", order: 30 }, + { type: "dependency", order: 20 }, + { type: "other", order: 10 }, + ], + }, + ], + errors: [{ messageId: "unsorted" }], + }, ], }) diff --git a/src/__tests__/imports.spec.ts b/src/__tests__/imports.spec.ts index bfeb1eb..b39834a 100644 --- a/src/__tests__/imports.spec.ts +++ b/src/__tests__/imports.spec.ts @@ -819,6 +819,7 @@ createRuleTester({ }, }).run("sort/imports", rule, { valid: [ + // typeOrder { name: "typeOrder: keep", code: dedent` @@ -847,8 +848,40 @@ createRuleTester({ import { b } from 'b' `, }, + + // Sort groups + { + name: "Sort groups", + code: dedent` + import 'index.css' + import 'side-effect' + import a from "dependency-b" + import b from "dependency-c" + import type { A } from "dependency-a" + import c from "a.png" + import d from "b.jpg" + import e from "a" + import f from "b" + import g from "c" + import h from "../b" + import i from "./b" + `, + options: [ + { + groups: [ + { type: "side-effect", order: 10 }, + { type: "type", order: 30 }, + { regex: "\\.(png|jpg)$", order: 40 }, + { regex: "^\\.+\\/", order: 60 }, + { type: "dependency", order: 20 }, + { type: "other", order: 50 }, + ], + }, + ], + }, ], invalid: [ + // typeOrder { name: "typeOrder: keep", code: dedent` @@ -899,5 +932,40 @@ createRuleTester({ options: [{ typeOrder: "first" }], errors: [{ messageId: "unsorted" }], }, + + // Sort groups + { + name: "Sort groups", + code: dedent` + import c from "a.png" + import h from "../b" + import b from "dependency-c" + import type { A } from "dependency-a" + import d from "b.jpg" + import a from "dependency-b" + import i from "./b" + `, + output: dedent` + import a from "dependency-b" + import b from "dependency-c" + import type { A } from "dependency-a" + import c from "a.png" + import d from "b.jpg" + import h from "../b" + import i from "./b" + `, + errors: [{ messageId: "unsorted" }], + options: [ + { + groups: [ + { type: "type", order: 30 }, + { regex: "\\.(png|jpg)$", order: 40 }, + { regex: "^\\.+\\/", order: 60 }, + { type: "dependency", order: 20 }, + { type: "other", order: 50 }, + ], + }, + ], + }, ], }) diff --git a/src/rules/exports.ts b/src/rules/exports.ts index f8daa2b..98f7d62 100644 --- a/src/rules/exports.ts +++ b/src/rules/exports.ts @@ -15,9 +15,17 @@ import { type Export = Exclude +const sortGroupsTypes = [ + "default", + "sourceless", + "dependency", + "type", + "other", +] as const + interface SortGroup { order: number - type?: "default" | "sourceless" | "dependency" | "other" + type?: (typeof sortGroupsTypes)[number] regex?: string } @@ -46,6 +54,12 @@ function getSortGroup(sortGroups: SortGroup[], node: Export) { if (!isDefaultExport && !node.source) return order break + case "type": { + const { exportKind } = node as { exportKind?: string } + if (exportKind === "type") return order + break + } + case "dependency": if (isResolved(source)) return order break @@ -127,7 +141,7 @@ export default { type: "object", properties: { type: { - enum: ["default", "sourceless", "dependency", "other"], + enum: sortGroupsTypes, }, regex: { type: "string", diff --git a/src/rules/imports.ts b/src/rules/imports.ts index 52e7f46..ce6087d 100644 --- a/src/rules/imports.ts +++ b/src/rules/imports.ts @@ -18,9 +18,11 @@ import { TypeOrder, } from "../utils.js" +const sortGroupsTypes = ["side-effect", "dependency", "type", "other"] as const + interface SortGroup { order: number - type?: "dependency" | "side-effect" | "other" + type?: (typeof sortGroupsTypes)[number] regex?: string } @@ -44,6 +46,12 @@ function getSortGroup(sortGroups: SortGroup[], node: ImportDeclaration) { if (!node.specifiers.length) return order break + case "type": { + const { importKind } = node as { importKind?: string } + if (importKind === "type") return order + break + } + case "dependency": if (isResolved(source)) return order break @@ -226,7 +234,7 @@ export default { type: "object", properties: { type: { - enum: ["side-effect", "dependency", "other"], + enum: sortGroupsTypes, }, regex: { type: "string",