Skip to content

Commit

Permalink
Allow customizing where type imports go (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
lydell committed Dec 8, 2020
2 parents 9d89748 + 5dd83b9 commit 6bb3b59
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 5 deletions.
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -5,7 +5,7 @@ Easy autofixable import sorting.
- ✅️ Runs via `eslint --fix` – no new tooling
- ✅️ Also sorts exports where possible
- ✅️ Handles comments
- ✅️ Handles [Flow type imports] \(via [babel-eslint])
- ✅️ Handles type imports/exports
- ✅️ [TypeScript] friendly \(via [@typescript-eslint/parser])
- ✅️ [Prettier] friendly
- ✅️ [eslint-plugin-import] friendly
Expand All @@ -16,9 +16,7 @@ Easy autofixable import sorting.
This is for those who use `eslint --fix` (autofix) a lot and want to completely forget about sorting imports!

[@typescript-eslint/parser]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser
[babel-eslint]: https://github.com/babel/babel-eslint
[eslint-plugin-import]: https://github.com/benmosher/eslint-plugin-import/
[flow type imports]: https://flow.org/en/docs/types/modules/
[no-require]: https://github.com/lydell/eslint-plugin-simple-import-sort/#does-it-support-require
[prettier]: https://prettier.io/
[typescript]: https://www.typescriptlang.org/
Expand Down Expand Up @@ -257,7 +255,7 @@ In other words, the imports/exports within groups are sorted alphabetically, cas

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).)

If both `import type` _and_ regular imports are used for the same source, the type imports come first. Same thing for `export type`.
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].)

### Example

Expand Down Expand Up @@ -395,7 +393,9 @@ Each `import` is matched against _all_ regexes on the `from` string. The import
Imports that don’t match any regex are grouped together last.

Side effect imports have `\u0000` prepended to their `from` string. You can match them with `"^\\u0000"`.
Side effect imports have `\u0000` _prepended_ to their `from` string (starts with `\u0000`). You can match them with `"^\\u0000"`.

Type imports have `\u0000` _appended_ to their `from` string (ends with `\u0000`). You can match them with `"\\u0000$"` – but you probably need more than that to avoid them also being matched by other groups.

The inner arrays are joined with one newline; the outer arrays are joined with two (creating a blank line).

