Skip to content

Commit 7ba8bef

Browse files
committed
support RemoveUnused on Scala 3.4+
1 parent 9f60582 commit 7ba8bef

24 files changed

+142
-84
lines changed

build.sbt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,7 @@ lazy val input = projectMatrix
196196
noPublishAndNoMima,
197197
scalacOptions ~= (_.filterNot(_ == "-Yno-adapted-args")),
198198
scalacOptions ++= warnAdaptedArgs.value, // For NoAutoTupling
199-
scalacOptions ++= warnUnusedImports.value, // For RemoveUnused
200-
scalacOptions ++= warnUnused.value, // For RemoveUnusedTerms
199+
scalacOptions += warnUnused.value, // For RemoveUnusedTerms
201200
logLevel := Level.Error, // avoid flood of compiler warnings
202201
libraryDependencies ++= testsDependencies.value,
203202
coverageEnabled := false,
@@ -213,7 +212,6 @@ lazy val output = projectMatrix
213212
.in(file("scalafix-tests/output"))
214213
.settings(
215214
noPublishAndNoMima,
216-
scalacOptions --= warnUnusedImports.value,
217215
libraryDependencies ++= testsDependencies.value,
218216
coverageEnabled := false,
219217
// mimic dependsOn(shared) but allowing binary Scala version matching

docs/rules/RemoveUnused.md

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@ example diff from running `sbt "scalafix RemoveUnused"`.
1414

1515
To use this rule:
1616

17-
- Enable the Scala compiler option `-Ywarn-unused` (or `-Wunused` in 2.13). In
18-
sbt, this is done with `scalacOptions += "-Ywarn-unused"`.
17+
- Enable the Scala compiler option `-Ywarn-unused` (2.12), `-Wunused` (2.13),
18+
or `-Wunused:all` (3.4+ or 3.3.4+).
1919
- Disable `-Xfatal-warnings` if you have it enabled. This is required so the
2020
compiler warnings do not fail the build before running Scalafix. If you are
2121
running 2.12.13+ or 2.13.2+, you may keep `-Xfatal-warnings` by modifying how
2222
specific warnings are handled via `scalacOptions += "-Wconf:cat=unused:info"`.
23-
- This rule **can't work** yet on Scala 3 projects since the compiler option `warn-unused`
24-
is not yet available in Scala 3. You need to remove `RemoveUnused`
25-
from `.scalafix.conf` for Scala 3 projects.
2623

2724
## Examples
2825

@@ -67,7 +64,7 @@ object Main {
6764
}
6865
```
6966

70-
Remove unused pattern match variables:
67+
Remove unused pattern match variables (Scala 2 only):
7168

7269
```scala
7370
case class AB(a: Int, b: String)
@@ -87,7 +84,7 @@ object Main {
8784
}
8885
```
8986

90-
Remove unused function parameters:
87+
Remove unused function parameters (Scala 2.13 only):
9188

9289
```scala
9390
// before
@@ -129,21 +126,56 @@ import scalafix.internal.rule._
129126
println(scalafix.website.rule("RemoveUnused", RemoveUnusedConfig.default))
130127
```
131128

132-
## -Ywarn-unused
129+
## More granular scalac options
133130

134-
Consult `scala -Y` in the command-line for more information about using
135-
`-Ywarn-unused`.
131+
You may request more granular warnings to the compiler if you opt-out
132+
from some rewrites in the rule configuration.
136133

137134
```
138-
$ scala -Ywarn-unused:help
139-
Enable or disable specific `unused' warnings
140-
imports Warn if an import selector is not referenced.
141-
patvars Warn if a variable bound in a pattern is unused.
142-
privates Warn if a private member is unused.
143-
locals Warn if a local definition is unused.
144-
explicits Warn if an explicit parameter is unused.
145-
implicits Warn if an implicit parameter is unused.
146-
params Enable -Ywarn-unused:explicits,implicits.
147-
linted -Xlint:unused.
135+
$ scala212 -Wunused:help
136+
Enable or disable specific `unused` warnings
137+
imports Warn if an import selector is not referenced.
138+
patvars Warn if a variable bound in a pattern is unused.
139+
privates Warn if a private member is unused.
140+
locals Warn if a local definition is unused.
141+
explicits Warn if an explicit parameter is unused.
142+
implicits Warn if an implicit parameter is unused.
143+
synthetics Warn if a synthetic implicit parameter (context bound) is unused.
144+
nowarn Warn if a @nowarn annotation does not suppress any warnings.
145+
params Enable -Wunused:explicits,implicits,synthetics.
146+
linted -Xlint:unused.
148147
Default: All choices are enabled by default.
149148
```
149+
150+
```
151+
$ scala3 -W
152+
...
153+
-Wunused Enable or disable specific `unused` warnings
154+
Choices :
155+
- nowarn,
156+
- all,
157+
- imports :
158+
Warn if an import selector is not referenced.
159+
NOTE : overrided by -Wunused:strict-no-implicit-warn,
160+
- privates :
161+
Warn if a private member is unused,
162+
- locals :
163+
Warn if a local definition is unused,
164+
- explicits :
165+
Warn if an explicit parameter is unused,
166+
- implicits :
167+
Warn if an implicit parameter is unused,
168+
- params :
169+
Enable -Wunused:explicits,implicits,
170+
- linted :
171+
Enable -Wunused:imports,privates,locals,implicits,
172+
- strict-no-implicit-warn :
173+
Same as -Wunused:import, only for imports of explicit named
174+
members.
175+
NOTE : This overrides -Wunused:imports and NOT set by
176+
-Wunused:all,
177+
- unsafe-warn-patvars :
178+
(UNSAFE) Warn if a variable bound in a pattern is unused.
179+
This warning can generate false positive, as warning cannot be
180+
suppressed yet.
181+
```

docs/users/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: Installation
99

1010
**Java LTS (8, 11, 17 or 21)**
1111

12-
**Scala 2.12, 2.13 or 3.x** (all rules are not available for Scala 3.x)
12+
**Scala 2.12, 2.13 or 3.x**
1313

1414
## sbt
1515

@@ -259,7 +259,7 @@ Central. To install a custom rule, add it to `scalafixDependencies`
259259
```scala
260260
// at the top of build.sbt
261261
ThisBuild / scalafixDependencies +=
262-
"ch.epfl.scala" %% "example-scalafix-rule" % "3.0.0"
262+
"com.geirsson" %% "example-scalafix-rule" % "1.3.0"
263263
```
264264

265265
Start sbt and type `scalafix <TAB>`, once the `example-scalafix-rule` dependency

project/ScalafixBuild.scala

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,10 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys {
7575
lazy val isScala212 = Def.setting {
7676
scalaVersion.value.startsWith("2.12")
7777
}
78-
lazy val warnUnusedImports = Def.setting {
79-
if (isScala3.value) Nil
80-
else if (isScala213.value) Seq("-Wunused:imports")
81-
else Seq("-Ywarn-unused-import")
82-
}
8378
lazy val warnUnused = Def.setting {
84-
if (isScala2.value) Seq("-Ywarn-unused")
85-
else Nil
79+
if (isScala3.value) "-Wunused:all"
80+
else if (isScala213.value) "-Wunused"
81+
else "-Ywarn-unused"
8682
}
8783
lazy val targetJvm = Def.setting {
8884
if (isScala3.value) Seq("-release:8")
@@ -109,13 +105,13 @@ object ScalafixBuild extends AutoPlugin with GhpagesKeys {
109105
scalaXml +: otherLibs
110106
}
111107
lazy val compilerOptions = Def.setting(
112-
warnUnusedImports.value ++
113-
targetJvm.value ++
108+
targetJvm.value ++
114109
Seq(
115110
"-encoding",
116111
"UTF-8",
117112
"-feature",
118-
"-unchecked"
113+
"-unchecked",
114+
warnUnused.value
119115
)
120116
)
121117

scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnused.scala

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,33 @@ class RemoveUnused(config: RemoveUnusedConfig)
2626
def this() = this(RemoveUnusedConfig.default)
2727

2828
override def description: String =
29-
"Removes unused imports and terms that reported by the compiler under -Ywarn-unused"
29+
"Removes unused imports and terms that reported by the compiler under -Wunused"
3030
override def isRewrite: Boolean = true
3131

3232
private def warnUnusedPrefix = List("-Wunused", "-Ywarn-unused")
3333
private def warnUnusedString = List("-Xlint", "-Xlint:unused")
3434
override def withConfiguration(config: Configuration): Configured[Rule] = {
35+
def diagnosticsUnavailableInSemanticdb(version: String) =
36+
version.startsWith("3.0") ||
37+
version.startsWith("3.1") ||
38+
version.startsWith("3.2") ||
39+
version.startsWith("3.3.1") ||
40+
version.startsWith("3.3.2") ||
41+
version.startsWith("3.3.3")
42+
3543
val hasWarnUnused = config.scalacOptions.exists(option =>
3644
warnUnusedPrefix.exists(prefix => option.startsWith(prefix)) ||
3745
warnUnusedString.contains(option)
3846
)
39-
if (config.scalaVersion.startsWith("3"))
47+
if (!hasWarnUnused) {
4048
Configured.error(
41-
"This rule is specific to Scala 2, because the compiler option `-Ywarn-unused` is not available yet in scala 3 " +
42-
"To fix this error, remove RemoveUnused from .scalafix.conf"
49+
"""|A Scala compiler option is required to use RemoveUnused. To fix this problem,
50+
|update your build to add -Ywarn-unused (with 2.12), -Wunused (with 2.13), or
51+
|-Wunused:all (with 3.4+ or 3.3.4+)""".stripMargin
4352
)
44-
else if (!hasWarnUnused) {
53+
} else if (diagnosticsUnavailableInSemanticdb(config.scalaVersion)) {
4554
Configured.error(
46-
s"""|The Scala compiler option "-Ywarn-unused" is required to use RemoveUnused.
47-
|To fix this problem, update your build to use at least one Scala compiler
48-
|option like -Ywarn-unused, -Xlint:unused (2.12.2 or above), or -Wunused (2.13 only)""".stripMargin
55+
"You must use a more recent version of the Scala 3 compiler (3.4+ or 3.3.4+)"
4956
)
5057
} else {
5158
config.conf
@@ -64,18 +71,23 @@ class RemoveUnused(config: RemoveUnusedConfig)
6471
raw"^pattern var .* in (value|method) .* is never used".r
6572

6673
doc.diagnostics.foreach { diagnostic =>
67-
if (config.imports && diagnostic.message == "Unused import") {
74+
val msg = diagnostic.message
75+
if (config.imports && diagnostic.message.toLowerCase == "unused import") {
6876
isUnusedImport += diagnostic.position
6977
} else if (
7078
config.privates &&
71-
diagnostic.message.startsWith("private") &&
72-
diagnostic.message.endsWith("is never used")
79+
(
80+
(msg.startsWith("private") && msg.endsWith("is never used")) ||
81+
msg == "unused private member"
82+
)
7383
) {
7484
isUnusedTerm += diagnostic.position
7585
} else if (
7686
config.locals &&
77-
diagnostic.message.startsWith("local") &&
78-
diagnostic.message.endsWith("is never used")
87+
(
88+
(msg.startsWith("local") && msg.endsWith("is never used")) ||
89+
msg == "unused local definition"
90+
)
7991
) {
8092
isUnusedTerm += diagnostic.position
8193
} else if (
@@ -85,8 +97,7 @@ class RemoveUnused(config: RemoveUnusedConfig)
8597
isUnusedPattern += diagnostic.position
8698
} else if (
8799
config.params &&
88-
diagnostic.message.startsWith("parameter") &&
89-
diagnostic.message.endsWith("is never used")
100+
(msg.startsWith("parameter") && msg.endsWith("is never used"))
90101
) {
91102
isUnusedParam += diagnostic.position
92103
}
@@ -116,29 +127,33 @@ class RemoveUnused(config: RemoveUnusedConfig)
116127
.atomic
117128
}
118129

130+
def isUnusedImportee(importee: Importee): Boolean = {
131+
val pos = importee match {
132+
case Importee.Rename(from, _) => from.pos
133+
case _ => importee.pos
134+
}
135+
isUnusedImport.exists { unused =>
136+
unused.start <= pos.start && pos.end <= unused.end
137+
}
138+
}
139+
119140
doc.tree.collect {
120141
case Importer(_, importees)
121142
if importees.forall(_.is[Importee.Unimport]) =>
122143
importees.map(Patch.removeImportee).asPatch
123144
case Importer(_, importees) =>
124145
val hasUsedWildcard = importees.exists {
125-
case i: Importee.Wildcard => !isUnusedImport(importPosition(i))
146+
case i: Importee.Wildcard => !isUnusedImportee(i)
126147
case _ => false
127148
}
128149
importees.collect {
129150
case i @ Importee.Rename(_, to)
130-
if isUnusedImport(importPosition(i)) && hasUsedWildcard =>
151+
if isUnusedImportee(i) && hasUsedWildcard =>
131152
// Unimport the identifier instead of removing the importee since
132153
// unused renamed may still impact compilation by shadowing an identifier.
133154
// See https://github.com/scalacenter/scalafix/issues/614
134155
Patch.replaceTree(to, "_").atomic
135-
case i
136-
if isUnusedImport
137-
.exists(unused =>
138-
unused.start <= importPosition(i).start && importPosition(
139-
i
140-
).end <= unused.end
141-
) =>
156+
case i if isUnusedImportee(i) =>
142157
Patch.removeImportee(i).atomic
143158
}.asPatch
144159
case i: Defn if isUnusedTerm(i.pos) =>
@@ -182,11 +197,6 @@ class RemoveUnused(config: RemoveUnusedConfig)
182197
}
183198
}
184199

185-
private def importPosition(importee: Importee): Position = importee match {
186-
case Importee.Rename(from, _) => from.pos
187-
case _ => importee.pos
188-
}
189-
190200
// Given ("val x = 2", "2"), returns "val x = ".
191201
private def leftTokens(t: Tree, right: Tree): Tokens = {
192202
val startT = t.tokens.start

scalafix-rules/src/main/scala/scalafix/internal/rule/RemoveUnusedConfig.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@ case class RemoveUnusedConfig(
1212
privates: Boolean = true,
1313
@Description("Remove unused local definitions")
1414
locals: Boolean = true,
15-
@Description(
16-
"Remove unused pattern match variables (compatible with Scala 2.12 and 2.13)"
17-
)
15+
@Description("Remove unused pattern match variables")
1816
patternvars: Boolean = true,
19-
@Description(
20-
"Remove unused function parameters (compatible with Scala 2.12 and 2.13)"
21-
)
17+
@Description("Remove unused function parameters")
2218
params: Boolean = true
2319
)
2420

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
rule = RemoveUnused
3+
*/
4+
package test.removeUnused
5+
6+
object UnusedParams {
7+
val f: String => Unit = unused => println("f")
8+
val ff = (unused: String) => println("f")
9+
val fs = (used: String, unused: Long) => println(used)
10+
def g(x: String => Unit): Unit = ???
11+
g{implicit string => println("g")}
12+
}

scalafix-tests/input/src/main/scala-2/test/removeUnused/RemoveUnusedTerms.scala renamed to scalafix-tests/input/src/main/scala/test/removeUnused/RemoveUnusedTerms.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package test.removeUnused
55

66
object RemoveUnusedTerms {
77

8-
def foo {
8+
def foo = {
99
val a = "unused"
1010
val aa = println(5)
1111
var b = 0

scalafix-tests/integration/src/test/scala-2/scalafix/tests/cli/BaseCliSuite.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ trait BaseCliSuite extends AnyFunSuite with DiffAssertions {
4343
val ps = new PrintStream(new ByteArrayOutputStream())
4444

4545
val removeImportsPath: RelativePath =
46-
RelativePath("scala-2/test/removeUnused/RemoveUnusedImports.scala")
46+
RelativePath("scala/test/removeUnused/RemoveUnusedImports.scala")
4747
val explicitResultTypesPath: RelativePath =
4848
RelativePath(
4949
"scala-2/test/explicitResultTypes/ExplicitResultTypesBase.scala"

scalafix-tests/integration/src/test/scala-2/scalafix/tests/cli/CliSemanticSuite.scala

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,4 @@ class CliSemanticSuite extends BaseCliSuite {
245245
assert(exit.is(ExitStatus.MissingSemanticdbError))
246246
}
247247

248-
checkSemantic(
249-
name = "ScalaVersion Scala3",
250-
args = Array(
251-
"--classpath",
252-
defaultClasspath,
253-
"--scalaVersion",
254-
"3.0.0-RC3"
255-
),
256-
expectedExit = ExitStatus.CommandLineError
257-
)
258-
259248
}

0 commit comments

Comments
 (0)