Skip to content

Commit 13635e7

Browse files
authored
Merge pull request #168 from olafurpg/unused-imports
Fix two buggy cases discovered while running on akka + scalding.
2 parents 8d660fd + 23cf79c commit 13635e7

File tree

6 files changed

+54
-10
lines changed

6 files changed

+54
-10
lines changed

.scalafmt.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ align.tokens = [
88
onTestFailure = "To fix this, run `./scalafmt` from the project base directory"
99
optIn.annotationNewlines = true
1010
project.excludeFilters = [
11+
scalafix-tests/input
1112
scalafix-tests/output
1213
]

scalafix-core/src/main/scala/scalafix/patch/ImportPatchOps.scala

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ object ImportPatchOps {
5353
.toSet
5454
val curlyBraceRemoves = allImporters.map { importer =>
5555
val keptImportees = importer.importees.filterNot(isRemovedImportee)
56+
val hasRemovedImportee = importer.importees.exists(isRemovedImportee)
5657
keptImportees match {
57-
case (Importee.Wildcard() | Importee.Name(_)) +: Nil =>
58+
case (Importee.Wildcard() | Importee.Name(_)) +: Nil
59+
if hasRemovedImportee =>
5860
ctx
5961
.toks(importer)
6062
.collectFirst {
@@ -70,34 +72,43 @@ object ImportPatchOps {
7072
case _ => Patch.empty
7173
}
7274
}
75+
// NOTE: keeps track of which comma is removed by which tree to prevent the
76+
// same comma being removed twice.
77+
val isRemovedComma = mutable.Map.empty[Token.Comma, Tree]
7378
val isRemovedImport =
7479
allImports.filter(_.importers.forall(isRemovedImporter))
7580
def remove(toRemove: Tree) = {
7681
val tokens = ctx.toks(toRemove)
77-
def removeFirstComma(lst: Iterable[Token]) =
82+
def removeFirstComma(lst: Iterable[Token]) = {
7883
lst
7984
.takeWhile {
8085
case Token.Space() => true
81-
case Token.Comma() => true
86+
case comma @ Token.Comma() =>
87+
if (!isRemovedComma.contains(comma)) {
88+
isRemovedComma(comma) = toRemove
89+
}
90+
true
8291
case _ => false
8392
}
8493
.map(ctx.removeToken(_))
94+
}
8595
val leadingComma =
86-
removeFirstComma(ctx.tokenList.to(tokens.head))
96+
removeFirstComma(ctx.tokenList.leading(tokens.head))
8797
val hadLeadingComma = leadingComma.exists {
88-
case TokenPatch.Add(_: Token.Comma, _, _, keepTok @ false) => true
98+
case TokenPatch.Add(comma: Token.Comma, _, _, keepTok @ false) =>
99+
isRemovedComma.get(comma).contains(toRemove)
89100
case _ => false
90101
}
91102
val trailingComma =
92103
if (hadLeadingComma) List(Patch.empty)
93-
else removeFirstComma(ctx.tokenList.from(tokens.last))
104+
else removeFirstComma(ctx.tokenList.trailing(tokens.last))
94105
PatchOps.removeTokens(tokens) ++ trailingComma ++ leadingComma
95106
}
96107

97108
val leadingNewlines = isRemovedImport.map { i =>
98109
var newline = false
99110
ctx.tokenList
100-
.to(ctx.toks(i).head)
111+
.leading(ctx.toks(i).head)
101112
.takeWhile(x =>
102113
!newline && {
103114
x.is[Token.Space] || {

scalafix-core/src/main/scala/scalafix/patch/Patch.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ abstract class TokenPatch(val tok: Token, val newTok: String)
5656
extends Patch
5757
with LowLevelPatch {
5858
override def toString: String =
59-
s"TokenPatch.${this.getClass.getSimpleName}(${tok.syntax.revealWhiteSpace}, ${tok.structure}, $newTok)"
59+
if (newTok.isEmpty) s"TokenPatch.Remove(${tok.structure.revealWhiteSpace})"
60+
else
61+
s"TokenPatch.${this.getClass.getSimpleName}(${tok.syntax.revealWhiteSpace}, ${tok.structure}, $newTok)"
6062
}
6163
private[scalafix] object TokenPatch {
6264
case class Remove(override val tok: Token) extends TokenPatch(tok, "")

scalafix-core/src/main/scala/scalafix/util/TokenList.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import scala.meta.tokens.Tokens
77

88
/** Helper to traverse tokens as a doubly linked list. */
99
class TokenList(tokens: Tokens) {
10-
def from(token: Token): SeqView[Token, IndexedSeq[Token]] =
10+
def trailing(token: Token): SeqView[Token, IndexedSeq[Token]] =
1111
tokens.view(tok2idx(token), tokens.length).drop(1)
12-
def to(token: Token): SeqView[Token, IndexedSeq[Token]] =
12+
def leading(token: Token): SeqView[Token, IndexedSeq[Token]] =
1313
tokens.view(0, tok2idx(token)).drop(1).reverse
1414
private[this] val tok2idx = {
1515
val map = Map.newBuilder[Token, Int]

scalafix-tests/input/src/main/scala/test/RemoveUnusedImports.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import scala.concurrent.{Await, Future}
99
import scala.util.{Properties, DynamicVariable, Try}
1010
import scala.util.{Success => Successful, Random}
1111
import scala.sys.process._
12+
import scala.concurrent.{CancellationException, ExecutionException, ExecutionContext}
13+
import scala.runtime.{RichBoolean}
14+
import scala.concurrent.{ // formatting caveat
15+
CancellationException,
16+
ExecutionException,
17+
TimeoutException
18+
}
19+
//import scala.concurrent.{
20+
// CancellationException
21+
// , ExecutionException
22+
// , TimeoutException // ERROR
23+
//}
1224

1325
object RemoveUnusedImports {
1426
import Future._
@@ -17,4 +29,7 @@ object RemoveUnusedImports {
1729
Properties.ScalaCompilerVersion
1830
Try(1)
1931
Successful(1)
32+
ExecutionContext.defaultReporter
33+
new RichBoolean(true)
34+
new TimeoutException
2035
}

scalafix-tests/output/src/main/scala/test/RemoveUnusedImports.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,26 @@ import scala.util.control.NonFatal
44
import scala.concurrent.Future
55
import scala.util.{Properties, Try}
66
import scala.util.{Success => Successful}
7+
import scala.concurrent.ExecutionContext
8+
import scala.runtime.{RichBoolean}
9+
import scala.concurrent. // formatting caveat
10+
11+
12+
TimeoutException
13+
14+
//import scala.concurrent.{
15+
// CancellationException
16+
// , ExecutionException
17+
// , TimeoutException // ERROR
18+
//}
719

820
object RemoveUnusedImports {
921
val NonFatal(a) = new Exception
1022
Future.successful(1)
1123
Properties.ScalaCompilerVersion
1224
Try(1)
1325
Successful(1)
26+
ExecutionContext.defaultReporter
27+
new RichBoolean(true)
28+
new TimeoutException
1429
}

0 commit comments

Comments
 (0)