diff --git a/docs/rules/OrganizeImports.md b/docs/rules/OrganizeImports.md index 9ccc05d39..95dc4cb86 100644 --- a/docs/rules/OrganizeImports.md +++ b/docs/rules/OrganizeImports.md @@ -52,15 +52,11 @@ do not rewrite import statements in ways that conflict with 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 +1. Usage of [deprecated package objects](http://dotty.epfl.ch/docs/reference/dropped-features/package-objects.html) may result in incorrect imports. -3. The +2. The [`groupExplicitlyImportedImplicitsSeparately`](OrganizeImports.md#groupexplicitlyimportedimplicitsseparately) option has no effect. @@ -1279,12 +1275,9 @@ Remove unused imports. > 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. +> The `removeUnused` option is not supported for source files compiled with +> early versions of Scala 3 as these do not export SemanticDB diagnostics for +> unused imports. You must compile with Scala 3.4.0 or later to use it. ### Value type @@ -1299,7 +1292,7 @@ Boolean ```conf OrganizeImports { groups = ["javax?\\.", "scala.", "*"] - removeUnused = true // not supported in Scala 3 + removeUnused = true } ``` diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala index bacccd24d..6a0aff93f 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala @@ -863,7 +863,9 @@ object OrganizeImports { scalacOptions: List[String], scalaVersion: String ): Configured[Rule] = { - val hasCompilerSupport = scalaVersion.startsWith("2") + val hasCompilerSupport = + Seq("3.0", "3.1", "3.2", "3.3") + .forall(v => !scalaVersion.startsWith(v)) val hasWarnUnused = hasCompilerSupport && { val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused") @@ -911,17 +913,17 @@ object OrganizeImports { ) else if (hasCompilerSupport) Configured.error( - "The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with" + "A Scala compiler option 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)." + + " build to add `-Ywarn-unused` (2.12), `-Wunused:imports` (2.13), or" + + " `-Wunused:import` (3.4+)." ) 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." + "\"OrganizeImports.removeUnused\"" + s"is not supported on $scalaVersion as the compiler is" + + " not providing enough information. Please upgrade the Scala compiler to 3.4.0 or greater." + + " Otherwise, run the rule with \"OrganizeImports.removeUnused\" set to false" + + " to organize imports while keeping potentially unused imports." ) } @@ -1105,12 +1107,24 @@ object OrganizeImports { class UnusedImporteePositions(implicit doc: SemanticDocument) { private val positions: Seq[Position] = doc.diagnostics.toSeq.collect { - case d if d.message == "Unused import" => d.position + // Scala2 says "Unused import" while Scala3 says "unused import" + case d if d.message.toLowerCase == "unused import" => d.position } /** Returns true if the importee was marked as unused by the compiler */ - def apply(importee: Importee): Boolean = - positions contains positionOf(importee) + def apply(importee: Importee): Boolean = { + // positionOf returns the position of `bar` for `import foo.{bar => baz}` + // this position matches with the diagnostics from Scala2, but Scala3 + // diagnostics has a position for `bar => baz`, which doesn't match + // with the return value of `positionOf`. + // We could adjust the behavior of `positionOf` based on Scala version, + // but this implementation just checking the unusedImporteePosition + // includes the importee pos, for simplicity. + val pos = positionOf(importee) + positions.exists { unused => + unused.start <= pos.start && pos.end <= unused.end + } + } } implicit private class SymbolExtension(symbol: Symbol) { diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnused.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnused.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnused.scala rename to scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnused.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedDisabled.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedDisabled.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedDisabled.scala rename to scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedDisabled.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedLocal.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedLocal.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedLocal.scala rename to scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedLocal.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedMixed.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedMixed.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedMixed.scala rename to scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedMixed.scala diff --git a/scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedRelative.scala b/scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedRelative.scala similarity index 100% rename from scalafix-tests/input/src/main/scala-2/test/organizeImports/RemoveUnusedRelative.scala rename to scalafix-tests/input/src/main/scala/test/organizeImports/RemoveUnusedRelative.scala