From 02033a43bfb4e0f9803b117800f6e4a88cb5f145 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Fri, 30 Jun 2023 14:51:55 +0900 Subject: [PATCH] Support OrganizeImports.removeUnused in Scala 3 Once this PR https://github.com/lampepfl/dotty/pull/17835 has merged and released, scalafix-organize-imports should be able to run OrganizeImports.removeUnused based on the diagnostics information in SemanticDB emit from Scala3 compiler. In order to make OrganizeImports rule to work with Scala 3, this commit added a few adjustments to the rule. --- docs/rules/OrganizeImports.md | 19 ++++------ .../internal/rule/OrganizeImports.scala | 35 +++++++++++++------ .../test/organizeImports/RemoveUnused.scala | 0 .../RemoveUnusedDisabled.scala | 0 .../organizeImports/RemoveUnusedLocal.scala | 0 .../organizeImports/RemoveUnusedMixed.scala | 0 .../RemoveUnusedRelative.scala | 0 7 files changed, 30 insertions(+), 24 deletions(-) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/organizeImports/RemoveUnused.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/organizeImports/RemoveUnusedDisabled.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/organizeImports/RemoveUnusedLocal.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/organizeImports/RemoveUnusedMixed.scala (100%) rename scalafix-tests/input/src/main/{scala-2 => scala}/test/organizeImports/RemoveUnusedRelative.scala (100%) 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 ce8023416..0e1255993 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala @@ -96,7 +96,8 @@ class OrganizeImports( private def fixWithImplicitDialect(implicit doc: SemanticDocument): Patch = { unusedImporteePositions ++= doc.diagnostics.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 } val (globalImports, localImports) = collectImports(doc.tree) @@ -112,8 +113,18 @@ class OrganizeImports( diagnostics.map(Patch.lint).asPatch + globalImportsPatch + localImportsPatch } - private def isUnused(importee: Importee): Boolean = - unusedImporteePositions contains positionOf(importee) + private def isUnused(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) + unusedImporteePositions.exists(unused => + unused.start <= pos.start && pos.end <= unused.end + ) + } private def organizeGlobalImports( imports: Seq[Import] @@ -839,7 +850,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") @@ -887,17 +900,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." ) } 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