❗
|
This rule is included in Scalafix as a built-in rule since Scalafix v0.11.0. Please refer to the Scalafix GitHub repository for bug reports and feature requests. |
OrganizeImports
is a CI-friendly Scalafix semantic rule that helps you organize Scala import statements.
Metals, the Scala language server, also uses OrganizeImports
to power its "organize imports" code action starting from version v0.9.5.
Please refer to the Scalafix documentation for how to install Scalafix and invoking it in your sbt build.
To try this rule in the sbt console without adding this rule to your sbt build:
sbt> scalafix dependency:[email protected]:organize-imports:0.6.0
To include this rule in your sbt build:
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.6.0"
You can also include this rule in your Mill build using mill-scalafix:
def scalafixIvyDeps = Agg(ivy"com.github.liancheng::organize-imports:0.6.0")
OrganizeImports
allows you to specify a preset style via the preset
option. To make it easier to add OrganizeImports
into existing Scala projects built using the IntelliJ Scala plugin, OrganizeImports
provides a preset style compatible with the default configuration of the IntelliJ Scala import optimizer. Please check the INTELLIJ_2020_3
preset style for more details.
The OrganizeImports
rule respects source-formatting tools like Scalafmt. If an import statement is already organized according to the configuration, its original source level format is preserved. Therefore, in an sbt project, if you run the following command sequence:
sbt> scalafixAll
...
sbt> scalafmtAll
...
sbt> scalafixAll --check
...
Assuming that the first two commands run successfully, the last scalafixAll --check
command should not fail even if some import statements are reformatted by the scalafmtAll
command.
However, you should make sure that the source-formatting tools you use do not rewrite import statements in ways that conflict with OrganizeImports
. For example, when using Scalafmt together with OrganizeImports
, the ExpandImportSelectors
, SortImports
, and AsciiSortImports
rewriting rules should not be used.
Available since v0.6.0.
Running the rule on source files compiled with Scala 3 is still experimental.
Known limitations:
-
You must use Scalafix 0.9.28 or later
-
The
removeUnused
option must be explicitly set tofalse
- the rule currently doesn’t remove unused imports as it’s currently not supported by the compiler. -
Usage of deprecated package objects may result in incorrect imports
-
The groupExplicitlyImportedImplicitsSeparately option has no effect
OrganizeImports {
blankLines = Auto
coalesceToWildcardImportThreshold = null
expandRelative = false
groupExplicitlyImportedImplicitsSeparately = false
groupedImports = Explode
groups = [
"*"
"re:(javax?|scala)\\."
]
importSelectorsOrder = Ascii
importsOrder = Ascii
preset = DEFAULT
removeUnused = true
}
|
Please do NOT use the Scalafix built-in Scalafix rewrites source files by applying patches generated by invoked rules. Each rule generates a patch based on the original text of the source files. When two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. It is very likely to happen when By default, |
Available since v0.5.0-alpha.1.
Configures whether blank lines between adjacent import groups are automatically or manually inserted. This option is used together with the ---
blank line markers.
Enum: Auto | Manual
- Auto
-
A blank line is automatically inserted between adjacent import groups. All blank line markers (
---
) configured in thegroups
option are ignored. - Manual
-
A blank line is inserted at all the positions where blank line markers appear in the
groups
option.
The following two configurations are equivalent:
OrganizeImports {
blankLines = Auto
groups = [
"re:javax?\\."
"scala."
"*"
]
}
OrganizeImports {
blankLines = Manual
groups = [
"re:javax?\\."
"---"
"scala."
"---"
"*"
]
}
Auto
-
Configuration:
OrganizeImports { blankLines = Auto groups = [ "re:javax?\\." "scala." "*" ] }
Before:
import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import sun.misc.BASE64Encoder
Manual
-
Configuration:
OrganizeImports { blankLines = Manual groups = [ "re:javax?\\." "scala." "---" "*" ] }
Before:
import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import sun.misc.BASE64Encoder
When the number of imported names exceeds a certain threshold, coalesce them into a wildcard import. Renames and unimports are left untouched.
🚧
|
Having this feature in Here is an example to illustrate the risk. The following snippet compiles successfully: import scala.collection.immutable._
import scala.collection.mutable.{ArrayBuffer, Map, Set}
object Example {
val m: Map[Int, Int] = ???
} The type of However, if we coalesce the grouped imports in the second import statement into a wildcard, there will be a compilation error: import scala.collection.immutable._
import scala.collection.mutable._
object Example {
val m: Map[Int, Int] = ???
} This is because the type of |
Configuration:
OrganizeImports {
groupedImports = Keep
coalesceToWildcardImportThreshold = 3
}
Before:
import scala.collection.immutable.{Seq, Map, Vector, Set}
import scala.collection.immutable.{Seq, Map, Vector}
import scala.collection.immutable.{Seq, Map, Vector => Vec, Set, Stream}
import scala.collection.immutable.{Seq, Map, Vector => _, Set, Stream}
After:
import scala.collection.immutable._
import scala.collection.immutable.{Map, Seq, Vector}
import scala.collection.immutable.{Vector => Vec, _}
import scala.collection.immutable.{Vector => _, _}
Expand relative imports into fully-qualified one.
🚧
|
Expanding relative imports may introduce new unused imports. For instance, relative imports in the following snippet import scala.util
import util.control
import control.NonFatal are expanded into import scala.util
import scala.util.control
import scala.util.control.NonFatal If neither Unfortunately, these newly introduced unused imports cannot be removed by setting |
Configuration:
OrganizeImports {
expandRelative = true
groups = ["re:javax?\\.", "scala.", "*"]
}
Before:
import scala.util
import util.control
import control.NonFatal
import scala.collection.JavaConverters._
import java.time.Clock
import sun.misc.BASE64Encoder
import javax.annotation.Generated
import scala.concurrent.ExecutionContext
After:
import java.time.Clock
import javax.annotation.Generated
import scala.collection.JavaConverters._
import scala.concurrent.ExecutionContext
import scala.util
import scala.util.control
import scala.util.control.NonFatal
import sun.misc.BASE64Encoder
This option provides a workaround to a subtle and rarely seen correctness issue related to explicitly imported implicit names.
The following snippet helps illustrate the problem:
package a
import c._
import b.i
object b { implicit def i: Int = 1 }
object c { implicit def i: Int = 2 }
object Imports {
def f()(implicit i: Int) = println(1)
def main() = f()
}
The above snippet compiles successfully and outputs 1
, because the explicitly imported implicit value b.i
overrides c.i
, which is made available via a wildcard import. However, if we reorder the two imports into:
import b.i
import c._
The Scala compiler starts complaining:
error: could not find implicit value for parameter i: Int def main() = f() ^
This behavior could be due to a Scala compiler bug since the Scala language specification requires that explicitly imported names should have higher precedence than names made available via a wildcard.
Unfortunately, Scalafix is not able to surgically identify conflicting implicit values behind a wildcard import. In order to guarantee correctness in all cases, when the groupExplicitlyImportedImplicitsSeparately
option is set to true
, all explicitly imported implicit names are moved into the trailing order-preserving import group together with relative imports, if any (see the trailing order-preserving import group section for more details).
🚧
|
In general, order-sensitive imports are fragile, and can easily be broken by either human collaborators or tools (e.g., the IntelliJ IDEA Scala import optimizer does not handle this case correctly). They should be eliminated whenever possible. This option is mostly useful when you are dealing with a large trunk of legacy codebase, and you want to minimize manual intervention and guarantee correctness in all cases. |
❗
|
The |
false
- Rationale
-
This option defaults to
false
due to the following reasons:-
Although setting it to
true
avoids the aforementioned correctness issue, the result is unintuitive and confusing for many users since it looks like thegroups
option is not respected.E.g., why my
scala.concurrent.ExecutionContext.Implicits.global
import is moved to a separate group even if I have ascala.
group defined in thegroups
option? -
The concerned correctness issue is rarely seen in real life. When it really happens, it is usually a sign of bad coding style, and you may want to tweak your imports to eliminate the root cause.
-
Configuration:
OrganizeImports {
groups = ["scala.", "*"]
groupExplicitlyImportedImplicitsSeparately = true // not supported in Scala 3
}
Before:
import org.apache.spark.SparkContext
import org.apache.spark.RDD
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Buffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.sys.process.stringToProcess
After:
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Buffer
import org.apache.spark.RDD
import org.apache.spark.SparkContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.sys.process.stringToProcess
Configure how to handle grouped imports.
Enum: Explode | Merge | AggressiveMerge | Keep
Explode
-
Explode grouped imports into separate import statements.
Merge
-
Merge imports sharing the same prefix into a single grouped import statement.
💡You may want to check the
AggressiveMerge
option for more concise results despite a relatively low risk of introducing compilation errors.❗OrganizeImports
does not support cases where one name is renamed to multiple aliases within the same source file whengroupedImports
is set toMerge
. (The IntelliJ IDEA Scala import optimizer does not support this either.)Scala allows a name to be renamed to multiple aliases within a single source file, which makes merging import statements tricky. For example:
import java.lang.{Double => JDouble} import java.lang.{Double => JavaDouble} import java.lang.Integer
The above three imports can be merged into:
import java.lang.{Double => JDouble} import java.lang.{Double => JavaDouble, Integer}
but not:
import java.lang.{Double => JDouble, Double => JavaDouble, Integer}
because Scala disallow a name (in this case,
Double
) to appear in one import multiple times.Here’s a more complicated example:
import p.{A => A1} import p.{A => A2} import p.{A => A3} import p.{B => B1} import p.{B => B2} import p.{C => C1} import p.{C => C2} import p.{C => C3} import p.{C => C4}
While merging these imports, we may want to "bin-pack" them to minimize the number of the result import statements:
import p.{A => A1, B => B1, C => C1} import p.{A => A2, B => B2, C => C2} import p.{A => A3, C3 => C3} import p.{C => C4}
However, in reality, renaming aliasing a name multiple times in the same source file is rarely a practical need. Therefore,
OrganizeImports
does not support this whengroupedImports
is set toMerge
to avoid the extra complexity.
AggressiveMerge
-
Similar to
Merge
, but merges imports more aggressively and produces more concise results, despite a relatively low risk of introducing compilation errors.The
OrganizeImports
rule tries hard to guarantee correctness in all cases. This forces it to be more conservative when merging imports, and may sometimes produce suboptimal output. Here is a concrete example about correctness:import scala.collection.immutable._ import scala.collection.mutable.Map import scala.collection.mutable._ object Example { val m: Map[Int, Int] = ??? }
At a first glance, it seems feasible to simply drop the second import since
mutable._
already coversmutble.Map
. However, similar to the example illustrated in the section about thecoalesceToWildcardImportThreshold
option, the type ofExample.m
above ismutable.Map
, because the mutableMap
explicitly imported in the second import takes higher precedence than the immutableMap
imported via wildcard in the first import. If we merge the last two imports naively, we’ll get:import scala.collection.immutable._ import scala.collection.mutable._
This triggers in a compilation error, because both
immutable.Map
andmutable.Map
are now imported via wildcards with the same precedence. This makes the type ofExample.m
ambiguous. The correct result should be:import scala.collection.immutable._ import scala.collection.mutable.{Map, _}
On the other hand, the case discussed above is rarely seen in practice. A more commonly seen case is something like:
import scala.collection.mutable.Map import scala.collection.mutable._
Instead of being conservative and produce a suboptimal output like:
import scala.collection.mutable.{Map, _}
setting
groupedImports
toAggressiveMerge
producesimport scala.collection.mutable._
Keep
-
Leave grouped imports and imports sharing the same prefix untouched.
Explode
- Rationale
-
Despite making the import section lengthier, exploding grouped imports into separate import statements is made the default behavior because it is more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes.
Explode
-
Configuration:
OrganizeImports.groupedImports = Explode
Before:
import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder}
After:
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder
Merge
-
Configuration:
OrganizeImports.groupedImports = Merge
Before:
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder import scala.collection.immutable.Set import scala.collection.immutable._
After:
import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} import scala.collection.immutable.{Set, _}
AggressiveMerge
-
Configuration:
OrganizeImports.groupedImports = AggressiveMerge
Before:
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.Buffer import scala.collection.mutable.StringBuilder import scala.collection.immutable.Set import scala.collection.immutable._
After:
import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} import scala.collection.immutable._
Defines import groups by prefix patterns. Only global imports are processed.
All the imports matching the same prefix pattern are gathered into the same group and sorted by the order defined by the importsOrder
option.
🚧
|
Comments living between imports being processed will be removed. |
💡
|
OrganizeImports.groups = [
"re:javax?\\."
"scala."
"scala.meta."
"*"
] |
❗
|
No matter how the
This special import group is necessary because the above two kinds of imports are order sensitive:
|
An ordered list of import prefix pattern strings. A prefix pattern can be one of the following:
- A plain-text pattern
-
For instance,
"scala."
is a plain-text pattern that matches imports referring thescala
package. Please note that the trailing dot is necessary, otherwise you may havescalafix
andscala
imports in the same group, which is not what you want in most cases. - A regular expression pattern
-
A regular expression pattern starts with
re:
. For instance,"re:javax?\\."
is such a pattern that matches both thejava
and thejavax
packages. Please refer to thejava.util.regex.Pattern
Javadoc page for the regular expression syntax. Note that special characters like backslashes must be escaped. - The wildcard pattern
-
The wildcard pattern,
"*"
, defines the wildcard group, which matches all fully-qualified imports not belonging to any other groups. It can be omitted when it’s the last group. So the following two configurations are equivalent:OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] OrganizeImports.groups = ["re:javax?\\.", "scala."]
- A blank line marker
-
Available since v0.5.0-alpha.1.
A blank line marker,
"---"
, defines a blank line between two adjacent import groups whenblankLines
is set toManual
. It is ignored whenblankLines
isAuto
. Leading and trailing blank line markers are always ignored. Multiple consecutive blank line markers are treated as a single one. So the following three configurations are all equivalent:OrganizeImports { blankLines = Manual groups = [ "---" "re:javax?\\." "---" "scala." "---" "---" "*" "---" ] } OrganizeImports { blankLines = Manual groups = [ "re:javax?\\." "---" "scala." "---" "*" ] } OrganizeImports { blankLines = Auto groups = [ "re:javax?\\." "scala." "*" ] }
[
"*"
"re:(javax?|scala)\\."
]
- Rationale
-
This aligns with the default configuration of the IntelliJ Scala plugin version 2020.3.
- Fully-qualified imports only
-
Configuration:
OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"]
Before:
import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import sun.misc.BASE64Encoder
- With relative imports
-
Configuration:
OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"]
Before:
import scala.util import util.control import control.NonFatal import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import scala.util import sun.misc.BASE64Encoder import util.control import control.NonFatal
- With relative imports and an explicitly imported implicit name
-
Configuration:
OrganizeImports { groups = ["re:javax?\\.", "scala.", "*"] groupExplicitlyImportedImplicitsSeparately = true }
Before:
import scala.util import util.control import control.NonFatal import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext.Implicits.global
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.util import sun.misc.BASE64Encoder import util.control import control.NonFatal import scala.concurrent.ExecutionContext.Implicits.global
- Regular expression
-
Defining import groups using regular expressions can be quite flexible. For instance, the
scala.meta
package is not part of the Scala standard library, but the default groups defined in theOrganizeImports.groups
option move imports from this package into thescala.
group. The following example illustrates how to move them into the wildcard group using regular expression.Configuration:
OrganizeImports.groups = [ "re:javax?\\." "re:scala.(?!meta\\.)" "*" ]
Before:
import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import scala.meta.Tree import javax.annotation.Generated import scala.concurrent.ExecutionContext import scala.meta.Import import scala.meta.Pkg
After:
import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import scala.meta.Import import scala.meta.Pkg import scala.meta.Tree import sun.misc.BASE64Encoder
- With manually configured blank lines
-
Configuration:
OrganizeImports { blankLines = Manual groups = [ "*" "---" "re:javax?\\." "scala." ] }
Before:
import scala.collection.JavaConverters._ import java.time.Clock import sun.misc.BASE64Encoder import javax.annotation.Generated import scala.concurrent.ExecutionContext
After:
import sun.misc.BASE64Encoder import java.time.Clock import javax.annotation.Generated import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext
Specifies the order of grouped import selectors within a single import expression.
Enum: Ascii | SymbolsFirst | Keep
Ascii
-
Sort import selectors by ASCII codes, equivalent to the
AsciiSortImports
rewriting rule in Scalafmt. SymbolsFirst
-
Sort import selectors by the groups: symbols, lower-case, upper-case, equivalent to the
SortImports
rewriting rule in Scalafmt. Keep
-
Keep the original order.
Ascii
-
Configuration:
OrganizeImports { groupedImports = Keep importSelectorsOrder = Ascii }
Before:
import foo.{~>, `symbol`, bar, Random}
After:
import foo.{Random, `symbol`, bar, ~>}
SymbolsFirst
-
Configuration:
OrganizeImports { groupedImports = Keep importSelectorsOrder = SymbolsFirst }
Before:
import foo.{Random, `symbol`, bar, ~>}
After:
import foo.{~>, `symbol`, bar, Random}
Specifies the order of import statements within import groups defined by the OrganizeImports.groups
option.
Enum: Ascii | SymbolsFirst | Keep
Ascii
-
Sort import statements by ASCII codes. This is the default sorting order that the IntelliJ IDEA Scala import optimizer picks ("lexicographically" option).
SymbolsFirst
-
Put wildcard imports and grouped imports with braces first, otherwise same as
Ascii
. This replicates IntelliJ IDEA Scala’s "scalastyle consistent" option. Keep
-
Keep the original order.
Ascii
-
Configuration:
OrganizeImports { groupedImports = Keep importsOrder = Ascii }
Before:
import scala.concurrent._ import scala.concurrent.{Future, Promise} import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration
After:
import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent._ import scala.concurrent.duration import scala.concurrent.{Promise, Future}
SymbolsFirst
-
Configuration:
OrganizeImports { groupedImports = Keep importsOrder = SymbolsFirst }
Before:
import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent._ import scala.concurrent.duration import scala.concurrent.{Promise, Future}
After:
import scala.concurrent._ import scala.concurrent.{Future, Promise} import scala.concurrent.ExecutionContext.Implicits._ import scala.concurrent.duration
Available since v0.5.0.
Specify a preset style.
Enum: DEFAULT | INTELLIJ_2020_3
DEFAULT
-
An opinionated style recommended for new projects. The
OrganizeImports
rule tries its best to ensure correctness in all cases when possible. This default style aligns with this principal. In addition, by settinggroupedImports
toExplode
, this style is also more friendly to version control and less likely to create annoying merge conflicts caused by trivial import changes.OrganizeImports { blankLines = Auto coalesceToWildcardImportThreshold = null expandRelative = false groupExplicitlyImportedImplicitsSeparately = false groupedImports = Explode groups = [ "*" "re:(javax?|scala)\\." ] importSelectorsOrder = Ascii importsOrder = Ascii preset = DEFAULT removeUnused = true }
INTELLIJ_2020_3
-
A style that is compatible with the default configuration of the IntelliJ Scala 2020.3 import optimizer. It is mostly useful for adding
OrganizeImports
to existing projects developed using the IntelliJ Scala plugin. However, the configuration of this style may introduce subtle correctness issues (so does the default configuration of the IntelliJ Scala plugin). Please see thecoalesceToWildcardImportThreshold
option for more details.OrganizeImports { blankLines = Auto coalesceToWildcardImportThreshold = 5 expandRelative = false groupExplicitlyImportedImplicitsSeparately = false groupedImports = Merge groups = [ "*" "re:(javax?|scala)\\." ] importSelectorsOrder = Ascii importsOrder = Ascii preset = INTELLIJ_2020_3 removeUnused = true }
Remove unused imports.
🚧
|
As mentioned in a previous section, the |
❗
|
The |
Configuration:
OrganizeImports {
groups = ["javax?\\.", "scala.", "*"]
removeUnused = true // not supported in Scala 3
}
Before:
import scala.collection.mutable.{Buffer, ArrayBuffer}
import java.time.Clock
import java.lang.{Long => JLong, Double => JDouble}
object RemoveUnused {
val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int]
val long: JLong = JLong.parseLong("0")
}
After:
import java.lang.{Long => JLong}
import scala.collection.mutable.ArrayBuffer
object RemoveUnused {
val buffer: ArrayBuffer[Int] = ArrayBuffer.empty[Int]
val long: JLong = JLong.parseLong("0")
}