Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OrganizeImports.removeUnused in Scala 3.4+ #1800

Merged
merged 1 commit into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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