diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b3f5d92e..2561dc950 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,7 @@ scalafix-tests | ├── integration # Integration test suites | +├── shared # Code that is shared between input and unit projects ├── input # Source files to be analyzed and fixed by rules ├── output # Expected output from running rewrite rules └── expect # Verify expectations defined in input/output using testkit diff --git a/build.sbt b/build.sbt index 35dc5ca49..e2e27af5f 100644 --- a/build.sbt +++ b/build.sbt @@ -174,6 +174,16 @@ lazy val testkit = projectMatrix .jvmPlatform(buildScalaVersions) .dependsOn(cli) +lazy val shared = projectMatrix + .in(file("scalafix-tests/shared")) + .settings( + noPublishAndNoMima, + coverageEnabled := false + ) + .defaultAxes(VirtualAxis.jvm) + .jvmPlatformFull(buildWithTargetVersions.map(_._2)) + .disablePlugins(ScalafixPlugin) + lazy val input = projectMatrix .in(file("scalafix-tests/input")) .settings( @@ -189,6 +199,7 @@ lazy val input = projectMatrix .defaultAxes(VirtualAxis.jvm) .jvmPlatformFull(buildWithTargetVersions.map(_._2)) .disablePlugins(ScalafixPlugin) + .dependsOn(shared) lazy val output = projectMatrix .in(file("scalafix-tests/output")) @@ -201,6 +212,7 @@ lazy val output = projectMatrix .defaultAxes(VirtualAxis.jvm) .jvmPlatform(buildScalaVersions) .disablePlugins(ScalafixPlugin) + .dependsOn(shared) lazy val unit = projectMatrix .in(file("scalafix-tests/unit")) diff --git a/docs/rules/OrganizeImports.md b/docs/rules/OrganizeImports.md new file mode 100644 index 000000000..05844bf07 --- /dev/null +++ b/docs/rules/OrganizeImports.md @@ -0,0 +1,1330 @@ +--- +id: OrganizeImports +title: OrganizeImports +--- + +OrganizeImports was originally a [community rule](https://github.com/liancheng/scalafix-organize-imports), +created and maintained by [Cheng Lian](https://github.com/liancheng). +Considering its [popularity, maturity and the lack of bandwidth from the +author](https://github.com/liancheng/scalafix-organize-imports/discussions/215), +it is now a built-in rule, since Scalafix 0.11.0. + +Getting started +--------------- + +### For IntelliJ Scala plugin users + +`OrganizeImports` allows you to specify a preset style via the [`preset` +option](#preset). 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`](#intellij-2020-3) preset style for more details. + +### Source formatting tools + +The `OrganizeImports` rule respects source-formatting tools like +[Scalafmt](https://scalameta.org/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. + +### Scala 3 + +Known limitations: + +1. The [`removeUnused`](OrganizeImports.md#removeunused) option must be + explicitly set to `false` - the rule currently doesn’t remove unused + imports as it is currently not supported by the compiler. + +2. Usage of [deprecated package + objects](http://dotty.epfl.ch/docs/reference/dropped-features/package-objects.html) + may result in incorrect imports. + +3. The + [`groupExplicitlyImportedImplicitsSeparately`](OrganizeImports.md#groupexplicitlyimportedimplicitsseparately) + option has no effect. + +Configuration +------------- + +> Please do NOT use the [`RemoveUnused.imports`](RemoveUnused.md) together with +> `OrganizeImports` to remove unused imports. You may end up with broken code! +> It is still safe to use `RemoveUnused` to remove unused private members or +> local definitions, though. +> +> 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 `RemoveUnused` +> and `OrganizeImports` are used together, since both rules rewrite import +> statements. +> +> By default, `OrganizeImports` already removes unused imports for you +> (see the [`removeUnused`](OrganizeImports.md#removeunused) option). It locates unused +> imports via compilation diagnostics, which is exactly how `RemoveUnused` +> does it. This mechanism works well in most cases, unless there are new +> unused imports generated while organizing imports, which is possible +> when the [`expandRelative`](OrganizeImports.md#expandrelative) option is set to true. For +> now, the only reliable workaround for this edge case is to run Scalafix +> with `OrganizeImports` twice. + +```scala mdoc:passthrough +import scalafix.internal.rule._ +import scalafix.website._ +``` + +```scala mdoc:passthrough +println( + defaults( + "OrganizeImports", + flat(OrganizeImportsConfig.default) + ) +) +``` + +`blankLines` +------------ + +Configures whether blank lines between adjacent import groups are +automatically or manually inserted. This option is used together with +the [`---` blank line markers](OrganizeImports.md#a-blank-line-marker). + +### Value type + +Enum: `Auto | Manual` + +#### `Auto` +A blank line is automatically inserted between adjacent import groups. +All blank line markers (`---`) configured in the [`groups` +option](#groups) are ignored. + +#### `Manual` +A blank line is inserted at all the positions where blank line markers +appear in the [`groups` option](#groups). + +The following two configurations are equivalent: + +```conf +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} +``` + +```conf +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "---" + "scala." + "---" + "*" + ] +} +``` + +### Default value + +`Auto` + +### Examples + +#### `Auto` + +```conf +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} +``` + +Before: + +```scala +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +``` + +After: + +```scala +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +``` + +#### `Manual` + +```conf +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "scala." + "---" + "*" + ] +} +``` + +Before: + +```scala +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +``` + +After: + +```scala +import java.time.Clock +import javax.annotation.Generated +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +``` + +`coalesceToWildcardImportThreshold` +----------------------------------- + +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 `OrganizeImports` is mostly for feature parity +> with the IntelliJ IDEA Scala import optimizer, but coalescing grouped +> import selectors into a wildcard import may introduce *compilation +> errors*! +> +> Here is an example to illustrate the risk. The following snippet +> compiles successfully: +> +> ```scala +> import scala.collection.immutable._ +> import scala.collection.mutable.{ArrayBuffer, Map, Set} +> +> object Example { +> val m: Map[Int, Int] = ??? +> } +> ``` +> +> The type of `Example.m` above is not ambiguous because the mutable `Map` +> explicitly imported in the second import takes higher precedence than +> the immutable `Map` imported via wildcard in the first import. +> +> However, if we coalesce the grouped imports in the second import +> statement into a wildcard, there will be a compilation error: +> +> ```scala +> import scala.collection.immutable._ +> import scala.collection.mutable._ +> +> object Example { +> val m: Map[Int, Int] = ??? +> } +> ``` +> +> This is because the type of `Example.m` becomes ambiguous now since both +> the mutable and immutable `Map` are imported via a wildcard and have the +> same precedence. + +### Value type + +Integer. Not setting it or setting it to `null` disables this feature. + +### Default value + +`null` + +### Examples + +```conf +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 3 +} +``` + +Before: + +```scala +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: + +```scala +import scala.collection.immutable._ +import scala.collection.immutable.{Map, Seq, Vector} +import scala.collection.immutable.{Vector => Vec, _} +import scala.collection.immutable.{Vector => _, _} +``` + +`expandRelative` +---------------- + +Expand relative imports into fully-qualified one. + +> Expanding relative imports may introduce new unused imports. For +> instance, relative imports in the following snippet +> +> ```scala +> import scala.util +> import util.control +> import control.NonFatal +> ``` +> +> are expanded into +> +> ```scala +> import scala.util +> import scala.util.control +> import scala.util.control.NonFatal +> ``` +> +> If neither `scala.util` nor `scala.util.control` is referenced anywhere +> after the expansion, they become unused imports. +> +> Unfortunately, these newly introduced unused imports cannot be removed +> by setting `removeUnused` to `true`. Please refer to the +> [`removeUnused`](OrganizeImports.md#removeunused) option for more details. + +### Value type + +Boolean + +### Default value + +`false` + +### Examples + +```conf +OrganizeImports { + expandRelative = true + groups = ["re:javax?\\.", "scala.", "*"] +} +``` + +Before: + +```scala +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: + +```scala +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 +``` + +`groupExplicitlyImportedImplicitsSeparately` +-------------------------------------------- + +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: + +```scala +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: + +```scala +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](https://scala-lang.org/files/archive/spec/2.13/02-identifiers-names-and-scopes.html) +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](OrganizeImports.md#groups) 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 `groupExplicitlyImportedImplicitsSeparately` option has currently no +> effect on source files compiled with Scala 3, as the [compiler does not +> expose full signature +> information](https://github.com/lampepfl/dotty/issues/12766), preventing +> the rule to identify imported implicits. + +### Value type + +Boolean + +### Default value + +`false` + +Rationale: + +1. Although setting it to `true` avoids the aforementioned correctness + issue, the result is unintuitive and confusing for many users since + it looks like the `groups` option is not respected. + + E.g., why my `scala.concurrent.ExecutionContext.Implicits.global` + import is moved to a separate group even if I have a `scala.` group + defined in the `groups` option? + +2. 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. + +### Examples + +```conf +OrganizeImports { + groups = ["scala.", "*"] + groupExplicitlyImportedImplicitsSeparately = true // not supported in Scala 3 +} +``` + +Before: + +```scala +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: +```scala +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 +``` + +`groupedImports` +---------------- + +Configure how to handle grouped imports. + +### Value type + +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`](OrganizeImports.md#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 when `groupedImports` is +> set to `Merge`. (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: +> +> ```scala +> import java.lang.{Double => JDouble} +> import java.lang.{Double => JavaDouble} +> import java.lang.Integer +> ``` +> +> The above three imports can be merged into: +> +> ```scala +> import java.lang.{Double => JDouble} +> import java.lang.{Double => JavaDouble, Integer} +> ``` +> +> but not: +> +> ```scala +> 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: +> +> ```scala +> 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: +> +> ```scala +> 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 when `groupedImports` is set to `Merge` 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: + +```scala +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 covers `mutble.Map`. However, similar to the +example illustrated in the section about the +[`coalesceToWildcardImportThreshold` +option](OrganizeImports.md#coalescetowildcardimportthreshold), the type of `Example.m` +above is `mutable.Map`, because the mutable `Map` explicitly imported in +the second import takes higher precedence than the immutable `Map` +imported via wildcard in the first import. If we merge the last two +imports naively, we’ll get: + +```scala +import scala.collection.immutable._ +import scala.collection.mutable._ +``` + +This triggers in a compilation error, because both `immutable.Map` and +`mutable.Map` are now imported via wildcards with the same precedence. +This makes the type of `Example.m` ambiguous. The correct result should +be: + +```scala +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: + +```scala +import scala.collection.mutable.Map +import scala.collection.mutable._ +``` + +Instead of being conservative and produce a suboptimal output like: + +```scala +import scala.collection.mutable.{Map, _} +``` + +setting `groupedImports` to `AggressiveMerge` produces +```scala +import scala.collection.mutable._ +``` + +#### `Keep` +Leave grouped imports and imports sharing the same prefix untouched. + +### Default value + +`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. + +### Examples + +#### `Explode` + +```conf +OrganizeImports.groupedImports = Explode +``` + +Before: + +```scala +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +``` + +After: + +```scala +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +``` + +#### `Merge` + +```conf +OrganizeImports.groupedImports = Merge +``` + +Before: + +```scala +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: + +```scala +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +import scala.collection.immutable.{Set, _} +``` + +#### `AggressiveMerge` + +```conf +OrganizeImports.groupedImports = AggressiveMerge +``` + +Before: + +```scala +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: + +```scala +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} +import scala.collection.immutable._ +``` + +`groups` +-------- + +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`](OrganizeImports.md#importsorder) option. + +> Comments living *between* imports being processed will be *removed*. + +> `OrganizeImports` tries to match the longest prefix while grouping +> imports. For instance, the following configuration groups `scala.meta.` +> and `scala.` imports into different two groups properly: +> +> ```conf +> OrganizeImports.groups = [ +> "re:javax?\\." +> "scala." +> "scala.meta." +> "*" +> ] +> ``` + +> No matter how the `groups` option is configured, a special +> order-preserving import group may appear after all the configured import +> groups when: +> +> 1. The `expandRelative` option is set to `false` and there are relative +> imports. +> +> 2. The `groupExplicitlyImportedImplicitsSeparately` option is set to +> `true` and there are implicit names explicitly imported. +> +> This special import group is necessary because the above two kinds of +> imports are order sensitive: +> +> #### Relative imports +> For instance, sorting the following imports in alphabetical order +> introduces compilation errors: +> +> ```scala +> import scala.util +> import util.control +> import control.NonFatal +> ``` +> #### Explicitly imported implicit names +> Please refer to the +> [`groupExplicitlyImportedImplicitsSeparately`](OrganizeImports.md#groupexplicitlyimportedimplicitsseparately) +> option for more details. + +### Value type + +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 the `scala` package. Please note that the trailing dot is +necessary, otherwise you may have `scalafix` and `scala` 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 the `java` and the +`javax` packages. Please refer to the +[`java.util.regex.Pattern`](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html) +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: + +```conf +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +OrganizeImports.groups = ["re:javax?\\.", "scala."] +``` + +#### A blank line marker +A blank line marker, `"---"`, defines a blank line between two adjacent +import groups when [`blankLines`](OrganizeImports.md#blanklines) is +set to `Manual`. It is ignored when `blankLines` is `Auto`. 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: + +```conf +OrganizeImports { + blankLines = Manual + groups = [ + "---" + "re:javax?\\." + "---" + "scala." + "---" + "---" + "*" + "---" + ] +} + +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "---" + "scala." + "---" + "*" + ] +} + +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "scala." + "*" + ] +} +``` + +### Default value + +```conf +[ + "*" + "re:(javax?|scala)\\." +] +``` + +Rationale: this aligns with the default configuration of the IntelliJ Scala plugin +version 2020.3. + +### Examples + +#### Fully-qualified imports only + +```conf +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +``` + +Before: + +```scala +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +``` + +After: + +```scala +import java.time.Clock +import javax.annotation.Generated + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import sun.misc.BASE64Encoder +``` + +#### With relative imports + +```conf +OrganizeImports.groups = ["re:javax?\\.", "scala.", "*"] +``` + +Before: + +```scala +import scala.utilRationale +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: +```scala +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 + +```conf +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + groupExplicitlyImportedImplicitsSeparately = true +} +``` + +Before: +```scala +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: + +```scala +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 the `OrganizeImports.groups` +option move imports from this package into the `scala.` group. The +following example illustrates how to move them into the wildcard group +using regular expression. + +```conf +OrganizeImports.groups = [ + "re:javax?\\." + "re:scala.(?!meta\\.)" + "*" +] +``` + +Before: + +```scala +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: + +```scala +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 + +```conf +OrganizeImports { + blankLines = Manual + groups = [ + "*" + "---" + "re:javax?\\." + "scala." + ] +} +``` + +Before: + +```scala +import scala.collection.JavaConverters._ +import java.time.Clock +import sun.misc.BASE64Encoder +import javax.annotation.Generated +import scala.concurrent.ExecutionContext +``` + +After: + +```scala +import sun.misc.BASE64Encoder + +import java.time.Clock +import javax.annotation.Generated +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext +``` + +`importSelectorsOrder` +---------------------- + +Specifies the order of grouped import selectors within a single import +expression. + +### Value type + +Enum: `Ascii | SymbolsFirst | Keep` + +#### `Ascii` +Sort import selectors by ASCII codes, equivalent to the +[`AsciiSortImports`](https://scalameta.org/scalafmt/docs/configuration.html#asciisortimports) +rewriting rule in Scalafmt. + +#### `SymbolsFirst` +Sort import selectors by the groups: symbols, lower-case, upper-case, +equivalent to the +[`SortImports`](https://scalameta.org/scalafmt/docs/configuration.html#sortimports) +rewriting rule in Scalafmt. + +#### `Keep` +Keep the original order. + +### Default value + +`Ascii` + +### Examples + +#### `Ascii` + +```conf +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = Ascii +} +``` + +Before: + +```scala +import foo.{~>, `symbol`, bar, Random} +``` + +After: + +```scala +import foo.{Random, `symbol`, bar, ~>} +``` + +#### `SymbolsFirst` + +```conf +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = SymbolsFirst +} +``` + +Before: + +```scala +import foo.{Random, `symbol`, bar, ~>} +``` + +After: +```scala +import foo.{~>, `symbol`, bar, Random} +``` + +`importsOrder` +-------------- + +Specifies the order of import statements within import groups defined by +the [`OrganizeImports.groups`](#groups) option. + +### Value type + +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. + +### Default value + +`Ascii` + +### Examples + +#### `Ascii` + +```conf +OrganizeImports { + groupedImports = Keep + importsOrder = Ascii +} +``` + +Before: + +```scala +import scala.concurrent._ +import scala.concurrent.{Future, Promise} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +``` + +After: + +```scala +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} +``` + +#### `SymbolsFirst` + +```conf +OrganizeImports { + groupedImports = Keep + importsOrder = SymbolsFirst +} +``` + +Before: + +```scala +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} +``` + +After: + +```scala +import scala.concurrent._ +import scala.concurrent.{Future, Promise} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +``` + +`preset` +-------- + +Specify a preset style. + +### Value type + +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 setting +`groupedImports` to `Explode`, this style is also more friendly to +version control and less likely to create annoying merge conflicts +caused by trivial import changes. + +```conf +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 the +[`coalesceToWildcardImportThreshold` +option](OrganizeImports.md#coalescetowildcardimportthreshold) +for more details. + +```conf +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 +} +``` + +### Default value + +`DEFAULT` + +`removeUnused` +-------------- + +Remove unused imports. + +> The `removeUnused` option doesn’t play perfectly with the `expandRelative` +> option. Setting `expandRelative` to `true` might introduce new unused +> imports (see [`expandRelative`](OrganizeImports.md#expandrelative)). +> These newly introduced unused imports cannot be removed by setting +> `removeUnused` to `true`. This is because unused imports are identified +> using Scala compilation diagnostics information, and the compilation phase +> happens before Scalafix rules get applied. + +> The `removeUnused` option is currently not supported for source files +> compiled with Scala 3, as the [compiler cannot issue warnings for unused +> imports +> yet](https://docs.scala-lang.org/scala3/guides/migration/options-lookup.html#warning-settings). +> As a result, you must set `removeUnused` to `false` when running the +> rule on source files compiled with Scala 3. + +### Value type + +Boolean + +### Default value + +`true` + +### Examples + +```conf +OrganizeImports { + groups = ["javax?\\.", "scala.", "*"] + removeUnused = true // not supported in Scala 3 +} +``` + +Before: + +```scala +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: + +```scala +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") +} +``` \ No newline at end of file diff --git a/docs/rules/community-rules.md b/docs/rules/community-rules.md index 3a0c78687..ee2a247cd 100644 --- a/docs/rules/community-rules.md +++ b/docs/rules/community-rules.md @@ -18,7 +18,6 @@ rewriting the code when possible. | - | - | - | [ghostbuster91/scalafix-unified](https://github.com/ghostbuster91/scalafix-unified) | `io.github.ghostbuster91.scalafix-unified::unified` | Set of opinionated rules to unify your codebase [jatcwang/scalafix-named-params](https://github.com/jatcwang/scalafix-named-params) | `com.github.jatcwang::scalafix-named-params` | Add named parameters for your constructor and method calls -[liancheng/scalafix-organize-imports](https://github.com/liancheng/scalafix-organize-imports) | `com.github.liancheng::organize-imports` | Help you organize Scala import statements [vovapolu/scaluzzi](https://github.com/vovapolu/scaluzzi) | `com.github.vovapolu::scaluzzi` | Ensure a subset of [scalazzi](http://yowconference.com.au/slides/yowwest2014/Morris-ParametricityTypesDocumentationCodeReadability.pdf) [xuwei-k/scalafix-rules](https://github.com/xuwei-k/scalafix-rules) | `com.github.xuwei-k::scalafix-rules` | Avoid ambiguous or redundant Scala syntax & features [pixiv/scalafix-pixiv-rule](https://github.com/pixiv/scalafix-pixiv-rule) | `net.pixiv::scalafix-pixiv-rule` | Redundant Scala code rewriting and anti-pattern warnings diff --git a/scalafix-docs/src/main/scala/docs/website.scala b/scalafix-docs/src/main/scala/docs/website.scala index 841de9167..8878d507d 100644 --- a/scalafix-docs/src/main/scala/docs/website.scala +++ b/scalafix-docs/src/main/scala/docs/website.scala @@ -80,7 +80,7 @@ package object website { .toString() case _ => any.toString } - private def flat[T]( + def flat[T]( default: T )(implicit settings: Settings[T], ev: T <:< Product): List[(Setting, Any)] = { settings.settings diff --git a/scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule b/scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule index c3d349733..f25260297 100644 --- a/scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule +++ b/scalafix-rules/src/main/resources-2/META-INF/services/scalafix.v1.Rule @@ -2,6 +2,7 @@ scalafix.internal.rule.DisableSyntax scalafix.internal.rule.ExplicitResultTypes scalafix.internal.rule.NoAutoTupling scalafix.internal.rule.NoValInForComprehension +scalafix.internal.rule.OrganizeImports scalafix.internal.rule.ProcedureSyntax scalafix.internal.rule.RedundantSyntax scalafix.internal.rule.RemoveUnused diff --git a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule b/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule index 2578d8ae9..6c02c66e4 100644 --- a/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule +++ b/scalafix-rules/src/main/resources-3/META-INF/services/scalafix.v1.Rule @@ -1,6 +1,7 @@ scalafix.internal.rule.DisableSyntax scalafix.internal.rule.NoAutoTupling scalafix.internal.rule.NoValInForComprehension +scalafix.internal.rule.OrganizeImports scalafix.internal.rule.RedundantSyntax scalafix.internal.rule.RemoveUnused scalafix.internal.rule.LeakingImplicitClassVal diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/ImportMatcher.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/ImportMatcher.scala new file mode 100644 index 000000000..b1341e855 --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/ImportMatcher.scala @@ -0,0 +1,42 @@ +package scalafix.internal.rule + +import scala.util.matching.Regex + +import scala.meta.Importer +import scala.meta.XtensionSyntax + +sealed trait ImportMatcher { + def matches(i: Importer): Int +} + +object ImportMatcher { + def parse(pattern: String): ImportMatcher = + pattern match { + case p if p startsWith "re:" => RE(new Regex(p stripPrefix "re:")) + case "---" => --- + case "*" => * + case p => PlainText(p) + } + + case class RE(pattern: Regex) extends ImportMatcher { + override def matches(i: Importer): Int = + pattern findPrefixMatchOf i.syntax map (_.end) getOrElse 0 + } + + case class PlainText(pattern: String) extends ImportMatcher { + override def matches(i: Importer): Int = + if (i.syntax startsWith pattern) pattern.length else 0 + } + + case object * extends ImportMatcher { + // The wildcard matcher matches nothing. It is special-cased at the end of the import group + // matching process. + def matches(importer: Importer): Int = 0 + } + + case object --- extends ImportMatcher { + // Blank line matchers are pseudo matchers matching nothing. They are special-cased at the end + // of the import group matching process. + override def matches(i: Importer): Int = 0 + } +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala new file mode 100644 index 000000000..9abbb3015 --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala @@ -0,0 +1,1006 @@ +package scalafix.internal.rule + +import scala.annotation.tailrec +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.util.Try + +import scala.meta.Import +import scala.meta.Importee +import scala.meta.Importee.GivenAll +import scala.meta.Importee.Wildcard +import scala.meta.Importer +import scala.meta.Name +import scala.meta.Pkg +import scala.meta.Source +import scala.meta.Stat +import scala.meta.Term +import scala.meta.Tree +import scala.meta.XtensionClassifiable +import scala.meta.XtensionCollectionLikeUI +import scala.meta.XtensionSyntax +import scala.meta.inputs.Position +import scala.meta.tokens.Token + +import metaconfig.Conf +import metaconfig.ConfDecoder +import metaconfig.ConfEncoder +import metaconfig.ConfOps +import metaconfig.Configured +import metaconfig.internal.ConfGet +import scalafix.internal.rule.ImportMatcher.* +import scalafix.internal.rule.ImportMatcher.--- +import scalafix.internal.rule.ImportMatcher.parse +import scalafix.lint.Diagnostic +import scalafix.patch.Patch +import scalafix.v1.Configuration +import scalafix.v1.Rule +import scalafix.v1.RuleName.stringToRuleName +import scalafix.v1.SemanticDocument +import scalafix.v1.SemanticRule +import scalafix.v1.Symbol +import scalafix.v1.SymbolInformation +import scalafix.v1.XtensionSeqPatch +import scalafix.v1.XtensionTreeScalafix + +class OrganizeImports(config: OrganizeImportsConfig) + extends SemanticRule("OrganizeImports") { + import OrganizeImports._ + import ImportMatcher._ + + private val matchers = buildImportMatchers(config) + + private val wildcardGroupIndex: Int = matchers indexOf * + + private val unusedImporteePositions: mutable.Set[Position] = + mutable.Set.empty[Position] + + private val diagnostics: ArrayBuffer[Diagnostic] = + ArrayBuffer.empty[Diagnostic] + + def this() = this(OrganizeImportsConfig()) + + override def isLinter: Boolean = true + + override def isRewrite: Boolean = true + + override def withConfiguration(config: Configuration): Configured[Rule] = + config.conf + .getOrElse("OrganizeImports")(OrganizeImportsConfig()) + .andThen(patchPreset(_, config.conf)) + .andThen(checkScalacOptions(_, config.scalacOptions, config.scalaVersion)) + + override def fix(implicit doc: SemanticDocument): Patch = { + unusedImporteePositions ++= doc.diagnostics.collect { + case d if d.message == "Unused import" => d.position + } + + val (globalImports, localImports) = collectImports(doc.tree) + + val globalImportsPatch = + if (globalImports.isEmpty) Patch.empty + else organizeGlobalImports(globalImports) + + val localImportsPatch = + if (!config.removeUnused || localImports.isEmpty) Patch.empty + else removeUnused(localImports) + + diagnostics.map(Patch.lint).asPatch + globalImportsPatch + localImportsPatch + } + + private def isUnused(importee: Importee): Boolean = + unusedImporteePositions contains positionOf(importee) + + private def organizeGlobalImports( + imports: Seq[Import] + )(implicit doc: SemanticDocument): Patch = { + val noUnused = imports flatMap (_.importers) flatMap (removeUnused(_).toSeq) + + val (implicits, noImplicits) = + if (!config.groupExplicitlyImportedImplicitsSeparately) (Nil, noUnused) + else partitionImplicits(noUnused) + + val (fullyQualifiedImporters, relativeImporters) = + noImplicits partition isFullyQualified + + // Organizes all the fully-qualified global importers. + val fullyQualifiedGroups: Seq[ImportGroup] = { + val expanded = + if (config.expandRelative) relativeImporters map expandRelative else Nil + groupImporters(fullyQualifiedImporters ++ expanded) + } + + // Moves relative imports (when `config.expandRelative` is false) and + // explicitly imported implicit names into a separate order preserving + // group. This group will be appended after all the other groups. + // + // See https://github.com/liancheng/scalafix-organize-imports/issues/30 + // for why implicits require special handling. + val orderPreservingGroup = { + val relatives = if (config.expandRelative) Nil else relativeImporters + Option( + relatives ++ implicits sortBy (_.importees.head.pos.start) + ) filter (_.nonEmpty) + } + + // Builds a patch that inserts the organized imports. + val insertionPatch = insertOrganizedImports( + imports.head.tokens.head, + fullyQualifiedGroups ++ + orderPreservingGroup.map(ImportGroup(matchers.length, _)) + ) + + // Builds a patch that removes all the tokens forming the original imports. + val removalPatch = Patch.removeTokens( + doc.tree.tokens.slice( + imports.head.tokens.start, + imports.last.tokens.end + ) + ) + + (insertionPatch + removalPatch).atomic + } + + private def removeUnused(imports: Seq[Import]): Patch = + Patch.fromIterable { + imports flatMap (_.importers) flatMap { case Importer(_, importees) => + val hasUsedWildcard = importees exists { i => + i.is[Importee.Wildcard] && !isUnused(i) + } + + importees collect { + case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since + // unused renamed may still impact compilation by shadowing an + // identifier. + // + // See https://github.com/scalacenter/scalafix/issues/614 + Patch.replaceTree(to, "_").atomic + + case i if isUnused(i) => + Patch.removeImportee(i).atomic + } + } + } + + private def removeUnused(importer: Importer): Option[Importer] = + if (!config.removeUnused) Some(importer) + else { + val hasUsedWildcard = importer.importees exists { i => + i.is[Importee.Wildcard] && !isUnused(i) + } + + var rewritten = false + + val noUnused = importer.importees.flatMap { + case i @ Importee.Rename(from, _) if isUnused(i) && hasUsedWildcard => + // Unimport the identifier instead of removing the importee since + // unused renamed may still impact compilation by shadowing an + // identifier. + // + // See https://github.com/scalacenter/scalafix/issues/614 + rewritten = true + Importee.Unimport(from) :: Nil + + case i if isUnused(i) => + rewritten = true + Nil + + case i => + i :: Nil + } + + if (!rewritten) Some(importer) + else if (noUnused.isEmpty) None + else Some(importer.copy(importees = noUnused)) + } + + private def partitionImplicits( + importers: Seq[Importer] + )(implicit doc: SemanticDocument): (Seq[Importer], Seq[Importer]) = { + val (implicits, implicitPositions) = importers.flatMap { + case importer @ Importer(_, importees) => + importees collect { + case i: Importee.Name if i.symbol.infoNoThrow exists (_.isImplicit) => + importer.copy(importees = i :: Nil) -> i.pos + } + }.unzip + + val noImplicits = importers.flatMap { + _.filterImportees { importee => + !implicitPositions.contains(importee.pos) + }.toSeq + } + + (implicits, noImplicits) + } + + private def isFullyQualified( + importer: Importer + )(implicit doc: SemanticDocument): Boolean = { + val topQualifier = topQualifierOf(importer.ref) + val topQualifierSymbol = topQualifier.symbol + val owner = topQualifierSymbol.owner + + ( + // The owner of the top qualifier is `_root_`, e.g.: `import scala.util` + owner.isRootPackage || + + // The top qualifier is a top-level class/trait/object defined under no packages. In this + // case, Scalameta defines the owner to be the empty package. + owner.isEmptyPackage || + + // The top qualifier itself is `_root_`, e.g.: `import _root_.scala.util` + topQualifier.value == "_root_" || + + // https://github.com/liancheng/scalafix-organize-imports/issues/64: + // Sometimes, the symbol of the top qualifier can be missing due to + // unknown reasons. In this case, we issue a warning and continue + // processing assuming that the top qualifier is fully-qualified. + topQualifierSymbol.isNone && { + diagnostics += ImporterSymbolNotFound(topQualifier) + true + } + ) + } + + private def expandRelative( + importer: Importer + )(implicit doc: SemanticDocument): Importer = { + + /** + * Converts a `Symbol` into a fully-qualified `Term.Ref`. + * + * NOTE: The returned `Term.Ref` does NOT contain symbol information since + * it's not parsed from the source file. + */ + def toFullyQualifiedRef(symbol: Symbol): Term.Ref = { + val owner = symbol.owner + + symbol match { + // When importing names defined within package objects, skip the `package` part for brevity. + // For instance, with the following definition: + // + // package object foo { val x: Int = ??? } + // + // when importing `foo.x`, we prefer "import foo.x" instead of "import foo.`package`.x", + // which is also valid, but unnecessarily lengthy. + // + // See https://github.com/liancheng/scalafix-organize-imports/issues/55. + case _ if symbol.infoNoThrow exists (_.isPackageObject) => + toFullyQualifiedRef(owner) + + // See the comment marked with "issues/64" for the case of `symbol.isNone` + case _ + if symbol.isNone || owner.isRootPackage || owner.isEmptyPackage => + Term.Name(symbol.displayName) + + case _ => + Term.Select(toFullyQualifiedRef(owner), Term.Name(symbol.displayName)) + } + } + + val fullyQualifiedTopQualifier = + toFullyQualifiedRef(topQualifierOf(importer.ref).symbol) + + importer.copy( + ref = replaceTopQualifier(importer.ref, fullyQualifiedTopQualifier) + ) + } + + private def groupImporters(importers: Seq[Importer]): Seq[ImportGroup] = + importers + .groupBy(matchImportGroup) // Groups imports by importer prefix. + .mapValues(deduplicateImportees _ andThen organizeImportGroup) + .map { case (index, imports) => ImportGroup(index, imports) } + .toSeq + .sortBy(_.index) + + private def deduplicateImportees(importers: Seq[Importer]): Seq[Importer] = { + // Scalameta `Tree` nodes do not provide structural equality comparisons, here we pretty-print + // them and compare the string results. + val seenImportees = mutable.Set.empty[(String, String)] + + importers flatMap { importer => + importer filterImportees { importee => + importee.is[Importee.Wildcard] || importee.is[Importee.GivenAll] || + seenImportees.add(importee.syntax -> importer.ref.syntax) + } + } + } + + private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = { + // https://github.com/liancheng/scalafix-organize-imports/issues/96: For + // importers with only a single `Importee.Name` importee, if the importee + // is curly-braced, remove the unneeded curly-braces. For example: + // `import p.{X}` should be rewritten into `import p.X`. + val noUnneededBraces = importers map removeRedundantBrances + + val importeesSorted = locally { + config.groupedImports match { + case GroupedImports.Merge => + mergeImporters(noUnneededBraces, aggressive = false) + case GroupedImports.AggressiveMerge => + mergeImporters(noUnneededBraces, aggressive = true) + case GroupedImports.Explode => + explodeImportees(noUnneededBraces) + case GroupedImports.Keep => + noUnneededBraces + } + } map (coalesceImportees _ andThen sortImportees) + + config.importsOrder match { + // https://github.com/liancheng/scalafix-organize-imports/issues/84: The Scalameta `Tree` node pretty-printer + // checks whether the node originates directly from the parser. If yes, the original source + // code text is returned, and may interfere imports sort order. The `.copy()` call below + // erases the source position information so that the pretty-printer would actually + // pretty-print an `Importer` into a single line. + case ImportsOrder.Ascii => + importeesSorted sortBy (i => importerSyntax(i.copy())) + case ImportsOrder.SymbolsFirst => + sortImportersSymbolsFirst(importeesSorted) + case ImportsOrder.Keep => + importeesSorted + } + } + + private def removeRedundantBrances(importer: Importer): Importer = + importer match { + case i @ Importer(_, Importee.Name(_) :: Nil) => + import Token.{Ident, LeftBrace, RightBrace} + + i.tokens.reverse.toList match { + // The `.copy()` call erases the source position information from the original importer, so + // that instead of returning the original source text, the pretty-printer will reformat + // `importer` without the unneeded curly-braces. + case RightBrace() :: Ident(_) :: LeftBrace() :: _ => i.copy() + case _ => i + } + + case _ => importer + } + + private def mergeImporters( + importers: Seq[Importer], + aggressive: Boolean + ): Seq[Importer] = + importers.groupBy(_.ref.syntax).values.toSeq.flatMap { + case importer :: Nil => + // If this group has only one importer, returns it as is to preserve the original source + // level formatting. + importer :: Nil + + case group @ Importer(ref, _) :: _ => + val importeeLists = group map (_.importees) + val hasWildcard = group exists (_.hasWildcard) + val hasGivenAll = group exists (_.hasGivenAll) + + // Collects the last set of unimports with a wildcard, if any. It cancels all previous + // unimports. E.g.: + // + // import p.{A => _} + // import p.{B => _, _} + // import p.{C => _, _} + // + // Only `C` is unimported. `A` and `B` are still available. + // + // TODO: Shall we issue a warning here as using order-sensitive imports is a bad practice? + val lastUnimportsWithWildcard = importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, _, Some(_)) => unimports + } + + val lastUnimportsWithGivenAll = importeeLists.reverse collectFirst { + case Importees(_, _, unimports @ _ :: _, _, Some(_), _) => unimports + } + + // Collects all unimports without an accompanying wildcard. + val unimports = importeeLists.collect { + case Importees(_, _, unimports, _, None, None) => + unimports + }.flatten + + val (givens, nonGivens) = + group.flatMap(_.importees).toList.partition(_.is[Importee.Given]) + + // Here we assume that a name is renamed at most once within a single source file, which is + // true in most cases. + // + // Note that the IntelliJ IDEA Scala import optimizer does not handle this case properly + // either. If a name is renamed more than once, it only keeps one of the renames in the + // result and may break compilation (unless other renames are not actually referenced). + val renames = nonGivens + .collect { case rename: Importee.Rename => rename } + .groupBy(_.name.value) + .map { + case (_, rename :: Nil) => rename + case (_, renames @ (head @ Importee.Rename(from, _)) :: _) => + diagnostics += TooManyAliases(from, renames) + head + } + .toList + + // Collects distinct explicitly imported names, and filters out those that are also renamed. + // If an explicitly imported name is also renamed, both the original name and the new name + // are available. This implies that both of them must be preserved in the merged result, but + // in two separate import statements (Scala only allows a name to appear in an import at + // most once). E.g.: + // + // import p.A + // import p.{A => A1} + // import p.B + // import p.{B => B1} + // + // The above snippet should be rewritten into: + // + // import p.{A, B} + // import p.{A => A1, B => B1} + val (renamedImportedNames, importedNames) = { + val renamedNames = + renames.map { case Importee.Rename(Name(from), _) => + from + }.toSet + + nonGivens + .filter(_.is[Importee.Name]) + .groupBy { case Importee.Name(Name(name)) => name } + .map { case (_, importees) => importees.head } + .toList + .partition { case Importee.Name(Name(name)) => + renamedNames contains name + } + } + + val mergedNonGivens = (hasWildcard, lastUnimportsWithWildcard) match { + case (true, _) => + // A few things to note in this case: + // + // 1. Unimports are discarded because they are canceled by the wildcard. E.g.: + // + // import scala.collection.mutable.{Set => _, _} + // import scala.collection.mutable._ + // + // The above two imports should be merged into: + // + // import scala.collection.mutable._ + // + // 2. Explicitly imported names can NOT be discarded even though they seem to be covered + // by the wildcard, unless groupedImports is set to AggressiveMerge. This is because + // explicitly imported names have higher precedence than names imported via a + // wildcard. Discarding them may introduce ambiguity in some cases. E.g.: + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // import scala.collection.mutable.Set + // + // object Main { val s: Set[Int] = ??? } + // + // The type of `Main.s` above is unambiguous because `mutable.Set` is explicitly + // imported, and has higher precedence than `immutable.Set`, which is made available + // via a wildcard. In this case, the imports should be merged into: + // + // import scala.collection.immutable._ + // import scala.collection.mutable.{Set, _} + // + // rather than + // + // import scala.collection.immutable._ + // import scala.collection.mutable._ + // + // Otherwise, the type of `Main.s` becomes ambiguous and a compilation error is + // introduced. + // + // 3. However, the case discussed above is relatively rare in real life. A more common + // case is something like: + // + // import scala.collection.Set + // import scala.collection._ + // + // In this case, we do want to merge them into: + // + // import scala.collection._ + // + // rather than + // + // import scala.collection.{Set, _} + // + // To achieve this, users may set `groupedImports` to `AggressiveMerge`. Instead of + // being conservative and ensure correctness in all the cases, this option merges + // imports aggressively for conciseness. + // + // 4. Renames must be moved into a separate import statement to make sure that the + // original names made available by the wildcard are still preserved. E.g.: + // + // import p._ + // import p.{A => A1} + // + // The above imports cannot be merged into + // + // import p.{A => A1, _} + // + // Otherwise, the original name `A` is no longer available. + if (aggressive) Seq(renames, Wildcard() :: Nil) + else Seq(renames, importedNames :+ Wildcard()) + + case (false, Some(lastUnimports)) => + // A wildcard must be appended for unimports. + Seq( + renamedImportedNames, + importedNames ++ renames ++ lastUnimports :+ Wildcard() + ) + + case (false, None) => + Seq(renamedImportedNames, importedNames ++ renames ++ unimports) + } + + /* Adjust the result to add givens imports, these are + * are the Scala 3 way of importing implicits, which are not imported + * with the wildcard import. + */ + val newImporteeListsWithGivens = if (hasGivenAll) { + if (aggressive) mergedNonGivens :+ List(GivenAll()) + else mergedNonGivens :+ (givens :+ GivenAll()) + } else { + lastUnimportsWithGivenAll match { + case Some(unimports) => + mergedNonGivens :+ (givens ++ unimports :+ GivenAll()) + case None => + mergedNonGivens :+ givens + } + } + + preserveOriginalImportersFormatting( + group, + newImporteeListsWithGivens, + ref + ) + } + + private def sortImportersSymbolsFirst( + importers: Seq[Importer] + ): Seq[Importer] = + importers.sortBy { importer => + // See the comment marked with "issues/84" for why a `.copy()` is needed. + val syntax = importer.copy().syntax + + importer match { + case Importer(_, Importee.Wildcard() :: Nil) => + syntax.patch(syntax.lastIndexOfSlice("._"), ".\u0001", 2) + + case _ if importer.isCurlyBraced => + syntax + .replaceFirst("[{]", "\u0002") + .patch(syntax.lastIndexOf("}"), "\u0002", 1) + + case _ => syntax + } + } + + private def coalesceImportees(importer: Importer): Importer = { + val Importees(names, renames, unimports, givens, _, _) = importer.importees + + config.coalesceToWildcardImportThreshold + .filter(importer.importees.length > _) + // Skips if there's no `Name`s or `Given`s. `Rename`s and `Unimport`s cannot be coalesced. + .filterNot(_ => names.isEmpty && givens.isEmpty) + .map { + case _ if givens.isEmpty => renames ++ unimports :+ Wildcard() + case _ if names.isEmpty => renames ++ unimports :+ GivenAll() + case _ => renames ++ unimports :+ GivenAll() :+ Wildcard() + } + .map(importees => importer.copy(importees = importees)) + .getOrElse(importer) + } + + private def sortImportees(importer: Importer): Importer = { + import ImportSelectorsOrder._ + + // The Scala language spec allows an import expression to have at most one final wildcard, which + // can only appears in the last position. + val (wildcards, others) = + importer.importees partition (i => + i.is[Importee.Wildcard] || i.is[Importee.GivenAll] + ) + + val orderedImportees = config.importSelectorsOrder match { + case Ascii => + Seq(others, wildcards) map (_.sortBy(_.syntax)) reduce (_ ++ _) + case SymbolsFirst => + Seq(others, wildcards) map sortImporteesSymbolsFirst reduce (_ ++ _) + case Keep => + importer.importees + } + + // Checks whether importees of the input importer are already sorted. If yes, we should return + // the original importer to preserve the original source level formatting. + val alreadySorted = + config.importSelectorsOrder == Keep || + (importer.importees corresponds orderedImportees) { (lhs, rhs) => + lhs.syntax == rhs.syntax + } + + if (alreadySorted) importer else importer.copy(importees = orderedImportees) + } + + /** + * Returns the index of the group to which the given importer belongs. Each + * group is represented by an `ImportMatcher`. If multiple `ImporterMatcher`s + * match the given import, the one matches the longest prefix wins. + */ + private def matchImportGroup(importer: Importer): Int = { + val matchedGroups = matchers + .map(_ matches importer) + .zipWithIndex + .filter { case (length, _) => length > 0 } + + if (matchedGroups.isEmpty) wildcardGroupIndex + else { + val (_, index) = matchedGroups.maxBy { case (length, _) => length } + index + } + } + + private def insertOrganizedImports( + token: Token, + importGroups: Seq[ImportGroup] + ): Patch = { + val prettyPrintedGroups = importGroups.map { + case ImportGroup(index, imports) => + index -> prettyPrintImportGroup(imports) + } + + val blankLines = { + // Indices of all blank lines configured in `OrganizeImports.groups`, either automatically or + // manually. + val blankLineIndices = matchers.zipWithIndex.collect { + case (`---`, index) => index + }.toSet + + // Checks each pair of adjacent import groups. Inserts a blank line between them if necessary. + importGroups map (_.index) sliding 2 filter (_.length == 2) flatMap { + case Seq(lhs, rhs) => + val hasBlankLine = blankLineIndices exists (i => lhs < i && i < rhs) + if (hasBlankLine) Some((lhs + 1) -> "") else None + } + } + + val withBlankLines = (prettyPrintedGroups ++ blankLines) + .sortBy { case (index, _) => index } + .map { case (_, lines) => lines } + .mkString("\n") + + // Global imports within curly-braced packages must be indented accordingly, e.g.: + // + // package foo { + // package bar { + // import baz + // import qux + // } + // } + val indented = withBlankLines.linesIterator.zipWithIndex.map { + // The first line will be inserted at an already indented position. + case (line, 0) => line + case (line, _) if line.isEmpty => line + case (line, _) => " " * token.pos.startColumn + line + } + + Patch.addLeft(token, indented mkString "\n") + } +} + +object OrganizeImports { + private case class ImportGroup(index: Int, imports: Seq[Importer]) + + private def patchPreset( + ruleConf: OrganizeImportsConfig, + conf: Conf + ): Configured[OrganizeImportsConfig] = { + val preset = OrganizeImportsConfig.presets(ruleConf.preset) + val presetConf = ConfEncoder[OrganizeImportsConfig].write(preset) + val userConf = + ConfGet.getKey(conf, "OrganizeImports" :: Nil).getOrElse(Conf.Obj.empty) + val mergedConf = ConfOps.merge(presetConf, userConf) + ConfDecoder[OrganizeImportsConfig].read(mergedConf) + } + + private def checkScalacOptions( + conf: OrganizeImportsConfig, + scalacOptions: List[String], + scalaVersion: String + ): Configured[Rule] = { + val hasCompilerSupport = scalaVersion.startsWith("2") + + val hasWarnUnused = hasCompilerSupport && { + val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") + val warnUnusedString = Set("-Xlint", "-Xlint:unused") + scalacOptions exists { option => + (warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option) + } + } + + if (!conf.removeUnused || hasWarnUnused) + Configured.ok(new OrganizeImports(conf)) + else if (hasCompilerSupport) + Configured.error( + "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + + " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your" + + " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11" + + " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)." + ) + else + Configured.error( + "\"OrganizeImports.removeUnused\" is not supported on Scala 3 as the compiler is" + + " not providing enough information. Run the rule with" + + " \"OrganizeImports.removeUnused\" set to false to organize imports while keeping" + + " potentially unused imports." + ) + } + + private def buildImportMatchers( + config: OrganizeImportsConfig + ): Seq[ImportMatcher] = { + val withWildcard = { + val parsed = config.groups map parse + // The wildcard group should always exist. Appends one at the end if omitted. + if (parsed contains *) parsed else parsed :+ * + } + + // Inserts a blank line marker between adjacent import groups when `blankLines` is `Auto`. + config.blankLines match { + case BlankLines.Manual => withWildcard + case BlankLines.Auto => withWildcard.flatMap(_ :: --- :: Nil) + } + } + + private def positionOf(importee: Importee): Position = + importee match { + case Importee.Rename(from, _) => from.pos + case _ => importee.pos + } + + @tailrec private def collectImports( + tree: Tree + ): (Seq[Import], Seq[Import]) = { + def extractImports(stats: Seq[Stat]): (Seq[Import], Seq[Import]) = { + val (importStats, otherStats) = stats.span(_.is[Import]) + val globalImports = importStats.map { case i: Import => i } + val localImports = otherStats.flatMap(_.collect { case i: Import => i }) + (globalImports, localImports) + } + + tree match { + case Source(Seq(p: Pkg)) => collectImports(p) + case Pkg(_, Seq(p: Pkg)) => collectImports(p) + case Source(stats) => extractImports(stats) + case Pkg(_, stats) => extractImports(stats) + case _ => (Nil, Nil) + } + } + + private def prettyPrintImportGroup(group: Seq[Importer]): String = + group + .map(i => "import " + importerSyntax(i)) + .mkString("\n") + + /** + * HACK: The Scalafix pretty-printer decides to add spaces after open and + * before close braces in imports with multiple importees, i.e., `import a.{ + * b, c }` instead of `import a.{b, c}`. On the other hand, renames are + * pretty-printed without the extra spaces, e.g., `import a.{b => c}`. This + * behavior is not customizable and makes ordering imports by ASCII order + * complicated. + * + * This function removes the unwanted spaces as a workaround. In cases where + * users do want the inserted spaces, Scalafmt should be used after running + * the `OrganizeImports` rule. + */ + private def importerSyntax(importer: Importer): String = + importer.pos match { + case pos: Position.Range => + // Position found, implies that `importer` was directly parsed from the source code. Returns + // the original parsed text to preserve the original source level formatting. + pos.text + + case Position.None => + // Position not found, implies that `importer` is derived from certain existing import + // statement(s). Pretty-prints it. + val syntax = importer.syntax + + // NOTE: We need to check whether the input importer is curly braced first and then replace + // the first "{ " and the last " }" if any. Naive string replacement is insufficient, e.g., + // a quoted-identifier like "`{ d }`" may cause broken output. + (importer.isCurlyBraced, syntax lastIndexOfSlice " }") match { + case (_, -1) => + syntax + case (true, index) => + syntax.patch(index, "}", 2).replaceFirst("\\{ ", "{") + case _ => + syntax + } + } + + @tailrec private def topQualifierOf(term: Term): Term.Name = + term match { + case Term.Select(qualifier, _) => topQualifierOf(qualifier) + case name: Term.Name => name + } + + /** + * Replaces the top-qualifier of the input `term` with a new term + * `newTopQualifier`. + */ + private def replaceTopQualifier( + term: Term, + newTopQualifier: Term.Ref + ): Term.Ref = + term match { + case _: Term.Name => + newTopQualifier + case Term.Select(qualifier, name) => + Term.Select(replaceTopQualifier(qualifier, newTopQualifier), name) + } + + private def sortImporteesSymbolsFirst( + importees: List[Importee] + ): List[Importee] = { + val symbols = ArrayBuffer.empty[Importee] + val lowerCases = ArrayBuffer.empty[Importee] + val upperCases = ArrayBuffer.empty[Importee] + + importees.foreach { + case i if i.syntax.head.isLower => lowerCases += i + case i if i.syntax.head.isUpper => upperCases += i + case i => symbols += i + } + + List(symbols, lowerCases, upperCases) flatMap (_ sortBy (_.syntax)) + } + + private def explodeImportees(importers: Seq[Importer]): Seq[Importer] = + importers flatMap { + case importer @ Importer(_, _ :: Nil) => + // If the importer has exactly one importee, returns it as is to preserve the original + // source level formatting. + importer :: Nil + + case importer @ Importer( + ref, + Importees(names, renames, unimports, givens, givenAll, wildcard) + ) if givenAll.isDefined || wildcard.isDefined => + // When a wildcard exists, all renames, unimports, and the wildcard must appear in the same + // importer, e.g.: + // + // import p.{A => _, B => _, C => D, E, _} + // + // should be rewritten into + // + // import p.{A => _, B => _, C => D, _} + // import p.E + val importeesList = + (names ++ givens).map( + _ :: Nil + ) :+ (renames ++ unimports ++ wildcard ++ givenAll) + preserveOriginalImportersFormatting(Seq(importer), importeesList, ref) + + case importer => + importer.importees map (i => importer.copy(importees = i :: Nil)) + } + + /** + * https://github.com/liancheng/scalafix-organize-imports/issues/127: After + * merging or exploding imports, checks whether there are any input importers + * left untouched. For those importers, returns the original importer instance + * to preserve the original source level formatting. + */ + private def preserveOriginalImportersFormatting( + importers: Seq[Importer], + newImporteeLists: Seq[List[Importee]], + newImporterRef: Term.Ref + ) = { + val importerSyntaxMap = importers.map { i => i.copy().syntax -> i }.toMap + + newImporteeLists filter (_.nonEmpty) map { importees => + val newImporter = Importer(newImporterRef, importees) + importerSyntaxMap.getOrElse(newImporter.syntax, newImporter) + } + } + + /** + * Categorizes a list of `Importee`s into the following four groups: + * + * - Names, e.g., `Seq`, `Option`, etc. + * - Renames, e.g., `{Long => JLong}`, `{Duration => D}`, etc. + * - Unimports, e.g., `{Foo => _}`. + * - Givens, e.g., `{given Foo}`. + * - GivenAll, i.e., `given`. + * - Wildcard, i.e., `_`. + */ + object Importees { + def unapply(importees: Seq[Importee]): Option[ + ( + List[Importee.Name], + List[Importee.Rename], + List[Importee.Unimport], + List[Importee.Given], + Option[Importee.GivenAll], + Option[Importee.Wildcard] + ) + ] = { + val names = ArrayBuffer.empty[Importee.Name] + val renames = ArrayBuffer.empty[Importee.Rename] + val givens = ArrayBuffer.empty[Importee.Given] + val unimports = ArrayBuffer.empty[Importee.Unimport] + var maybeWildcard: Option[Importee.Wildcard] = None + var maybeGivenAll: Option[Importee.GivenAll] = None + + importees foreach { + case i: Importee.Wildcard => maybeWildcard = Some(i) + case i: Importee.Unimport => unimports += i + case i: Importee.Rename => renames += i + case i: Importee.Name => names += i + case i: Importee.Given => givens += i + case i: Importee.GivenAll => maybeGivenAll = Some(i) + } + + Option( + ( + names.toList, + renames.toList, + unimports.toList, + givens.toList, + maybeGivenAll, + maybeWildcard + ) + ) + } + } + + implicit private class SymbolExtension(symbol: Symbol) { + + /** + * HACK: In certain cases, `Symbol#info` may throw `MissingSymbolException` + * due to some unknown reason. This implicit class adds a safe version of + * `Symbol#info` to return `None` instead of throw an exception when this + * happens. + * + * See [[https://github.com/scalacenter/scalafix/issues/1123 issue #1123]]. + */ + def infoNoThrow(implicit doc: SemanticDocument): Option[SymbolInformation] = + Try(symbol.info).toOption.flatten + } + + implicit private class ImporterExtension(importer: Importer) { + + /** Checks whether the `Importer` is curly-braced when pretty-printed. */ + def isCurlyBraced: Boolean = { + val importees @ Importees(_, renames, unimports, _, _, _) = + importer.importees + renames.nonEmpty || unimports.nonEmpty || importees.length > 1 + } + + /** + * Returns an `Importer` with all the `Importee`s that are selected from the + * input `Importer` and satisfy a predicate. If all the `Importee`s are + * selected, the input `Importer` instance is returned to preserve the + * original source level formatting. If none of the `Importee`s are + * selected, returns a `None`. + */ + def filterImportees(f: Importee => Boolean): Option[Importer] = { + val filtered = importer.importees filter f + if (filtered.length == importer.importees.length) Some(importer) + else if (filtered.isEmpty) None + else Some(importer.copy(importees = filtered)) + } + + /** Returns true if the `Importer` contains a standalone wildcard. */ + def hasWildcard: Boolean = { + val Importees(_, _, unimports, _, _, wildcard) = importer.importees + unimports.isEmpty && wildcard.nonEmpty + } + + /** Returns true if the `Importer` contains a standalone given wildcard. */ + def hasGivenAll: Boolean = { + val Importees(_, _, unimports, _, givenAll, _) = importer.importees + unimports.isEmpty && givenAll.nonEmpty + } + } +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImportsConfig.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImportsConfig.scala new file mode 100644 index 000000000..d9703df4a --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImportsConfig.scala @@ -0,0 +1,111 @@ +package scalafix.internal.rule + +import metaconfig.Conf +import metaconfig.ConfDecoder +import metaconfig.ConfEncoder +import metaconfig.generic.Surface +import metaconfig.generic.deriveDecoder +import metaconfig.generic.deriveEncoder +import metaconfig.generic.deriveSurface +import scalafix.internal.config.ReaderUtil + +sealed trait ImportsOrder + +object ImportsOrder { + case object Ascii extends ImportsOrder + case object SymbolsFirst extends ImportsOrder + case object Keep extends ImportsOrder + + implicit def reader: ConfDecoder[ImportsOrder] = + ReaderUtil.oneOf(Ascii, SymbolsFirst, Keep) + implicit def writer: ConfEncoder[ImportsOrder] = + ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +sealed trait ImportSelectorsOrder + +object ImportSelectorsOrder { + case object Ascii extends ImportSelectorsOrder + case object SymbolsFirst extends ImportSelectorsOrder + case object Keep extends ImportSelectorsOrder + + implicit def reader: ConfDecoder[ImportSelectorsOrder] = + ReaderUtil.oneOf(Ascii, SymbolsFirst, Keep) + + implicit def writer: ConfEncoder[ImportSelectorsOrder] = + ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +sealed trait GroupedImports + +object GroupedImports { + case object AggressiveMerge extends GroupedImports + case object Merge extends GroupedImports + case object Explode extends GroupedImports + case object Keep extends GroupedImports + + implicit def reader: ConfDecoder[GroupedImports] = + ReaderUtil.oneOf(AggressiveMerge, Merge, Explode, Keep) + + implicit def writer: ConfEncoder[GroupedImports] = + ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +sealed trait BlankLines + +object BlankLines { + case object Auto extends BlankLines + case object Manual extends BlankLines + + implicit def reader: ConfDecoder[BlankLines] = + ReaderUtil.oneOf(Auto, Manual) + implicit def writer: ConfEncoder[BlankLines] = + ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +sealed trait Preset + +object Preset { + case object DEFAULT extends Preset + case object INTELLIJ_2020_3 extends Preset + + implicit def reader: ConfDecoder[Preset] = + ReaderUtil.oneOf(DEFAULT, INTELLIJ_2020_3) + implicit def writer: ConfEncoder[Preset] = + ConfEncoder.instance(v => Conf.Str(v.toString)) +} + +final case class OrganizeImportsConfig( + blankLines: BlankLines = BlankLines.Auto, + coalesceToWildcardImportThreshold: Option[Int] = None, + expandRelative: Boolean = false, + groupExplicitlyImportedImplicitsSeparately: Boolean = false, + groupedImports: GroupedImports = GroupedImports.Explode, + groups: Seq[String] = Seq( + "*", + "re:(javax?|scala)\\." + ), + importSelectorsOrder: ImportSelectorsOrder = ImportSelectorsOrder.Ascii, + importsOrder: ImportsOrder = ImportsOrder.Ascii, + preset: Preset = Preset.DEFAULT, + removeUnused: Boolean = true +) + +object OrganizeImportsConfig { + val default: OrganizeImportsConfig = OrganizeImportsConfig() + + implicit val surface: Surface[OrganizeImportsConfig] = + deriveSurface + implicit val encoder: ConfEncoder[OrganizeImportsConfig] = + deriveEncoder + implicit val decoder: ConfDecoder[OrganizeImportsConfig] = + deriveDecoder(default) + + val presets: Map[Preset, OrganizeImportsConfig] = Map( + Preset.DEFAULT -> OrganizeImportsConfig(), + Preset.INTELLIJ_2020_3 -> OrganizeImportsConfig( + coalesceToWildcardImportThreshold = Some(5), + groupedImports = GroupedImports.Merge + ) + ) +} diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/diagnostics.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/diagnostics.scala new file mode 100644 index 000000000..bf7c6e6ef --- /dev/null +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/diagnostics.scala @@ -0,0 +1,45 @@ +package scalafix.internal.rule + +import scala.meta.Importee +import scala.meta.Name +import scala.meta.Term +import scala.meta.XtensionSyntax + +import scalafix.lint.Diagnostic +import scalafix.lint.LintSeverity + +case class ImporterSymbolNotFound(ref: Term.Name) extends Diagnostic { + override def position: meta.Position = ref.pos + + override def message: String = + s"Could not determine whether '${ref.syntax}' is fully-qualified because the symbol" + + " information is missing. We will continue processing assuming that it is fully-qualified." + + " Please check whether the corresponding .semanticdb file is properly generated." + + override def severity: LintSeverity = LintSeverity.Warning +} + +case class TooManyAliases(name: Name, renames: Seq[Importee.Rename]) + extends Diagnostic { + assert(renames.length > 1) + + override def position: meta.Position = name.pos + + override def message: String = { + val aliases = renames + .map { case Importee.Rename(_, Name(alias)) => alias } + .sorted + .map("'" + _ + "'") + + val aliasList = aliases match { + case head :: last :: Nil => s"$head and $last" + case init :+ last => init.mkString("", ", ", s", and $last") + } + + s"When `OrganizeImports.groupedImports` is set to `Merge`, renaming a name to multiple" + + s" aliases within the same source file is not supported. In this case, '${name.value}' is" + + s" renamed to $aliasList." + } + + override def severity: LintSeverity = LintSeverity.Error +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExpandRelativePackageObject.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExpandRelativePackageObject.scala new file mode 100644 index 000000000..b133e16cb --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExpandRelativePackageObject.scala @@ -0,0 +1,10 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import PackageObject.a + +object ExpandRelativePackageObject diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExplicitlyImportedImplicits.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExplicitlyImportedImplicits.scala new file mode 100644 index 000000000..774d24275 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/ExplicitlyImportedImplicits.scala @@ -0,0 +1,18 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupExplicitlyImportedImplicitsSeparately = true + */ +package test.organizeImports + +import scala.concurrent.ExecutionContext +import test.organizeImports.Implicits.b._ +import ExecutionContext.Implicits.global +import test.organizeImports.Implicits.a.{nonImplicit, intImplicit, stringImplicit} + +object ExplicitlyImportedImplicits { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetDefault.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetDefault.scala new file mode 100644 index 000000000..f473347cb --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetDefault.scala @@ -0,0 +1,22 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + preset = DEFAULT + groupedImports = Merge + removeUnused = false +} + */ +package test.organizeImports + +import scala.collection.mutable +import scala.util.Try +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import test.organizeImports.PresetDefault.a + +object PresetDefault { + val a: Any = ??? +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetIntelliJ_2020_3.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetIntelliJ_2020_3.scala new file mode 100644 index 000000000..1415bc93a --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/PresetIntelliJ_2020_3.scala @@ -0,0 +1,22 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + preset = INTELLIJ_2020_3 + groupedImports = Explode + removeUnused = false +} + */ +package test.organizeImports + +import scala.collection.mutable +import scala.util.Try +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import test.organizeImports.PresetIntelliJ_2020_3.a + +object PresetIntelliJ_2020_3 { + val a: Any = ??? +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnused.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnused.scala new file mode 100644 index 000000000..43040f9fe --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnused.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.{v1, v2} +import test.organizeImports.UnusedImports.b.v3 +import test.organizeImports.UnusedImports.c.{v5 => w1, v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => unused, _} + +object RemoveUnused { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedDisabled.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedDisabled.scala new file mode 100644 index 000000000..d839dd34e --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedDisabled.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = false +} + */ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.{v1, v2} +import test.organizeImports.UnusedImports.b.v3 +import test.organizeImports.UnusedImports.c.{v5 => w1, v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => unused, _} + +object RemoveUnusedDisabled { + import test.organizeImports.UnusedImports.e.v9 + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedLocal.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedLocal.scala new file mode 100644 index 000000000..336d0cbb6 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedLocal.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package test.organizeImports + +object RemoveUnusedLocal { + import UnusedImports._ + import a.{v1, v2} + import b.v3 + import c.{v5 => w1, v6 => w2} + import d.{v7 => unused, _} + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedMixed.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedMixed.scala new file mode 100644 index 000000000..030911ed8 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedMixed.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.{v1, v2} +import test.organizeImports.UnusedImports.b.v3 +import test.organizeImports.UnusedImports.c.{v5 => w1, v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => unused, _} + +object RemoveUnusedMixed { + import test.organizeImports.UnusedImports.e.v9 + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedRelative.scala b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedRelative.scala new file mode 100644 index 000000000..68926796f --- /dev/null +++ b/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedRelative.scala @@ -0,0 +1,26 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + expandRelative = true + groupedImports = Explode + groups = ["re:javax?\\.", "scala.", "*"] + removeUnused = true +} + */ +package test.organizeImports + +import test.organizeImports.UnusedImports.a +import test.organizeImports.UnusedImports.b +import test.organizeImports.UnusedImports.c +import test.organizeImports.UnusedImports.d + +import a.{v1, v2} +import b.v3 +import c.{v5 => w1, v6 => w2} +import d.{v7 => unused, _} + +object RemoveUnusedRelative { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala new file mode 100644 index 000000000..0d6a6fe0f --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.{A, B => B1, C => _, given D, _} + +object CoalesceImporteesGivensAndNames diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala new file mode 100644 index 000000000..82e3aa7a8 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package test.organizeImports + +import test.organizeImports.Givens.{A, B, C => C1} + +object CoalesceImporteesNoGivens diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala new file mode 100644 index 000000000..e91fedb27 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package test.organizeImports + +import test.organizeImports.Givens.{A => A1, B => _, _} + +object CoalesceImporteesNoGivensNoNames diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala new file mode 100644 index 000000000..9e374ccff --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 2 + removeUnused = false +} + */ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.{A => A1, given B, given C} + +object CoalesceImporteesNoNames diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala new file mode 100644 index 000000000..9005f1932 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.{given A, given B} +import test.organizeImports.Givens.given A +import test.organizeImports.Givens.given C +import test.organizeImports.Givens.given C + +object DeduplicateGivenImportees diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandGiven.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandGiven.scala new file mode 100644 index 000000000..f23f6848d --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandGiven.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import test.organizeImports.GivenImports.Beta +import test.organizeImports.GivenImports.Alpha +import test.organizeImports.GivenImports.{given Beta, given Alpha} +import scala.util.Either + +object ExpandGiven diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala new file mode 100644 index 000000000..457db872f --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import test.organizeImports.GivenImports.Beta +import test.organizeImports.GivenImports.Alpha +import test.organizeImports.GivenImports.given Alpha +import test.organizeImports.GivenImports.{alpha => _} +import test.organizeImports.GivenImports.{beta => _, given} +import scala.util.Either + +object ExpandUnimportGiven diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala new file mode 100644 index 000000000..f34b71897 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.GivenImports._ +import test.organizeImports.GivenImports.{alpha => _, given} +import test.organizeImports.GivenImports.{given Beta} +import test.organizeImports.GivenImports.{gamma => _, given} +import test.organizeImports.GivenImports.{given Zeta} + +import test.organizeImports.GivenImports2.{alpha => _} +import test.organizeImports.GivenImports2.{beta => _} +import test.organizeImports.GivenImports2.{given Gamma} +import test.organizeImports.GivenImports2.{given Zeta} + +object GroupedGivenImportsMergeUnimports diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala new file mode 100644 index 000000000..bb0632fcc --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala @@ -0,0 +1,23 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = AggressiveMerge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Wildcard1._ +import test.organizeImports.MergeImports.Wildcard1.{a => _, _} +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard1.{c => _, _} +import test.organizeImports.MergeImports.Wildcard1.d + +import test.organizeImports.GivenImports._ +import test.organizeImports.GivenImports.{Alpha, Beta, Zeta} +import test.organizeImports.GivenImports.{given Alpha, given Beta, given Zeta} +import test.organizeImports.GivenImports.given + +import test.organizeImports.MergeImports.Wildcard2._ +import test.organizeImports.MergeImports.Wildcard2.{a, b} + + +object GroupedImportsAggressiveMergeGivenAll diff --git a/scalafix-tests/input/src/main/scala-3/test/organizeImports/MergeGiven.scala b/scalafix-tests/input/src/main/scala-3/test/organizeImports/MergeGiven.scala new file mode 100644 index 000000000..a7d628d91 --- /dev/null +++ b/scalafix-tests/input/src/main/scala-3/test/organizeImports/MergeGiven.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.GivenImports.Beta +import test.organizeImports.GivenImports.Alpha +import test.organizeImports.GivenImports.{given Beta} +import test.organizeImports.GivenImports.{given Alpha} +import scala.util.Either + +object MergeGiven diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/AlreadyOrganized.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/AlreadyOrganized.scala new file mode 100644 index 000000000..392a2d280 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/AlreadyOrganized.scala @@ -0,0 +1,15 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import scala.collection.mutable.{ + ArrayBuffer, + Map, + Queue, + Set +} + +object AlreadyOrganized diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/BlankLinesManual.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/BlankLinesManual.scala new file mode 100644 index 000000000..593975a7e --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/BlankLinesManual.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] + +OrganizeImports { + blankLines = Manual + groups = [ + "re:javax?\\." + "scala." + "---" + "*" + ] + removeUnused = false +} + */ +package test.organizeImports + +import scala.collection.mutable +import java.util.Arrays +import sun.misc.Unsafe + +object BlankLinesManual diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/CoalesceImportees.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/CoalesceImportees.scala new file mode 100644 index 000000000..6a35ea974 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/CoalesceImportees.scala @@ -0,0 +1,16 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + groupedImports = Keep + coalesceToWildcardImportThreshold = 3 + removeUnused = false +} + */ +package test.organizeImports + +import scala.collection.immutable.{Seq, Map, Vector} +import scala.collection.mutable.{Buffer, Seq, Map, Set} +import scala.concurrent.{Await, Channel => Ch, Future, Promise, duration} +import scala.util.{Either, Random => _, Try, Success, Failure} + +object CoalesceImportees diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala new file mode 100644 index 000000000..9f8255a67 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala @@ -0,0 +1,10 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.{Map} +import scala.collection.{Set => ImmutableSet} + +object CurlyBracedSingleImportee diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/DeduplicateImportees.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/DeduplicateImportees.scala new file mode 100644 index 000000000..4150b056b --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/DeduplicateImportees.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.immutable.Vector +import scala.collection.immutable.{Map => Dict} +import scala.collection.immutable.{Set => _, Map => Dict, _} +import scala.collection.immutable.{Map => Dict, Vector} + +object DeduplicateImportees diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelative.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelative.scala new file mode 100644 index 000000000..8b900b7a1 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelative.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import scala.util +import util.control +import control.NonFatal + +object ExpandRelative diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala new file mode 100644 index 000000000..cfa3dd503 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala @@ -0,0 +1,22 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + expandRelative = true + groupedImports = Explode + removeUnused = false +} + */ +import P._ +import Q.x +import Q._ + +object P { + object x +} + +object Q { + object x + object y +} + +object ExpandRelativeEmptyPackage diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala new file mode 100644 index 000000000..7fa9ce009 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import scala.util +import java.time.Clock +import util.control +import javax.management.JMX +import control.NonFatal + +object ExpandRelativeMultiGroups diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala new file mode 100644 index 000000000..834cea545 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import QuotedIdent.`a.b` +import QuotedIdent.`macro` +import `a.b`.c +import `a.b`.`{ d }` + +object ExpandRelativeQuotedIdent diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..0b096c22c --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import _root_.scala.collection.mutable.ArrayBuffer +import _root_.scala.util +import util.control +import control.NonFatal + +object ExpandRelativeRootPackage diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala new file mode 100644 index 000000000..179b9c68a --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Explode + */ + +package test.organizeImports + +import test.organizeImports.ExplodeImports.FormatPreserving.g1.{ a, b } +import test.organizeImports.ExplodeImports.FormatPreserving.g2.{ c => C, _ } + +object ExplodeImportsFormatPreserving diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GlobalImportsOnly.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GlobalImportsOnly.scala new file mode 100644 index 000000000..76d134787 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GlobalImportsOnly.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.mutable +import scala.concurrent.duration.Duration +import java.time.Clock +import java.sql.DriverManager + +object GlobalImportsOnly + +// These imports should not be organized +import java.time.Duration, java.sql.Connection + +object Foo diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala new file mode 100644 index 000000000..e31c70827 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = AggressiveMerge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Wildcard1._ +import test.organizeImports.MergeImports.Wildcard1.{a => _, _} +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard1.{c => _, _} +import test.organizeImports.MergeImports.Wildcard1.d + +import test.organizeImports.MergeImports.Wildcard2._ +import test.organizeImports.MergeImports.Wildcard2.{a, b} + +object GroupedImportsAggressiveMergeWildcard diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplode.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplode.scala new file mode 100644 index 000000000..059af75c9 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplode.scala @@ -0,0 +1,9 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + +object GroupedImportsExplode diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala new file mode 100644 index 000000000..9d438645d --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala @@ -0,0 +1,10 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.immutable._ +import scala.collection.mutable.{Map, Seq => S, Buffer => _, _} + +object GroupedImportsExplodeMixed diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala new file mode 100644 index 000000000..acd497cea --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala @@ -0,0 +1,9 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +import scala.collection.{Seq => _, _} + +object GroupedImportExplodeUnimport diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsKeep.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsKeep.scala new file mode 100644 index 000000000..7ba18bd9e --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsKeep.scala @@ -0,0 +1,11 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Keep + */ +package test.organizeImports + +import scala.collection.mutable.{ArrayBuffer, Buffer} +import scala.collection.mutable.StringBuilder + +object GroupedImportsKeep diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMerge.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMerge.scala new file mode 100644 index 000000000..21761dfa6 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMerge.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder +import scala.collection.mutable.ArrayBuffer + +object GroupedImportsMerge diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala new file mode 100644 index 000000000..15fe853fd --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala @@ -0,0 +1,15 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Dedup.a +import test.organizeImports.MergeImports.Dedup.a +import test.organizeImports.MergeImports.Dedup.{b => b1} +import test.organizeImports.MergeImports.Dedup.{b => b1} +import test.organizeImports.MergeImports.Dedup.{c => _} +import test.organizeImports.MergeImports.Dedup.{c => _} + +object GroupedImportsMergeDedup diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala new file mode 100644 index 000000000..3a4c2cc60 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala @@ -0,0 +1,19 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Rename1.{a => A} +import test.organizeImports.MergeImports.Rename1.{b => B} +import test.organizeImports.MergeImports.Rename1.c +import test.organizeImports.MergeImports.Rename1.d + +import test.organizeImports.MergeImports.Rename2.a +import test.organizeImports.MergeImports.Rename2.{a => A} +import test.organizeImports.MergeImports.Rename2.b +import test.organizeImports.MergeImports.Rename2.{b => B} +import test.organizeImports.MergeImports.Rename2.c + +object GroupedImportsMergeRenames diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala new file mode 100644 index 000000000..511213668 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala @@ -0,0 +1,18 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Unimport1.{a => _, _} +import test.organizeImports.MergeImports.Unimport1.{b => B} +import test.organizeImports.MergeImports.Unimport1.{c => _, _} +import test.organizeImports.MergeImports.Unimport1.d + +import test.organizeImports.MergeImports.Unimport2.{a => _} +import test.organizeImports.MergeImports.Unimport2.{b => _} +import test.organizeImports.MergeImports.Unimport2.{c => C} +import test.organizeImports.MergeImports.Unimport2.d + +object GroupedImportsMergeUnimports diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala new file mode 100644 index 000000000..b81148506 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ +package test.organizeImports + +import test.organizeImports.MergeImports.Wildcard1._ +import test.organizeImports.MergeImports.Wildcard1.{a => _, _} +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard1.{c => _, _} +import test.organizeImports.MergeImports.Wildcard1.d + +import test.organizeImports.MergeImports.Wildcard2._ +import test.organizeImports.MergeImports.Wildcard2.{a, b} + +object GroupedImportsMergeWildcard diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/Groups.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/Groups.scala new file mode 100644 index 000000000..4f0a8097a --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/Groups.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ +package test.organizeImports + +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl + +object Groups diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/GroupsLongestMatch.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupsLongestMatch.scala new file mode 100644 index 000000000..5b7182b60 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/GroupsLongestMatch.scala @@ -0,0 +1,16 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["re:javax?\\.", "scala.", "scala.util.", "*"] + */ +package test.organizeImports + +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl +import scala.util.control.NonFatal +import scala.util.Random + +object GroupsLongestMatch diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala new file mode 100644 index 000000000..19a626366 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala @@ -0,0 +1,14 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Keep + */ +package test.organizeImports + +import scala.collection.immutable.{ + Map, + Seq +} +import scala.collection.immutable.{Vector, IntMap} + +object ImportsOrderAsciiPreformatted diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderKeep.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderKeep.scala new file mode 100644 index 000000000..7e08de4d1 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderKeep.scala @@ -0,0 +1,20 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = Keep + importsOrder = Keep +} + */ +package test.organizeImports + +import scala.concurrent.ExecutionContext.Implicits._ +import test.organizeImports.QuotedIdent.`a.b`.`{ d }`.e +import scala.concurrent.duration +import test.organizeImports.QuotedIdent._ +import scala.concurrent._ +import test.organizeImports.QuotedIdent.`a.b`.{c => _, _} +import scala.concurrent.{Promise, Future} + +object ImportsOrderKeep diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala new file mode 100644 index 000000000..ee3b2db00 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala @@ -0,0 +1,21 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports { + groupedImports = Keep + importSelectorsOrder = Keep + importsOrder = SymbolsFirst +} + */ +package test.organizeImports + +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent._ +import scala.concurrent.duration +import scala.concurrent.{Promise, Future} + +import test.organizeImports.QuotedIdent.`a.b`.`{ d }`.e +import test.organizeImports.QuotedIdent.`a.b`.{c => _, _} +import test.organizeImports.QuotedIdent._ + +object ImportsOrderSymbolsFirst diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala new file mode 100644 index 000000000..686e3358f --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala @@ -0,0 +1,17 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports { + groupedImports = Keep + importsOrder = SymbolsFirst +} + */ +package test.organizeImports + +import scala.collection.immutable.{ + Map, + Seq +} +import scala.collection.immutable.{Vector, IntMap} + +object ImportsOrderSymbolsFirstPreformatted diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/Inheritance.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/Inheritance.scala new file mode 100644 index 000000000..b83a18e2c --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/Inheritance.scala @@ -0,0 +1,10 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.expandRelative = true + */ +package test.organizeImports + +import SomeObject.field.any + +object Inheritance diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala new file mode 100644 index 000000000..9be512c10 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Merge + */ + +package test.organizeImports + +import test.organizeImports.MergeImports.FormatPreserving.g1.{ a, b } +import test.organizeImports.MergeImports.FormatPreserving.g2._ +import test.organizeImports.MergeImports.FormatPreserving.g2.{ d => D } + +object MergeImportsFormatPreserving diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/NoImports.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/NoImports.scala new file mode 100644 index 000000000..b966bbbea --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/NoImports.scala @@ -0,0 +1,7 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports + +object NoImports \ No newline at end of file diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala new file mode 100644 index 000000000..d8cca9bdf --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala @@ -0,0 +1,12 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl + +object OrganizeImportsRootPackage diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/RelativeImports.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RelativeImports.scala new file mode 100644 index 000000000..7e3b0084f --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/RelativeImports.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["scala.", "*"] + */ +package test.organizeImports + +import scala.util +import sun.misc.Unsafe +import util.control +import control.NonFatal + +object RelativeImports diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala new file mode 100644 index 000000000..59e21b6d6 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala @@ -0,0 +1,10 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groupedImports = Keep + */ +package test.organizeImports + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImportSelectorsAscii diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala new file mode 100644 index 000000000..ddd5a19f1 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala @@ -0,0 +1,13 @@ +/* +rules = OrganizeImports +OrganizeImports { + importSelectorsOrder = Keep + groupedImports = Keep + removeUnused = false +} + */ +package test.organizeImports + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImportSelectorsKeep diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala new file mode 100644 index 000000000..0db2501bd --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala @@ -0,0 +1,13 @@ +/* +rules = [OrganizeImports] +OrganizeImports { + importSelectorsOrder = SymbolsFirst + groupedImports = Keep + removeUnused = false +} + */ +package test.organizeImports + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImportSelectorsSymbolsFirst diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/Suppression.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/Suppression.scala new file mode 100644 index 000000000..deb9f5d85 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/Suppression.scala @@ -0,0 +1,16 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ +package test.organizeImports + +// scalafix:off +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl +// scalafix:on + +object Suppression diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackage.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackage.scala new file mode 100644 index 000000000..661c26d1e --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackage.scala @@ -0,0 +1,15 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false +OrganizeImports.groups = ["re:javax?\\.", "*", "scala."] + */ +package test.organizeImports +package nested + +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl + +object OrganizeImportsNestedPackage diff --git a/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala new file mode 100644 index 000000000..600c75737 --- /dev/null +++ b/scalafix-tests/input/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala @@ -0,0 +1,15 @@ +/* +rules = [OrganizeImports] +OrganizeImports.removeUnused = false + */ +package test.organizeImports { + package nested { + import java.time.Clock + import scala.collection.JavaConverters._ + import sun.misc.Unsafe + import scala.concurrent.ExecutionContext + import javax.net.ssl + + object NestedPackageWithBraces + } +} diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala new file mode 100644 index 000000000..e9530cea6 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesGivensAndNames.scala @@ -0,0 +1,6 @@ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.{B => B1, C => _, _, given} + +object CoalesceImporteesGivensAndNames diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala new file mode 100644 index 000000000..b5bbceec9 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivens.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import test.organizeImports.Givens.{C => C1, _} + +object CoalesceImporteesNoGivens diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala new file mode 100644 index 000000000..7d4401969 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoGivensNoNames.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import test.organizeImports.Givens.{A => A1, B => _, _} + +object CoalesceImporteesNoGivensNoNames diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala new file mode 100644 index 000000000..c6a2d2940 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/CoalesceImporteesNoNames.scala @@ -0,0 +1,6 @@ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.{A => A1, given} + +object CoalesceImporteesNoNames diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala new file mode 100644 index 000000000..67b6cb5c4 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/DeduplicateGivenImportees.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import test.organizeImports.Givens._ +import test.organizeImports.Givens.given A +import test.organizeImports.Givens.given B +import test.organizeImports.Givens.given C + +object DeduplicateGivenImportees diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandGiven.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandGiven.scala new file mode 100644 index 000000000..e5e028ac8 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandGiven.scala @@ -0,0 +1,10 @@ +package test.organizeImports + +import test.organizeImports.GivenImports.Alpha +import test.organizeImports.GivenImports.Beta +import test.organizeImports.GivenImports.given Alpha +import test.organizeImports.GivenImports.given Beta + +import scala.util.Either + +object ExpandGiven diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala new file mode 100644 index 000000000..132be5915 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/ExpandUnimportGiven.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +import test.organizeImports.GivenImports.Alpha +import test.organizeImports.GivenImports.Beta +import test.organizeImports.GivenImports.given Alpha +import test.organizeImports.GivenImports.{alpha => _} +import test.organizeImports.GivenImports.{beta => _, given} + +import scala.util.Either + +object ExpandUnimportGiven diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala new file mode 100644 index 000000000..4a1a27b26 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedGivenImportsMergeUnimports.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import test.organizeImports.GivenImports._ +import test.organizeImports.GivenImports.{gamma => _, given Beta, given Zeta, given} +import test.organizeImports.GivenImports2.{alpha => _, beta => _} +import test.organizeImports.GivenImports2.{given Gamma, given Zeta} + +object GroupedGivenImportsMergeUnimports diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala new file mode 100644 index 000000000..2c5436cc0 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/GroupedImportsAggressiveMergeGivenAll.scala @@ -0,0 +1,10 @@ +package test.organizeImports + +import test.organizeImports.GivenImports._ +import test.organizeImports.GivenImports.given +import test.organizeImports.MergeImports.Wildcard1._ +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard2._ + + +object GroupedImportsAggressiveMergeGivenAll diff --git a/scalafix-tests/output/src/main/scala-3/test/organizeImports/MergeGiven.scala b/scalafix-tests/output/src/main/scala-3/test/organizeImports/MergeGiven.scala new file mode 100644 index 000000000..b02b7e822 --- /dev/null +++ b/scalafix-tests/output/src/main/scala-3/test/organizeImports/MergeGiven.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import test.organizeImports.GivenImports.{Alpha, Beta} +import test.organizeImports.GivenImports.{given Alpha, given Beta} + +import scala.util.Either + +object MergeGiven diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/AlreadyOrganized.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/AlreadyOrganized.scala new file mode 100644 index 000000000..8c4fbc1ed --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/AlreadyOrganized.scala @@ -0,0 +1,10 @@ +package test.organizeImports + +import scala.collection.mutable.{ + ArrayBuffer, + Map, + Queue, + Set +} + +object AlreadyOrganized diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/BlankLinesManual.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/BlankLinesManual.scala new file mode 100644 index 000000000..234965884 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/BlankLinesManual.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import java.util.Arrays +import scala.collection.mutable + +import sun.misc.Unsafe + +object BlankLinesManual diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/CoalesceImportees.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/CoalesceImportees.scala new file mode 100644 index 000000000..fd90a9de4 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/CoalesceImportees.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import scala.collection.immutable.{Map, Seq, Vector} +import scala.collection.mutable._ +import scala.concurrent.{Channel => Ch, _} +import scala.util.{Random => _, _} + +object CoalesceImportees diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala new file mode 100644 index 000000000..4a2996246 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/CurlyBracedSingleImportee.scala @@ -0,0 +1,6 @@ +package test.organizeImports + +import scala.collection.Map +import scala.collection.{Set => ImmutableSet} + +object CurlyBracedSingleImportee diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/DeduplicateImportees.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/DeduplicateImportees.scala new file mode 100644 index 000000000..1264c5874 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/DeduplicateImportees.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import scala.collection.immutable.Vector +import scala.collection.immutable.{Map => Dict} +import scala.collection.immutable.{Set => _, _} + +object DeduplicateImportees diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelative.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelative.scala new file mode 100644 index 000000000..254171769 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelative.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import scala.util +import scala.util.control +import scala.util.control.NonFatal + +object ExpandRelative diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala new file mode 100644 index 000000000..9db518228 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeEmptyPackage.scala @@ -0,0 +1,14 @@ +import P._ +import Q._ +import Q.x + +object P { + object x +} + +object Q { + object x + object y +} + +object ExpandRelativeEmptyPackage diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala new file mode 100644 index 000000000..ab5f229ff --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeMultiGroups.scala @@ -0,0 +1,9 @@ +package test.organizeImports + +import java.time.Clock +import javax.management.JMX +import scala.util +import scala.util.control +import scala.util.control.NonFatal + +object ExpandRelativeMultiGroups diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativePackageObject.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativePackageObject.scala new file mode 100644 index 000000000..1a5f3013d --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativePackageObject.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import test.organizeImports.PackageObject.a + +object ExpandRelativePackageObject diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala new file mode 100644 index 000000000..6ae0b6bc4 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeQuotedIdent.scala @@ -0,0 +1,8 @@ +package test.organizeImports + +import test.organizeImports.QuotedIdent.`a.b` +import test.organizeImports.QuotedIdent.`a.b`.`{ d }` +import test.organizeImports.QuotedIdent.`a.b`.c +import test.organizeImports.QuotedIdent.`macro` + +object ExpandRelativeQuotedIdent diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala new file mode 100644 index 000000000..c8e61214f --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExpandRelativeRootPackage.scala @@ -0,0 +1,9 @@ +package test.organizeImports + +import _root_.scala.collection.mutable.ArrayBuffer +import _root_.scala.util + +import scala.util.control +import scala.util.control.NonFatal + +object ExpandRelativeRootPackage diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExplicitlyImportedImplicits.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExplicitlyImportedImplicits.scala new file mode 100644 index 000000000..5115ab564 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExplicitlyImportedImplicits.scala @@ -0,0 +1,17 @@ +package test.organizeImports + +import test.organizeImports.Implicits.a.nonImplicit +import test.organizeImports.Implicits.b._ + +import scala.concurrent.ExecutionContext + +import ExecutionContext.Implicits.global +import test.organizeImports.Implicits.a.intImplicit +import test.organizeImports.Implicits.a.stringImplicit + +object ExplicitlyImportedImplicits { + def f1()(implicit i: Int) = ??? + def f2()(implicit s: String) = ??? + f1() + f2() +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala new file mode 100644 index 000000000..fef0bb4ec --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ExplodeImportsFormatPreserving.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import test.organizeImports.ExplodeImports.FormatPreserving.g1.a +import test.organizeImports.ExplodeImports.FormatPreserving.g1.b +import test.organizeImports.ExplodeImports.FormatPreserving.g2.{ c => C, _ } + +object ExplodeImportsFormatPreserving diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GlobalImportsOnly.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GlobalImportsOnly.scala new file mode 100644 index 000000000..b89d37aba --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GlobalImportsOnly.scala @@ -0,0 +1,13 @@ +package test.organizeImports + +import java.sql.DriverManager +import java.time.Clock +import scala.collection.mutable +import scala.concurrent.duration.Duration + +object GlobalImportsOnly + +// These imports should not be organized +import java.time.Duration, java.sql.Connection + +object Foo diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala new file mode 100644 index 000000000..e184fab9b --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsAggressiveMergeWildcard.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.Wildcard1._ +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard2._ + +object GroupedImportsAggressiveMergeWildcard diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplode.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplode.scala new file mode 100644 index 000000000..b7f8052c1 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplode.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.Buffer +import scala.collection.mutable.StringBuilder + +object GroupedImportsExplode diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala new file mode 100644 index 000000000..6b851dd2d --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeMixed.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import scala.collection.immutable._ +import scala.collection.mutable.Map +import scala.collection.mutable.{Buffer => _, Seq => S, _} + +object GroupedImportsExplodeMixed diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala new file mode 100644 index 000000000..c48b63045 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsExplodeUnimport.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import scala.collection.{Seq => _, _} + +object GroupedImportExplodeUnimport diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsKeep.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsKeep.scala new file mode 100644 index 000000000..cacbb69e6 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsKeep.scala @@ -0,0 +1,6 @@ +package test.organizeImports + +import scala.collection.mutable.StringBuilder +import scala.collection.mutable.{ArrayBuffer, Buffer} + +object GroupedImportsKeep diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMerge.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMerge.scala new file mode 100644 index 000000000..102e99f99 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMerge.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import scala.collection.mutable.{ArrayBuffer, Buffer, StringBuilder} + +object GroupedImportsMerge diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala new file mode 100644 index 000000000..ce414cdc2 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeDedup.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.Dedup.{a, b => b1, c => _} + +object GroupedImportsMergeDedup diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala new file mode 100644 index 000000000..050585919 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeRenames.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.Rename1.{a => A, b => B, c, d} +import test.organizeImports.MergeImports.Rename2.{a => A, b => B, c} +import test.organizeImports.MergeImports.Rename2.{a, b} + +object GroupedImportsMergeRenames diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala new file mode 100644 index 000000000..a8c7b14b8 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeUnimports.scala @@ -0,0 +1,6 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.Unimport1.{b => B, c => _, d, _} +import test.organizeImports.MergeImports.Unimport2.{a => _, b => _, c => C, d} + +object GroupedImportsMergeUnimports diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala new file mode 100644 index 000000000..9b502ea1b --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupedImportsMergeWildcard.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.Wildcard1.{b => B} +import test.organizeImports.MergeImports.Wildcard1.{d, _} +import test.organizeImports.MergeImports.Wildcard2.{a, b, _} + +object GroupedImportsMergeWildcard diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/Groups.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/Groups.scala new file mode 100644 index 000000000..cd706d64e --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/Groups.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +import java.time.Clock +import javax.net.ssl + +import sun.misc.Unsafe + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +object Groups diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/GroupsLongestMatch.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupsLongestMatch.scala new file mode 100644 index 000000000..1ca806834 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/GroupsLongestMatch.scala @@ -0,0 +1,14 @@ +package test.organizeImports + +import java.time.Clock +import javax.net.ssl + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +import scala.util.Random +import scala.util.control.NonFatal + +import sun.misc.Unsafe + +object GroupsLongestMatch diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala new file mode 100644 index 000000000..d056bac1b --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderAsciiPreformatted.scala @@ -0,0 +1,9 @@ +package test.organizeImports + +import scala.collection.immutable.{IntMap, Vector} +import scala.collection.immutable.{ + Map, + Seq +} + +object ImportsOrderAsciiPreformatted diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderKeep.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderKeep.scala new file mode 100644 index 000000000..e742e4d35 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderKeep.scala @@ -0,0 +1,12 @@ +package test.organizeImports + +import test.organizeImports.QuotedIdent.`a.b`.`{ d }`.e +import test.organizeImports.QuotedIdent._ +import test.organizeImports.QuotedIdent.`a.b`.{c => _, _} + +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration +import scala.concurrent._ +import scala.concurrent.{Promise, Future} + +object ImportsOrderKeep diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala new file mode 100644 index 000000000..5bd3317c0 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirst.scala @@ -0,0 +1,12 @@ +package test.organizeImports + +import test.organizeImports.QuotedIdent._ +import test.organizeImports.QuotedIdent.`a.b`.{c => _, _} +import test.organizeImports.QuotedIdent.`a.b`.`{ d }`.e + +import scala.concurrent._ +import scala.concurrent.{Promise, Future} +import scala.concurrent.ExecutionContext.Implicits._ +import scala.concurrent.duration + +object ImportsOrderSymbolsFirst diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala new file mode 100644 index 000000000..416b92948 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/ImportsOrderSymbolsFirstPreformatted.scala @@ -0,0 +1,9 @@ +package test.organizeImports + +import scala.collection.immutable.{IntMap, Vector} +import scala.collection.immutable.{ + Map, + Seq +} + +object ImportsOrderSymbolsFirstPreformatted diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/Inheritance.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/Inheritance.scala new file mode 100644 index 000000000..df855c631 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/Inheritance.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import test.organizeImports.SomeObject.field.any + +object Inheritance diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala new file mode 100644 index 000000000..dee1ea2a2 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/MergeImportsFormatPreserving.scala @@ -0,0 +1,7 @@ +package test.organizeImports + +import test.organizeImports.MergeImports.FormatPreserving.g1.{ a, b } +import test.organizeImports.MergeImports.FormatPreserving.g2._ +import test.organizeImports.MergeImports.FormatPreserving.g2.{ d => D } + +object MergeImportsFormatPreserving diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/NoImports.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/NoImports.scala new file mode 100644 index 000000000..d22b896f9 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/NoImports.scala @@ -0,0 +1,3 @@ +package test.organizeImports + +object NoImports \ No newline at end of file diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala new file mode 100644 index 000000000..926b68dfc --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/OrganizeImportsRootPackage.scala @@ -0,0 +1,9 @@ +import java.time.Clock +import javax.net.ssl + +import sun.misc.Unsafe + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +object OrganizeImportsRootPackage diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/PresetDefault.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/PresetDefault.scala new file mode 100644 index 000000000..9cb2ccb5e --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/PresetDefault.scala @@ -0,0 +1,13 @@ +package test.organizeImports + +import test.organizeImports.PresetDefault.a + +import java.math.{BigDecimal, BigInteger} +import java.util.Collections.{binarySearch, emptyList} +import javax.management.MXBean +import scala.collection.mutable +import scala.util.Try + +object PresetDefault { + val a: Any = ??? +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/PresetIntelliJ_2020_3.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/PresetIntelliJ_2020_3.scala new file mode 100644 index 000000000..4ff7dcff4 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/PresetIntelliJ_2020_3.scala @@ -0,0 +1,15 @@ +package test.organizeImports + +import test.organizeImports.PresetIntelliJ_2020_3.a + +import java.math.BigDecimal +import java.math.BigInteger +import java.util.Collections.binarySearch +import java.util.Collections.emptyList +import javax.management.MXBean +import scala.collection.mutable +import scala.util.Try + +object PresetIntelliJ_2020_3 { + val a: Any = ??? +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RelativeImports.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RelativeImports.scala new file mode 100644 index 000000000..bd8bca1f3 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RelativeImports.scala @@ -0,0 +1,10 @@ +package test.organizeImports + +import scala.util + +import sun.misc.Unsafe + +import util.control +import control.NonFatal + +object RelativeImports diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnused.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnused.scala new file mode 100644 index 000000000..4ed707256 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnused.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.v1 +import test.organizeImports.UnusedImports.c.{v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => _, _} + +object RemoveUnused { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedDisabled.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedDisabled.scala new file mode 100644 index 000000000..79fc47a24 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedDisabled.scala @@ -0,0 +1,16 @@ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.v1 +import test.organizeImports.UnusedImports.a.v2 +import test.organizeImports.UnusedImports.b.v3 +import test.organizeImports.UnusedImports.c.{v5 => w1} +import test.organizeImports.UnusedImports.c.{v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => unused, _} + +object RemoveUnusedDisabled { + import test.organizeImports.UnusedImports.e.v9 + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedLocal.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedLocal.scala new file mode 100644 index 000000000..8dac4161b --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedLocal.scala @@ -0,0 +1,12 @@ +package test.organizeImports + +object RemoveUnusedLocal { + import UnusedImports._ + import a.v1 + import c.{v6 => w2} + import d.{v7 => _, _} + + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedMixed.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedMixed.scala new file mode 100644 index 000000000..e7e339a91 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedMixed.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +import test.organizeImports.UnusedImports.a.v1 +import test.organizeImports.UnusedImports.c.{v6 => w2} +import test.organizeImports.UnusedImports.d.{v7 => _, _} + +object RemoveUnusedMixed { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedRelative.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedRelative.scala new file mode 100644 index 000000000..3f873f0db --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/RemoveUnusedRelative.scala @@ -0,0 +1,15 @@ +package test.organizeImports + +import test.organizeImports.UnusedImports.a +import test.organizeImports.UnusedImports.a.v1 +import test.organizeImports.UnusedImports.b +import test.organizeImports.UnusedImports.c +import test.organizeImports.UnusedImports.c.{v6 => w2} +import test.organizeImports.UnusedImports.d +import test.organizeImports.UnusedImports.d.{v7 => _, _} + +object RemoveUnusedRelative { + val x1 = v1 + val x2 = w2 + val x3 = v8 +} diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala new file mode 100644 index 000000000..c2c90a608 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsAscii.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import scala.{:+, ::, Any, Predef, collection, concurrent} + +object SortImportSelectorsAscii diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala new file mode 100644 index 000000000..a2b6a6423 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsKeep.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import scala.{Any, ::, collection, :+, Predef, concurrent} + +object SortImportSelectorsKeep diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala new file mode 100644 index 000000000..980b72e7c --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/SortImportSelectorsSymbolsFirst.scala @@ -0,0 +1,5 @@ +package test.organizeImports + +import scala.{:+, ::, collection, concurrent, Any, Predef} + +object SortImportSelectorsSymbolsFirst diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/Suppression.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/Suppression.scala new file mode 100644 index 000000000..42baa7238 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/Suppression.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +// scalafix:off +import java.time.Clock +import scala.collection.JavaConverters._ +import sun.misc.Unsafe +import scala.concurrent.ExecutionContext +import javax.net.ssl +// scalafix:on + +object Suppression diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackage.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackage.scala new file mode 100644 index 000000000..aedfa20ec --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackage.scala @@ -0,0 +1,12 @@ +package test.organizeImports +package nested + +import java.time.Clock +import javax.net.ssl + +import sun.misc.Unsafe + +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +object OrganizeImportsNestedPackage diff --git a/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala b/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala new file mode 100644 index 000000000..ddc5cd4d9 --- /dev/null +++ b/scalafix-tests/output/src/main/scala/test/organizeImports/nested/NestedPackageWithBraces.scala @@ -0,0 +1,12 @@ +package test.organizeImports { + package nested { + import sun.misc.Unsafe + + import java.time.Clock + import javax.net.ssl + import scala.collection.JavaConverters._ + import scala.concurrent.ExecutionContext + + object NestedPackageWithBraces + } +} diff --git a/scalafix-tests/shared/src/main/scala-3/test/organizeImports/GivenImports.scala b/scalafix-tests/shared/src/main/scala-3/test/organizeImports/GivenImports.scala new file mode 100644 index 000000000..9a417ded7 --- /dev/null +++ b/scalafix-tests/shared/src/main/scala-3/test/organizeImports/GivenImports.scala @@ -0,0 +1,25 @@ +package test.organizeImports + +object GivenImports { + trait Alpha + trait Beta + trait Gamma + trait Delta + trait Zeta + + given alpha: Alpha = ??? + given beta: Beta = ??? + given gamma: Gamma = ??? + given delta: Delta = ??? + given zeta: Zeta = ??? +} + +object GivenImports2 { + import GivenImports.* + + given alpha: Alpha = ??? + given beta: Beta = ??? + given gamma: Gamma = ??? + given delta: Delta = ??? + given zeta: Zeta = ??? +} diff --git a/scalafix-tests/shared/src/main/scala-3/test/organizeImports/Givens.scala b/scalafix-tests/shared/src/main/scala-3/test/organizeImports/Givens.scala new file mode 100644 index 000000000..69e136edf --- /dev/null +++ b/scalafix-tests/shared/src/main/scala-3/test/organizeImports/Givens.scala @@ -0,0 +1,15 @@ +package test.organizeImports + +object Givens { + trait A + trait B + trait C + trait D + trait E + + given a: A = ??? + given b: B = ??? + given c: C = ??? + given d: D = ??? + given e: E = ??? +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/ExplodeImports.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/ExplodeImports.scala new file mode 100644 index 000000000..4a7e585d7 --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/ExplodeImports.scala @@ -0,0 +1,60 @@ +package test.organizeImports + +object ExplodeImports { + object Wildcard1 { + object a + object b + object c + object d + } + + object Wildcard2 { + object a + object b + } + + object Unimport1 { + object a + object b + object c + object d + } + + object Unimport2 { + object a + object b + object c + object d + } + + object Rename1 { + object a + object b + object c + object d + } + + object Rename2 { + object a + object b + object c + } + + object Dedup { + object a + object b + object c + } + + object FormatPreserving { + object g1 { + object a + object b + } + + object g2 { + object c + object d + } + } +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/Implicits.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/Implicits.scala new file mode 100644 index 000000000..2eed0d4fd --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/Implicits.scala @@ -0,0 +1,14 @@ +package test.organizeImports + +object Implicits { + object a { + def nonImplicit: Unit = ??? + implicit def intImplicit: Int = ??? + implicit def stringImplicit: String = ??? + } + + object b { + implicit def intImplicit: Int = ??? + implicit def stringImplicit: String = ??? + } +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/Inheritance.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/Inheritance.scala new file mode 100644 index 000000000..9ff845caf --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/Inheritance.scala @@ -0,0 +1,11 @@ +package test.organizeImports + +class SomeClass { + val any: Any = ??? +} + +trait SomeTrait { + val field = new SomeClass +} + +object SomeObject extends SomeTrait diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/MergeImports.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/MergeImports.scala new file mode 100644 index 000000000..ede6105b9 --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/MergeImports.scala @@ -0,0 +1,60 @@ +package test.organizeImports + +object MergeImports { + object Wildcard1 { + object a + object b + object c + object d + } + + object Wildcard2 { + object a + object b + } + + object Unimport1 { + object a + object b + object c + object d + } + + object Unimport2 { + object a + object b + object c + object d + } + + object Rename1 { + object a + object b + object c + object d + } + + object Rename2 { + object a + object b + object c + } + + object Dedup { + object a + object b + object c + } + + object FormatPreserving { + object g1 { + object a + object b + } + + object g2 { + object c + object d + } + } +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/QuotedIdent.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/QuotedIdent.scala new file mode 100644 index 000000000..4bfd2b578 --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/QuotedIdent.scala @@ -0,0 +1,12 @@ +package test.organizeImports + +object QuotedIdent { + object `a.b` { + object c + object `{ d }` { + object e + } + } + + object `macro` +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/UnusedImports.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/UnusedImports.scala new file mode 100644 index 000000000..ac50470bd --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/UnusedImports.scala @@ -0,0 +1,27 @@ +package test.organizeImports + +object UnusedImports { + object a { + object v1 + object v2 + } + + object b { + object v3 + object v4 + } + + object c { + object v5 + object v6 + } + + object d { + object v7 + object v8 + } + + object e { + object v9 + } +} diff --git a/scalafix-tests/shared/src/main/scala/test/organizeImports/package.scala b/scalafix-tests/shared/src/main/scala/test/organizeImports/package.scala new file mode 100644 index 000000000..c4858a722 --- /dev/null +++ b/scalafix-tests/shared/src/main/scala/test/organizeImports/package.scala @@ -0,0 +1,5 @@ +package test.organizeImports { + object PackageObject { + object a + } +} diff --git a/website/i18n/en.json b/website/i18n/en.json index 9f08d5edf..77bee14ac 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -16,6 +16,9 @@ "title": "Contributing Guide", "sidebar_label": "Guide" }, + "developers/cross-publish-custom-rules": { + "title": "Cross publish custom rules" + }, "developers/local-rules": { "title": "Local rules" }, @@ -40,6 +43,10 @@ "developers/tutorial": { "title": "Tutorial" }, + "rules/community-rules": { + "title": "Community rules", + "sidebar_label": "Community rules" + }, "rules/DisableSyntax": { "title": "DisableSyntax" }, @@ -59,6 +66,9 @@ "rules/NoValInForComprehension": { "title": "NoValInForComprehension" }, + "rules/OrganizeImports": { + "title": "OrganizeImports" + }, "rules/overview": { "title": "Built-in Rules", "sidebar_label": "Built-in rules" @@ -66,6 +76,9 @@ "rules/ProcedureSyntax": { "title": "ProcedureSyntax" }, + "rules/RedundantSyntax": { + "title": "RedundantSyntax" + }, "rules/RemoveUnused": { "title": "RemoveUnused" }, diff --git a/website/sidebars.json b/website/sidebars.json index 5eb350b8a..90d84b4ac 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -8,6 +8,7 @@ "rules/LeakingImplicitClassVal", "rules/NoAutoTupling", "rules/NoValInForComprehension", + "rules/OrganizeImports", "rules/ProcedureSyntax", "rules/RedundantSyntax", "rules/RemoveUnused",