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 9abbb30158..d96c291d83 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/OrganizeImports.scala @@ -3,6 +3,8 @@ package scalafix.internal.rule import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer +import scala.util.Failure +import scala.util.Success import scala.util.Try import scala.meta.Import @@ -28,6 +30,7 @@ import metaconfig.ConfEncoder import metaconfig.ConfOps import metaconfig.Configured import metaconfig.internal.ConfGet +import scalafix.internal.config.ScalaVersion import scalafix.internal.rule.ImportMatcher.* import scalafix.internal.rule.ImportMatcher.--- import scalafix.internal.rule.ImportMatcher.parse @@ -72,7 +75,8 @@ class OrganizeImports(config: OrganizeImportsConfig) override def fix(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) @@ -88,8 +92,16 @@ class OrganizeImports(config: OrganizeImportsConfig) 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] @@ -707,32 +719,40 @@ object OrganizeImports { 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) - } - } + ScalaVersion.from(scalaVersion).map { v => + v.isScala2 || ( + v.isScala3 && + v.minor.getOrElse(0) >= 3 && + v.patch.getOrElse(0) >= 1 + ) + } match { + case Failure(exception) => Configured.error(exception.getMessage()) + case Success(hasCompilerSupport) => + 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." - ) + 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\"" + s"is not supported on $scalaVersion as the compiler is" + + " not providing enough information. Please upgrade Scala 3 version to 3.3.1 or greater." + + " Otherwise, run the rule with \"OrganizeImports.removeUnused\" set to false" + + " to organize imports while keeping potentially unused imports." + ) + } } private def buildImportMatchers(