Skip to content

Commit a87a188

Browse files
authored
Merge pull request scalacenter#1921 from bjaglin/dontsharestate
OrganizeImports: don't leak state from one fix execution to another
2 parents 9271030 + 22cb445 commit a87a188

File tree

1 file changed

+65
-30
lines changed

1 file changed

+65
-30
lines changed

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

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@ class OrganizeImports(
6262

6363
private val wildcardGroupIndex: Int = matchers indexOf *
6464

65-
private val unusedImporteePositions: mutable.Set[Position] =
66-
mutable.Set.empty[Position]
67-
68-
private val diagnostics: ArrayBuffer[Diagnostic] =
69-
ArrayBuffer.empty[Diagnostic]
70-
7165
def this() = this(OrganizeImportsConfig())
7266

7367
override def description: String = "Organize import statements"
@@ -95,43 +89,49 @@ class OrganizeImports(
9589
}
9690

9791
private def fixWithImplicitDialect(implicit doc: SemanticDocument): Patch = {
98-
unusedImporteePositions ++= doc.diagnostics.collect {
99-
case d if d.message == "Unused import" => d.position
100-
}
92+
93+
val diagnostics: ArrayBuffer[Diagnostic] = ArrayBuffer.empty[Diagnostic]
94+
95+
val unusedImporteePositions = new UnusedImporteePositions
10196

10297
val (globalImports, localImports) = collectImports(doc.tree)
10398

10499
val globalImportsPatch =
105100
if (globalImports.isEmpty) Patch.empty
106-
else organizeGlobalImports(globalImports)
101+
else
102+
organizeGlobalImports(unusedImporteePositions, diagnostics)(
103+
globalImports
104+
)
107105

108106
val localImportsPatch =
109107
if (!config.removeUnused || localImports.isEmpty) Patch.empty
110-
else removeUnused(localImports)
108+
else removeUnusedImports(unusedImporteePositions)(localImports)
111109

112110
diagnostics.map(Patch.lint).asPatch + globalImportsPatch + localImportsPatch
113111
}
114112

115-
private def isUnused(importee: Importee): Boolean =
116-
unusedImporteePositions contains positionOf(importee)
117-
118113
private def organizeGlobalImports(
114+
unusedImporteePositions: UnusedImporteePositions,
115+
diagnostics: ArrayBuffer[Diagnostic]
116+
)(
119117
imports: Seq[Import]
120118
)(implicit doc: SemanticDocument): Patch = {
121-
val noUnused = imports flatMap (_.importers) flatMap (removeUnused(_).toSeq)
119+
val noUnused = imports flatMap (_.importers) flatMap (
120+
removeUnusedImporters(unusedImporteePositions)(_).toSeq
121+
)
122122

123123
val (implicits, noImplicits) =
124124
if (!config.groupExplicitlyImportedImplicitsSeparately) (Nil, noUnused)
125125
else partitionImplicits(noUnused)
126126

127127
val (fullyQualifiedImporters, relativeImporters) =
128-
noImplicits partition isFullyQualified
128+
noImplicits partition isFullyQualified(diagnostics)
129129

130130
// Organizes all the fully-qualified global importers.
131131
val fullyQualifiedGroups: Seq[ImportGroup] = {
132132
val expanded =
133133
if (config.expandRelative) relativeImporters map expandRelative else Nil
134-
groupImporters(fullyQualifiedImporters ++ expanded)
134+
groupImporters(diagnostics)(fullyQualifiedImporters ++ expanded)
135135
}
136136

137137
// Moves relative imports (when `config.expandRelative` is false) and
@@ -165,39 +165,49 @@ class OrganizeImports(
165165
(insertionPatch + removalPatch).atomic
166166
}
167167

168-
private def removeUnused(imports: Seq[Import]): Patch =
168+
private def removeUnusedImports(
169+
unusedImporteePositions: UnusedImporteePositions
170+
)(
171+
imports: Seq[Import]
172+
): Patch =
169173
Patch.fromIterable {
170174
imports flatMap (_.importers) flatMap { case Importer(_, importees) =>
171175
val hasUsedWildcard = importees exists { i =>
172-
i.is[Importee.Wildcard] && !isUnused(i)
176+
i.is[Importee.Wildcard] && !unusedImporteePositions(i)
173177
}
174178

175179
importees collect {
176-
case i @ Importee.Rename(_, to) if isUnused(i) && hasUsedWildcard =>
180+
case i @ Importee.Rename(_, to)
181+
if unusedImporteePositions(i) && hasUsedWildcard =>
177182
// Unimport the identifier instead of removing the importee since
178183
// unused renamed may still impact compilation by shadowing an
179184
// identifier.
180185
//
181186
// See https://github.com/scalacenter/scalafix/issues/614
182187
Patch.replaceTree(to, "_").atomic
183188

184-
case i if isUnused(i) =>
189+
case i if unusedImporteePositions(i) =>
185190
Patch.removeImportee(i).atomic
186191
}
187192
}
188193
}
189194

190-
private def removeUnused(importer: Importer): Option[Importer] =
195+
private def removeUnusedImporters(
196+
unusedImporteePositions: UnusedImporteePositions
197+
)(
198+
importer: Importer
199+
): Option[Importer] =
191200
if (!config.removeUnused) Some(importer)
192201
else {
193202
val hasUsedWildcard = importer.importees exists { i =>
194-
i.is[Importee.Wildcard] && !isUnused(i)
203+
i.is[Importee.Wildcard] && !unusedImporteePositions(i)
195204
}
196205

197206
var rewritten = false
198207

199208
val noUnused = importer.importees.flatMap {
200-
case i @ Importee.Rename(from, _) if isUnused(i) && hasUsedWildcard =>
209+
case i @ Importee.Rename(from, _)
210+
if unusedImporteePositions(i) && hasUsedWildcard =>
201211
// Unimport the identifier instead of removing the importee since
202212
// unused renamed may still impact compilation by shadowing an
203213
// identifier.
@@ -206,7 +216,7 @@ class OrganizeImports(
206216
rewritten = true
207217
Importee.Unimport(from) :: Nil
208218

209-
case i if isUnused(i) =>
219+
case i if unusedImporteePositions(i) =>
210220
rewritten = true
211221
Nil
212222

@@ -240,6 +250,8 @@ class OrganizeImports(
240250
}
241251

242252
private def isFullyQualified(
253+
diagnostics: ArrayBuffer[Diagnostic]
254+
)(
243255
importer: Importer
244256
)(implicit doc: SemanticDocument): Boolean = {
245257
val topQualifier = topQualifierOf(importer.ref)
@@ -312,10 +324,16 @@ class OrganizeImports(
312324
)
313325
}
314326

315-
private def groupImporters(importers: Seq[Importer]): Seq[ImportGroup] =
327+
private def groupImporters(
328+
diagnostics: ArrayBuffer[Diagnostic]
329+
)(
330+
importers: Seq[Importer]
331+
): Seq[ImportGroup] =
316332
importers
317333
.groupBy(matchImportGroup) // Groups imports by importer prefix.
318-
.mapValues(deduplicateImportees _ andThen organizeImportGroup)
334+
.mapValues(
335+
deduplicateImportees _ andThen organizeImportGroup(diagnostics)
336+
)
319337
.map { case (index, imports) => ImportGroup(index, imports) }
320338
.toSeq
321339
.sortBy(_.index)
@@ -333,13 +351,17 @@ class OrganizeImports(
333351
}
334352
}
335353

336-
private def organizeImportGroup(importers: Seq[Importer]): Seq[Importer] = {
354+
private def organizeImportGroup(
355+
diagnostics: ArrayBuffer[Diagnostic]
356+
)(
357+
importers: Seq[Importer]
358+
): Seq[Importer] = {
337359
val importeesSorted = locally {
338360
config.groupedImports match {
339361
case GroupedImports.Merge =>
340-
mergeImporters(importers, aggressive = false)
362+
mergeImporters(diagnostics)(importers, aggressive = false)
341363
case GroupedImports.AggressiveMerge =>
342-
mergeImporters(importers, aggressive = true)
364+
mergeImporters(diagnostics)(importers, aggressive = true)
343365
case GroupedImports.Explode =>
344366
explodeImportees(importers)
345367
case GroupedImports.Keep =>
@@ -363,6 +385,8 @@ class OrganizeImports(
363385
}
364386

365387
private def mergeImporters(
388+
diagnostics: ArrayBuffer[Diagnostic]
389+
)(
366390
importers: Seq[Importer],
367391
aggressive: Boolean
368392
): Seq[Importer] =
@@ -1078,6 +1102,17 @@ object OrganizeImports {
10781102
}
10791103
}
10801104

1105+
class UnusedImporteePositions(implicit doc: SemanticDocument) {
1106+
private val positions: Seq[Position] =
1107+
doc.diagnostics.toSeq.collect {
1108+
case d if d.message == "Unused import" => d.position
1109+
}
1110+
1111+
/** Returns true if the importee was marked as unused by the compiler */
1112+
def apply(importee: Importee): Boolean =
1113+
positions contains positionOf(importee)
1114+
}
1115+
10811116
implicit private class SymbolExtension(symbol: Symbol) {
10821117

10831118
/**

0 commit comments

Comments
 (0)