diff --git a/scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnused.scala b/scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnused.scala index 48ce8cad5..2583ddc6c 100644 --- a/scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnused.scala +++ b/scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnused.scala @@ -132,7 +132,13 @@ class RemoveUnused(config: RemoveUnusedConfig) // 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 isUnusedImport(importPosition(i)) => + case i + if isUnusedImport + .exists(unused => + unused.start <= importPosition(i).start && importPosition( + i + ).end <= unused.end + ) => Patch.removeImportee(i).atomic }.asPatch case i: Defn if isUnusedTerm(i.pos) => diff --git a/scalafix-tests/unit/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala b/scalafix-tests/unit/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala index dc1cc63a4..063dccaef 100644 --- a/scalafix-tests/unit/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala +++ b/scalafix-tests/unit/src/test/scala/scalafix/tests/interfaces/ScalafixArgumentsSuite.scala @@ -433,6 +433,73 @@ class ScalafixArgumentsSuite extends AnyFunSuite with DiffAssertions { assert(obtainedError == ScalafixFileEvaluationError.ParseError) } + + test("Scala 3 style wildcard import", SkipWindows) { + // https://github.com/scalacenter/scalafix/issues/1663 + + if (scala.util.Properties.versionNumberString.startsWith("2.11")) { + cancel() + } + + val cwd = StringFS + .string2dir( + """|/src/Main.scala + |import scala.collection.mutable + |import scala.util._ + |import scala.io.* + | + |object Main + """.stripMargin, + charset + ) + .toNIO + val d = cwd.resolve("out") + val target = cwd.resolve("target") + val src = cwd.resolve("src") + Files.createDirectories(d) + val main = src.resolve("Main.scala") + + val scalacOptions = Array[String]( + "-Xsource:3", + "-Yrangepos", + removeUnused, + s"-Xplugin:${SemanticdbPlugin.semanticdbPluginPath()}", + "-Xplugin-require:semanticdb", + "-classpath", + s"${scalaLibrary.mkString(":")}", + s"-P:semanticdb:sourceroot:$src", + s"-P:semanticdb:targetroot:$target", + "-d", + d.toString, + main.toString + ) + + val _ = scala.tools.nsc.Main.process(scalacOptions) + val result = api + .withRules( + Collections.singletonList(removeUnsuedRule().name.toString()) + ) + .withClasspath((scalaLibrary.map(_.toNIO) :+ target).asJava) + .withScalacOptions(Collections.singletonList(removeUnused)) + .withScalaVersion("2.13.8") + .withPaths(Seq(main).asJava) + .withSourceroot(src) + .evaluate() + + val error = result.getError + assert(!error.isPresent) + assert(result.isSuccessful) + assert(result.getFileEvaluations.length == 1) + val fileEvaluation = result.getFileEvaluations.head + assert(fileEvaluation.isSuccessful) + val expected = + """| + |object Main + |""".stripMargin + val obtained = fileEvaluation.previewPatches.get() + assertNoDiff(obtained, expected) + } + test("withScalaVersion: non-parsable scala version") { val run = Try(api.withScalaVersion("213")) assert(run.isFailure)