OrganizeImports
is a Scalafix semantic rule that helps you to organize import statements.
Please refer to the Scalafix documentation for how to install Scalafix and invoking it in your build.
To try this rule in SBT console without updating your SBT build:
sbt> scalafix dependency:OrganizeImports@com.github.liancheng:organize-imports:0.3.1-RC3
To include this rule in your SBT build:
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC3"
|
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. Therefore, when two patches generated by different rules conflict with each other, Scalafix is not able to reconcile the conflicts, and may produce broken code. This is exactly what happens when By default, |
OrganizeImports {
coalesceToWildcardImportThreshold = 2147483647 # Int.MaxValue
expandRelative = false
groupExplicitlyImportedImplicitsSeparately = false
groupedImports = Explode
groups = ["re:javax?\\.", "scala.", "*"]
importSelectorsOrder = Ascii
importsOrder = Ascii
removeUnused = true
}
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 importes 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 |
Int.MaxValue
- Rationale
-
Setting the default value to
Int.MaxValue
essentially disables this feature, since it can may cause correctness issues.
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 complianing:
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. |
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
}
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
Enum: Explode | Merge | Keep
Explode
-
Explode grouped imports into separate import statements.
Merge
-
Merge imports sharing the same prefix into a single grouped import statement.
Keep
-
Leave grouped imports and imports sharing the same prefix untouched.
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
After:
import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder}
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 a regular expression pattern that matches bothjava
andjavax
packages. - 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."]
- 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.", "*"]
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 (yet), 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
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
Remove unused imports.
🚧
|
As mentioned in the Getting started section, the |
Configuration:
OrganizeImports {
groups = ["javax?\\.", "scala.", "*"]
removeUnused = true
}
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")
}