Skip to content

Commit a1097a7

Browse files
committed
support RemoveUnused on Scala 3.4+
1 parent 96642e5 commit a1097a7

26 files changed

+515
-84
lines changed

build.sbt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,6 @@ 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
201199
logLevel := Level.Error, // avoid flood of compiler warnings
202200
libraryDependencies ++= testsDependencies.value,
203201
coverageEnabled := false,
@@ -213,7 +211,6 @@ lazy val output = projectMatrix
213211
.in(file("scalafix-tests/output"))
214212
.settings(
215213
noPublishAndNoMima,
216-
scalacOptions --= warnUnusedImports.value,
217214
libraryDependencies ++= testsDependencies.value,
218215
coverageEnabled := false,
219216
// mimic dependsOn(shared) but allowing binary Scala version matching

docs/developers/symbol-information.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Use `MethodSignature.parameterLists` to look up parameters of a method.
107107
def printMethodParameters(symbol: Symbol): Unit = {
108108
symbol.info.get.signature match {
109109
case signature @ MethodSignature(typeParameters, parameterLists, _) =>
110+
println("signature = " + signature)
110111
if (typeParameters.nonEmpty) {
111112
println("typeParameters")
112113
println(typeParameters.mkString(" ", "\n ", ""))
@@ -295,7 +296,7 @@ constructor.
295296
```scala mdoc
296297
def getConstructors(symbol: Symbol): List[SymbolInformation] =
297298
symbol.info.get.signature match {
298-
case ClassSignature(_, parents, _, declarations) =>
299+
case ClassSignature(_, _, _, declarations) =>
299300
declarations.filter { declaration =>
300301
declaration.isConstructor
301302
}

docs/rules/RemoveUnused.md

Lines changed: 50 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+).
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 (requires `-Wunused:unsafe-warn-patvars` on Scala 3):
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 only):
9188

9289
```scala
9390
// before
@@ -129,21 +126,54 @@ 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 members.
174+
NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all,
175+
- unsafe-warn-patvars :
176+
(UNSAFE) Warn if a variable bound in a pattern is unused.
177+
This warning can generate false positive, as warning cannot be
178+
suppressed yet.
179+
```

docs/users/installation.md

Lines changed: 1 addition & 1 deletion
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

project/ScalafixBuild.scala

Lines changed: 5 additions & 9 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) Seq("-Wunused:all", "-Wunused:unsafe-warn-patvars")
80+
else if (isScala213.value) Seq("-Wunused")
81+
else Seq("-Ywarn-unused")
8682
}
8783
lazy val targetJvm = Def.setting {
8884
if (isScala3.value) Seq("-release:8")
@@ -109,8 +105,8 @@ 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 ++
109+
warnUnused.value ++
114110
Seq(
115111
"-encoding",
116112
"UTF-8",

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

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,29 @@ 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+
val diagnosticsAvailableInSemanticdb =
36+
Seq("3.0", "3.1", "3.2", "3.3")
37+
.forall(v => !config.scalaVersion.startsWith(v))
38+
3539
val hasWarnUnused = config.scalacOptions.exists(option =>
3640
warnUnusedPrefix.exists(prefix => option.startsWith(prefix)) ||
3741
warnUnusedString.contains(option)
3842
)
39-
if (config.scalaVersion.startsWith("3"))
43+
if (!hasWarnUnused) {
4044
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"
45+
"""|A Scala compiler option is required to use RemoveUnused. To fix this problem,
46+
|update your build to add -Ywarn-unused (with 2.12), -Wunused (with 2.13), or
47+
|-Wunused:all (with 3.4+)""".stripMargin
4348
)
44-
else if (!hasWarnUnused) {
49+
} else if (!diagnosticsAvailableInSemanticdb) {
4550
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
51+
"You must use a more recent version of the Scala 3 compiler (3.4+)"
4952
)
5053
} else {
5154
config.conf
@@ -64,29 +67,36 @@ class RemoveUnused(config: RemoveUnusedConfig)
6467
raw"^pattern var .* in (value|method) .* is never used".r
6568

6669
doc.diagnostics.foreach { diagnostic =>
67-
if (config.imports && diagnostic.message == "Unused import") {
70+
val msg = diagnostic.message
71+
if (config.imports && diagnostic.message.toLowerCase == "unused import") {
6872
isUnusedImport += diagnostic.position
6973
} else if (
7074
config.privates &&
71-
diagnostic.message.startsWith("private") &&
72-
diagnostic.message.endsWith("is never used")
75+
(
76+
(msg.startsWith("private") && msg.endsWith("is never used")) ||
77+
msg == "unused private member"
78+
)
7379
) {
7480
isUnusedTerm += diagnostic.position
7581
} else if (
7682
config.locals &&
77-
diagnostic.message.startsWith("local") &&
78-
diagnostic.message.endsWith("is never used")
83+
(
84+
(msg.startsWith("local") && msg.endsWith("is never used")) ||
85+
msg == "unused local definition"
86+
)
7987
) {
8088
isUnusedTerm += diagnostic.position
8189
} else if (
8290
config.patternvars &&
83-
unusedPatterExpr.findFirstMatchIn(diagnostic.message).isDefined
91+
(
92+
unusedPatterExpr.findFirstMatchIn(diagnostic.message).isDefined ||
93+
msg == "unused pattern variable"
94+
)
8495
) {
8596
isUnusedPattern += diagnostic.position
8697
} else if (
8798
config.params &&
88-
diagnostic.message.startsWith("parameter") &&
89-
diagnostic.message.endsWith("is never used")
99+
(msg.startsWith("parameter") && msg.endsWith("is never used"))
90100
) {
91101
isUnusedParam += diagnostic.position
92102
}
@@ -116,29 +126,33 @@ class RemoveUnused(config: RemoveUnusedConfig)
116126
.atomic
117127
}
118128

129+
def isUnusedImportee(importee: Importee): Boolean = {
130+
val pos = importee match {
131+
case Importee.Rename(from, _) => from.pos
132+
case _ => importee.pos
133+
}
134+
isUnusedImport.exists { unused =>
135+
unused.start <= pos.start && pos.end <= unused.end
136+
}
137+
}
138+
119139
doc.tree.collect {
120140
case Importer(_, importees)
121141
if importees.forall(_.is[Importee.Unimport]) =>
122142
importees.map(Patch.removeImportee).asPatch
123143
case Importer(_, importees) =>
124144
val hasUsedWildcard = importees.exists {
125-
case i: Importee.Wildcard => !isUnusedImport(importPosition(i))
145+
case i: Importee.Wildcard => !isUnusedImportee(i)
126146
case _ => false
127147
}
128148
importees.collect {
129149
case i @ Importee.Rename(_, to)
130-
if isUnusedImport(importPosition(i)) && hasUsedWildcard =>
150+
if isUnusedImportee(i) && hasUsedWildcard =>
131151
// Unimport the identifier instead of removing the importee since
132152
// unused renamed may still impact compilation by shadowing an identifier.
133153
// See https://github.com/scalacenter/scalafix/issues/614
134154
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-
) =>
155+
case i if isUnusedImportee(i) =>
142156
Patch.removeImportee(i).atomic
143157
}.asPatch
144158
case i: Defn if isUnusedTerm(i.pos) =>
@@ -182,11 +196,6 @@ class RemoveUnused(config: RemoveUnusedConfig)
182196
}
183197
}
184198

185-
private def importPosition(importee: Importee): Position = importee match {
186-
case Importee.Rename(from, _) => from.pos
187-
case _ => importee.pos
188-
}
189-
190199
// Given ("val x = 2", "2"), returns "val x = ".
191200
private def leftTokens(t: Tree, right: Tree): Tokens = {
192201
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 (Scala 2 only)")
2218
params: Boolean = true
2319
)
2420

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)