Skip to content

Commit

Permalink
Support OrganizeImports.removeUnused in Scala 3
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tanishiking authored and bjaglin committed Feb 3, 2024
1 parent e4eb15d commit 02033a4
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 24 deletions.
19 changes: 6 additions & 13 deletions docs/rules/OrganizeImports.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -1299,7 +1292,7 @@ Boolean
```conf
OrganizeImports {
groups = ["javax?\\.", "scala.", "*"]
removeUnused = true // not supported in Scala 3
removeUnused = true
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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."
)
}

Expand Down

0 comments on commit 02033a4

Please sign in to comment.