Expand Down
102 changes: 102 additions & 0 deletions examples/.eslintrc.js
Expand Up @@ -118,6 +118,108 @@ module.exports = {
],
},
},
{
files: ["groups.type-imports-first.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports first as a separate group.
groups: [["^.*\\u0000$"], ["^\\u0000"], ["^@?\\w"], ["^"], ["^\\."]],
},
],
},
},
{
files: ["groups.type-imports-last.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports last as a separate group.
groups: [["^\\u0000"], ["^@?\\w"], ["^"], ["^\\."], ["^.+\\u0000$"]],
},
],
},
},
{
files: ["groups.type-imports-first-sorted.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports first as a separate
// group, sorting that group like non-type imports are grouped.
groups: [
["^@?\\w.*\\u0000$", "^[^.].*\\u0000$", "^\\..*\\u0000$"],
["^\\u0000"],
["^@?\\w"],
["^"],
["^\\."],
],
},
],
},
},
{
files: ["groups.type-imports-last-sorted.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports last as a separate
// group, sorting that group like non-type imports are grouped.
groups: [
["^\\u0000"],
["^@?\\w"],
["^"],
["^\\."],
["^@?\\w.*\\u0000$", "^[^.].*\\u0000$", "^\\..*\\u0000$"],
],
},
],
},
},
{
files: ["groups.type-imports-first-in-each-group.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports first in each group.
groups: [
["^\\u0000"],
["^@?\\w.*\\u0000$", "^@?\\w"],
["(?<=\\u0000)$", "^"],
["^\\..*\\u0000$", "^\\."],
],
},
],
},
},
{
files: ["groups.type-imports-last-in-each-group.ts"],
parser: "@typescript-eslint/parser",
rules: {
imports: [
"error",
{
// The default grouping, but with type imports last in each group.
groups: [
["^\\u0000"],
["^@?\\w", "^@?\\w.*\\u0000$"],
["(?<!\\u0000)$", "(?<=\\u0000)$"],
["^\\.", "^\\..*\\u0000$"],
],
},
],
},
},
{
files: ["groups.none.js"],
rules: {
Expand Down
14 changes: 14 additions & 0 deletions examples/groups.type-imports-first-in-each-group.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
14 changes: 14 additions & 0 deletions examples/groups.type-imports-first-sorted.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
14 changes: 14 additions & 0 deletions examples/groups.type-imports-first.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
14 changes: 14 additions & 0 deletions examples/groups.type-imports-last-in-each-group.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
14 changes: 14 additions & 0 deletions examples/groups.type-imports-last-sorted.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
14 changes: 14 additions & 0 deletions examples/groups.type-imports-last.ts
@@ -0,0 +1,14 @@
import "./polyfills";
import react from "react";
import type { Component } from "react";
import type { Store } from "redux";
import type { Story } from "@storybook/react";
import { storiesOf } from "@storybook/react";
import type { AppRouter } from "@/App";
import App from "@/App";
import type { Page } from "./page";
import page from "./page";
import type { Css } from "./styles";
import styles from "./styles";
import config from "/config";
import type { Config } from "/config";
2 changes: 2 additions & 0 deletions src/imports.js
Expand Up @@ -87,6 +87,8 @@ function makeSortedItems(items, outerGroups) {
const { originalSource } = item.source;
const source = item.isSideEffectImport
? `\0${originalSource}`
: item.source.kind !== "value"
? `${originalSource}\0`
: originalSource;
const [matchedGroup] = shared
.flatMap(itemGroups, (groups) =>
Expand Down
130 changes: 130 additions & 0 deletions test/__snapshots__/examples.test.js.snap
Expand Up @@ -135,6 +135,136 @@ 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 { Component } from "react";
import type { Store } from "redux";
import "./polyfills";
import { storiesOf } from "@storybook/react";
import react from "react";
import config from "/config";
import App from "@/App";
import page from "./page";
import styles from "./styles";
`;

exports[`examples groups.type-imports-first-in-each-group.ts 1`] = `
import "./polyfills";
import type { Story } from "@storybook/react";
import type { Component } from "react";
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 App from "@/App";
import type { Page } from "./page";
import type { Css } from "./styles";
import page from "./page";
import styles from "./styles";
`;

exports[`examples groups.type-imports-first-sorted.ts 1`] = `
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 { Page } from "./page";
import type { Css } from "./styles";
import "./polyfills";
import { storiesOf } from "@storybook/react";
import react from "react";
import config from "/config";
import App from "@/App";
import page from "./page";
import styles from "./styles";
`;

exports[`examples groups.type-imports-last.ts 1`] = `
import "./polyfills";
import { storiesOf } from "@storybook/react";
import react from "react";
import config from "/config";
import App from "@/App";
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 { Component } from "react";
import type { Store } from "redux";
`;

exports[`examples groups.type-imports-last-in-each-group.ts 1`] = `
import "./polyfills";
import { storiesOf } from "@storybook/react";
import react from "react";
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 type { AppRouter } from "@/App";
import page from "./page";
import styles from "./styles";
import type { Page } from "./page";
import type { Css } from "./styles";
`;

exports[`examples groups.type-imports-last-sorted.ts 1`] = `
import "./polyfills";
import { storiesOf } from "@storybook/react";
import react from "react";
import config from "/config";
import App from "@/App";
import page from "./page";
import styles from "./styles";
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 { Page } from "./page";
import type { Css } from "./styles";
`;

exports[`examples ignore.js 1`] = `
// First off, imports that are only used for side effects stay in the input
// order. These won’t be sorted:
Expand Down

0 comments on commit 6bb3b59

Please sign in to comment.