Skip to content

Commit

Permalink
Merge pull request #1800 from tanishiking/organize-imports-3
Browse files Browse the repository at this point in the history
Support OrganizeImports.removeUnused in Scala 3.4+
  • Loading branch information
bjaglin authored Feb 3, 2024
2 parents a87a188 + 6eee581 commit de53a33
Show file tree
Hide file tree
Showing 7 changed files with 31 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 @@ -863,7 +863,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 @@ -911,17 +913,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 Expand Up @@ -1105,12 +1107,24 @@ object OrganizeImports {
class UnusedImporteePositions(implicit doc: SemanticDocument) {
private val positions: Seq[Position] =
doc.diagnostics.toSeq.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
}

/** Returns true if the importee was marked as unused by the compiler */
def apply(importee: Importee): Boolean =
positions contains positionOf(importee)
def apply(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)
positions.exists { unused =>
unused.start <= pos.start && pos.end <= unused.end
}
}
}

implicit private class SymbolExtension(symbol: Symbol) {
Expand Down

0 comments on commit de53a33

Please sign in to comment.