Skip to content

Commit 11fc77d

Browse files
committed
Support OrganizeImports.removeUnused in Scala 3
Once this PR scala/scala3#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.
1 parent f9672da commit 11fc77d

File tree

1 file changed

+48
-28
lines changed

1 file changed

+48
-28
lines changed

scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package scalafix.internal.rule
33
import scala.annotation.tailrec
44
import scala.collection.mutable
55
import scala.collection.mutable.ArrayBuffer
6+
import scala.util.Failure
7+
import scala.util.Success
68
import scala.util.Try
79

810
import scala.meta.Import
@@ -28,6 +30,7 @@ import metaconfig.ConfEncoder
2830
import metaconfig.ConfOps
2931
import metaconfig.Configured
3032
import metaconfig.internal.ConfGet
33+
import scalafix.internal.config.ScalaVersion
3134
import scalafix.internal.rule.ImportMatcher.*
3235
import scalafix.internal.rule.ImportMatcher.---
3336
import scalafix.internal.rule.ImportMatcher.parse
@@ -72,7 +75,8 @@ class OrganizeImports(config: OrganizeImportsConfig)
7275

7376
override def fix(implicit doc: SemanticDocument): Patch = {
7477
unusedImporteePositions ++= doc.diagnostics.collect {
75-
case d if d.message == "Unused import" => d.position
78+
// Scala2 says "Unused import" while Scala3 says "unused import"
79+
case d if d.message.toLowerCase == "unused import" => d.position
7680
}
7781

7882
val (globalImports, localImports) = collectImports(doc.tree)
@@ -88,8 +92,16 @@ class OrganizeImports(config: OrganizeImportsConfig)
8892
diagnostics.map(Patch.lint).asPatch + globalImportsPatch + localImportsPatch
8993
}
9094

91-
private def isUnused(importee: Importee): Boolean =
92-
unusedImporteePositions contains positionOf(importee)
95+
private def isUnused(importee: Importee): Boolean = {
96+
// positionOf returns the position of `bar` for `import foo.{bar => baz}`
97+
// this position matches with the diagnostics from Scala2,
98+
// but Scala3 diagnostics has a position for `bar => baz`, which doesn't match with
99+
// the return value of `positionOf`.
100+
// We could adjust the behavior of `positionOf` based on Scala version,
101+
// but this implementation just checking the unusedImporteePosition includes the importee pos, for simplicity.
102+
val pos = positionOf(importee)
103+
unusedImporteePositions.exists(unused => unused.start <= pos.start && pos.end <= unused.end)
104+
}
93105

94106
private def organizeGlobalImports(
95107
imports: Seq[Import]
@@ -707,32 +719,40 @@ object OrganizeImports {
707719
scalacOptions: List[String],
708720
scalaVersion: String
709721
): Configured[Rule] = {
710-
val hasCompilerSupport = scalaVersion.startsWith("2")
711-
712-
val hasWarnUnused = hasCompilerSupport && {
713-
val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused")
714-
val warnUnusedString = Set("-Xlint", "-Xlint:unused")
715-
scalacOptions exists { option =>
716-
(warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option)
717-
}
718-
}
722+
ScalaVersion.from(scalaVersion).map { v =>
723+
v.isScala2 || (
724+
v.isScala3 &&
725+
v.minor.getOrElse(0) >= 3 &&
726+
v.patch.getOrElse(0) >= 1
727+
)
728+
} match {
729+
case Failure(exception) => Configured.error(exception.getMessage())
730+
case Success(hasCompilerSupport) =>
731+
val hasWarnUnused = hasCompilerSupport && {
732+
val warnUnusedPrefix = Set("-Wunused", "-Ywarn-unused")
733+
val warnUnusedString = Set("-Xlint", "-Xlint:unused")
734+
scalacOptions exists { option =>
735+
(warnUnusedPrefix exists option.startsWith) || (warnUnusedString contains option)
736+
}
737+
}
719738

720-
if (!conf.removeUnused || hasWarnUnused)
721-
Configured.ok(new OrganizeImports(conf))
722-
else if (hasCompilerSupport)
723-
Configured.error(
724-
"The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with"
725-
+ " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your"
726-
+ " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11"
727-
+ " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)."
728-
)
729-
else
730-
Configured.error(
731-
"\"OrganizeImports.removeUnused\" is not supported on Scala 3 as the compiler is"
732-
+ " not providing enough information. Run the rule with"
733-
+ " \"OrganizeImports.removeUnused\" set to false to organize imports while keeping"
734-
+ " potentially unused imports."
735-
)
739+
if (!conf.removeUnused || hasWarnUnused)
740+
Configured.ok(new OrganizeImports(conf))
741+
else if (hasCompilerSupport)
742+
Configured.error(
743+
"The Scala compiler option \"-Ywarn-unused\" is required to use OrganizeImports with"
744+
+ " \"OrganizeImports.removeUnused\" set to true. To fix this problem, update your"
745+
+ " build to use at least one Scala compiler option like -Ywarn-unused-import (2.11"
746+
+ " only), -Ywarn-unused, -Xlint:unused (2.12.2 or above) or -Wunused (2.13 only)."
747+
)
748+
else
749+
Configured.error(
750+
"\"OrganizeImports.removeUnused\"" + s"is not supported on $scalaVersion as the compiler is"
751+
+ " not providing enough information. Please upgrade Scala 3 version to 3.3.1 or greater."
752+
+ " Otherwise, run the rule with \"OrganizeImports.removeUnused\" set to false"
753+
+ " to organize imports while keeping potentially unused imports."
754+
)
755+
}
736756
}
737757

738758
private def buildImportMatchers(

0 commit comments

Comments
 (0)