organizeImports
Summary
Section titled “Summary”- Rule available since:
v1.0.0
- Diagnostic Category:
assist/source/organizeImports
- This action is recommended.
- The default severity of this rule is information.
How to enable in your editor
Section titled “How to enable in your editor”{ "code_actions_on_format": { "source.organizeImports.biome": true, }}
{ "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit", }}
source.organizeImports.biome
Description
Section titled “Description”Provides a code action to sort the imports and exports in the file using a built-in or custom order.
Imports and exports are first separated into chunks, before being sorted. Imports or exports of a chunk are then grouped according to the user-defined groups. Within a group, imports are sorted using a built-in order that depends on the import/export kind, whether the import/export has attributes and the source being imported from. source is also often called specifier in the JavaScript ecosystem.
import A from "@my/lib" with { "attribute1": "value" };^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ kind source attributes
export * from "@my/lib" with { "attribute1": "value" };^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ kind source attributes
Chunk of imports and chunk of exports
Section titled “Chunk of imports and chunk of exports”A chunk is a sequence of adjacent imports or exports. A chunk contains only imports or exports, not both at the same time. The following example includes two chunks. The first chunk consists of the three imports and the second chunk consists of the three exports.
// chunk 1import A from "a";import * as B from "b";import { C } from "c";// chunk 2export * from "d";export * as F from "e";export { F } from "f";
Chunks also end as soon as a statement or a side-effect import (also called bare import) is encountered. Every side-effect import forms an independent chunk. The following example contains six chunks:
// chunk 1import A from "a";import * as B from "b";// chunk 2import "x";// chunk 3import "y";// chunk 4import { C } from "c";// chunk 5export * from "d";function f() {}// chunk 6export * as E from "e";export { F } from "f";
- The first chunk contains the two first
import
and ends with the appearance of the first side-effect importimport "x"
. - The second chunk contains only the side-effect import
import "x"
. - The third chunk contains only the side-effect import
import "y"
. - The fourth chunk contains a single
import
; The firstexport
ends it. - The fifth chunk contains the first
export
; The function declaration ends it. - The sixth chunk contains the last two
export
.
Chunks are also delimited by detached comments. A detached comment is a comment followed by a blank line. Comments not followed by a blank line are attached comments. Note that blank lines alone are not taken into account when chunking imports and exports. The following example contains a detached comment that splits the imports into two chunks:
// Attached comment 1import A from "a";
// Attached comment 2import * as B from "b";// Detached comment
import { C } from "c";
The line import { C } from "c"
forms the second chunk.
The blank line between the first two imports is ignored so they form a single chunk.
The sorter ensures that chunks are separated from each other with a blank lines. Only side-effect imports adjacent to a chunk of imports are not separated by a blank line. The following code…
import A from "a";import * as B from "b";import "x";import { C } from "c";export * from "d";// Detached comment
export * as F from "e";// Attached commentexport { F } from "f";
is sorted as:
import A from "a";import * as B from "b";import "x";import { C } from "c";
export * from "d";
// Detached comment
export * as F from "e";// Attached commentexport { F } from "f";
Also, note that blank lines inside a chunk are ignored and preserved. They can be removed by explicitly defining groups as demonstrated in the next section.
Import and export sorting
Section titled “Import and export sorting”Once chunks are formed, imports and exports of each chunk are sorted. Imports and exports are sorted by their source. Sources are ordered by “distance”. Sources that are “farther” from the current module are put on the top, sources “closer” to the user are put on the bottom. This leads to the following order:
- URLs such as
https://example.org
. - Packages with a protocol such as
node:path
,bun:test
,jsr:@my?lib
, ornpm:lib
. - Packages such as
mylib
or@my/lib
. - Aliases: sources starting with
@/
,#
,~
, or%
. They usually are Node.js subpath imports or TypeScript path aliases. - Absolute and relative paths.
Two imports/exports with the same source category are sorted using a natural sort order tailored to URLs, packages, and paths.
Notably, the order ensures that A < a < B < b
.
The order takes also numbers into account, e.g. a9 < a10
.
For example, the following code…
import sibling from "./file.js";import internal from "#alias";import fs from "fs";import { test } from "node:test";import path from "node:path";import parent from "../parent.js";import scopedLibUsingJsr from "jsr:@scoped/lib";import data from "https://example.org";import lib from "lib";import scopedLib from "@scoped/lib";
…is sorted as follows:
import data from "https://example.org";import scopedLibUsingJsr from "jsr:@scoped/lib";import path from "node:path";import { test } from "node:test";import scopedLib from "@scoped/lib";import fs from "fs";import lib from "lib";import internal from "#alias";import parent from "../parent.js";import sibling from "./file.js";
If two imports or exports share the same source and are in the same chunk, then they are ordered according to their kind as follows:
- Namespace type import / Namespace type export
- Default type import
- Named type import / Named type export
- Namespace import / Namespace export
- Combined default and namespace import
- Default import
- Combined default and named import
- Named import / Named export
Imports and exports with attributes are always placed first. For example, the following code…
import * as namespaceImport from "same-source";import type * as namespaceTypeImport from "same-source";import type { namedTypeImport } from "same-source";import defaultNamespaceCombined, * as namespaceCombined from "same-source";import defaultNamedCombined, { namedCombined } from "same-source";import defaultImport from "same-source";import type defaultTypeImport from "same-source";import { importWithAttribute } from "same-source" with { "attribute": "value" } ;
is sorted as follows:
import { importWithAttribute } from "same-source" with { "attribute": "value" } ;import type * as namespaceTypeImport from "same-source";import type defaultTypeImport from "same-source";import type { namedTypeImport } from "same-source";import * as namespaceImport from "same-source";import defaultNamespaceCombined, * as namespaceCombined from "same-source";import defaultImport from "same-source";import defaultNamedCombined, { namedCombined } from "same-source";
This default order cannot be changed. However, users can still customize how imports and exports are sorted using the concept of groups as explained in the following section.
Import and export groups
Section titled “Import and export groups”Imports or exports of a chunk are divided into groups before being sorted with the built-in order described in the previous section.
By default every chunk consists of a single group.
These default groups and their order may not be to your taste.
The sorter provides a groups
option that allows you to customize how the chunks are divided into groups.
The groups
option is a list of group matchers.
A group matcher is:
- A predefined group matcher, or
- A glob pattern, or
- An object matcher, or
- A list of glob patterns, predefined group matchers, and object matchers.
Predefined group matchers are strings in CONSTANT_CASE
prefixed and suffixed by :
.
The sorter provides several predefined group matchers:
:ALIAS:
: sources starting with#
,@/
,~
, or%
.:BUN:
: sources starting with the protocolbun:
or that correspond to a built-in Bun module such asbun
.:NODE:
: sources starting with the protocolnode:
or that correspond to a built-in Node.js module such asfs
orpath
.:PACKAGE:
: scoped and bare packages.:PACKAGE_WITH_PROTOCOL:
: scoped and bare packages with a protocol.:PATH:
: absolute and relative paths.:URL:
: sources starting withhttps://
andhttp://
.
Let’s take an example.
In the default configuration, Node.js modules without the node:
protocol are separated from those with a protocol.
To groups them together, you can use the predefined group :NODE:
.
Given the following configuration…
{ "assist": { "actions": { "source": { "organizeImports": { "level": "on", "options": { "groups": [ ":URL:", ":NODE:" ] } } } } }}
…and the following code…
import sibling from "./file.js";import internal from "#alias";import fs from "fs";import { test } from "node:test";import path from "node:path";import parent from "../parent.js";import scopedLibUsingJsr from "jsr:@scoped/lib";import data from "https://example.org";import lib from "lib";import scopedLib from "@scoped/lib";
…we end up with the following sorted result where the imports of node:path
and the fs
Node.js module are grouped together:
import data from "https://example.org";import fs from "fs";import path from "node:path";import { test } from "node:test";import scopedLibUsingJsr from "jsr:@scoped/lib";import scopedLib from "@scoped/lib";import lib from "lib";import internal from "#alias";import parent from "../parent.js";import sibling from "./file.js";
Note that all imports that don’t match a group matcher are always placed at the end.
Group matchers can also be glob patterns and list of glob patterns.
Glob patterns select imports and exports with a source that matches the pattern.
In the following example, we create two groups: one that gathers imports/exports with a source starting with @my/lib
except @my/lib/speciaal
and the other that gathers imports/exports starting with @/
.
{ "options": { "groups": [ ["@my/lib", "@my/lib/**", "!@my/lib/special", "!@my/lib/special/**"], "@/**" ] }}
By applying this configuration to the following code…
import lib from "@my/lib";import aliased from "@/alias";import path from "@my/lib/special";import test from "@my/lib/path";
…we obtain the following sorted result.
Imports with the sources @my/lib
and @my/lib/path
form the first group.
They match the glob patterns @my/lib
and @my/lib/**
respectively.
The import with the source @my/lib/special
is not placed in this first group because it is rejected by the exception !@my/lib/special
.
The import with the source @/alias
is placed in a second group because it matches the glob pattern @/**
.
Finally, other imports are placed at the end.
import lib from "@my/lib";import test from "@my/lib/path";import aliased from "@/alias";import path from "@my/lib/special";
Note that @my/lib
matches @my/lib
but not @my/lib/**
.
Conversely, @my/lib/subpath
matches @my/lib/**
, but not @my/lib
.
So, you have to specify both glob patterns if you want to accept all imports/exports that start with @my/lib
.
The prefix !
indicates an exception.
You can create exceptions of exceptions by following an exception by a regular glob pattern.
For example ["@my/lib", "@my/lib/**", "!@my/lib/special", "!@my/lib/special/**", "@my/lib/special/*/accepted/**"]
allows you to accepts all sources matching @my/lib/special/*/accepted/**
.
Note that the predefined groups can also be negated. !:NODE:
matches all sources that don’t match :NODE:
.
For more details on the supported glob patterns, see the dedicated section.
Finally, group matchers can be object matchers. An object matcher allows to match type-only imports and exports.
Given the following configuration:
{ "options": { "groups": [ { "type": false, "source": ["@my/lib", "@my/lib/**"] }, ["@my/lib", "@my/lib/**"] ] }}
The following code:
import type { T } from "@my/lib";import { V } from "@my/lib";
is sorted as follows:
import { V } from "@my/lib";import type { T } from "@my/lib";
The object matcher { "type": false, "source": ["@my/lib", "@my/lib/**"] }
match against imports and exports without the type
keyword with a source that matches one of the glob pattern of the list ["@my/lib", "@my/lib/**"]
.
The sorter allows the separation of two groups with a blank line using the predefined string :BLANK_LINE:
.
Given the following configuration…
{ "options": { "groups": [ [":BUN:", ":NODE:"], ":BLANK_LINE:", ["@my/lib", "@my/lib/**", "!@my/lib/special", "!@my/lib/special/**"], "@/**" ] }}
…the following code…
import test from "bun:test";import path from "node:path";import lib from "@my/lib";import libPath from "@my/lib/path";import libSpecial from "@my/lib/special";import aliased from "@/alias";
…is sorted as:
import path from "node:path";
import lib from "@my/lib";import test from "@my/lib/path";import aliased from "@/alias";import path from "@my/lib/special";
Groups are matched in order.
This means that one group matcher can shadow succeeding groups.
For example, in the following configuration, the group matcher :URL:
is never matched because all imports and exports match the first matcher **
.
{ "options": { "groups": [ "**", ":URL:" ] }}
Comment handling
Section titled “Comment handling”When sorting imports and exports, attached comments are moved with their import or export, and detached comments (comments followed by a blank line) are left where they are.
However, there is an exception to the rule. If a comment appears at the top of the file, it is considered as detached even if no blank line follows. This ensures that copyright notice and file header comments stay at the top of the file.
For example, the following code…
// Copyright notice and file header commentimport F from "f";// Attached comment for `e`import E from "e";// Attached comment for `d`import D from "d";// Detached comment (new chunk)
// Attached comment for `b`import B from "b";// Attached comment for `a`import A from "a";
…is sorted as follows. A blank line is automatically added after the header comment to ensure that the attached comment doesn’t merge with the header comment.
// Copyright notice and file header comment
// Attached comment for `d`import D from "d";// Attached comment for `e`import E from "e";import F from "f";
// Detached comment (new chunk)
// Attached comment for `a`import A from "a";// Attached comment for `b`import B from "b";
Import and export merging
Section titled “Import and export merging”The organizer also merges imports and exports that can be merged.
For example, the following code:
import type { T1 } from "package";import type { T2 } from "package";import * as ns from "package";import D1 from "package";import D2 from "package";import { A } from "package";import { B } from "package";
is merged as follows:
import type { T1, T2 } from "package";import D1, * as ns from "package";import D2, { A, B } from "package";
Named imports, named exports and attributes sorting
Section titled “Named imports, named exports and attributes sorting”The sorter also sorts named imports, named exports, as well as attributes. It uses a natural sort order for comparing numbers.
The following code…
import { a, b, A, B, c10, c9 } from "a";
export { a, b, A, B, c10, c9 } from "a";
import special from "special" with { "type": "ty", "metadata": "data" };
…is sorted as follows:
import { A, a, B, b, c9, c10 } from "a";
export { A, a, B, b, c9, c10 } from "a";
import special from "special" with { "metadata": "data", "type": "ty" };
Supported glob patterns
Section titled “Supported glob patterns”You need to understand the structure of a source to understand which source matches a glob.
A source is divided in source segments.
Every source segment is delimited by the separator /
or the start/end of the source.
For instance src/file.js
consists of two source segments: src
and file.js
.
- star
*
that matches zero or more characters inside a source segment
file.js
matches *.js
.
Conversely, src/file.js
doesn’t match *.js
- globstar
**
that matches zero or more source segments**
must be enclosed by separators/
or the start/end of the glob. For example,**a
is not a valid glob. Also,**
must not be followed by another globstar. For example,**/**
is not a valid glob.
file.js
and src/file.js
match **
and **/*.js
Conversely, README.txt
doesn’t match **/*.js
because the source ends with .txt
.
- Use
\*
to escape*
\*
matches the literal *
character in a source.
-
?
,[
,]
,{
, and}
must be escaped using\
. These characters are reserved for possible future use. -
Use
!
as first character to negate a glob
file.js
matches !*.test.js
.
src/file.js
matches !*.js
because the source contains several segments.
Common configurations
Section titled “Common configurations”This section provides some examples of common configurations.
Placing import type
and export type
at the start of the chunks
Section titled “Placing import type and export type at the start of the chunks”{ "options": { "groups": [ { "type": true } ] }}
Note that you can want to use the lint rule useImportType
and its style
to enforce the use of import type
instead of import { type }
.
Placing import type
and export type
at the end of the chunks
Section titled “Placing import type and export type at the end of the chunks”{ "options": { "groups": [ { "type": false } ] }}
How to configure
Section titled “How to configure”{ "assist": { "actions": { "source": { "organizeImports": "on" } } }}