Optimize Imports should remove unused import alias

#KT-17375 Fixed
This commit is contained in:
Dmitry Gridin
2019-04-29 17:04:53 +07:00
parent 7fe0503337
commit 96ed33e357
16 changed files with 90 additions and 66 deletions
@@ -41,7 +41,6 @@ import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import java.util.*
class OptimizedImportsBuilder( class OptimizedImportsBuilder(
private val file: KtFile, private val file: KtFile,
@@ -88,16 +87,6 @@ class OptimizedImportsBuilder(
private val importRules = HashSet<ImportRule>() private val importRules = HashSet<ImportRule>()
fun buildOptimizedImports(): List<ImportPath>? { fun buildOptimizedImports(): List<ImportPath>? {
// TODO: should we drop unused aliases?
// keep all non-trivial aliases
file.importDirectives
.mapNotNull { it.importPath }
.filter {
val aliasName = it.alias
aliasName != null && aliasName != it.fqName.shortName()
}
.mapTo(importRules) { ImportRule.Add(it) }
while (true) { while (true) {
val importRulesBefore = importRules.size val importRulesBefore = importRules.size
val result = tryBuildOptimizedImports() val result = tryBuildOptimizedImports()
@@ -124,18 +113,20 @@ class OptimizedImportsBuilder(
.mapTo(importsToGenerate) { it.importPath } .mapTo(importsToGenerate) { it.importPath }
val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>() val descriptorsByParentFqName = HashMap<FqName, MutableSet<DeclarationDescriptor>>()
for (descriptor in data.descriptorsToImport.keys) { for ((descriptor, names) in data.descriptorsToImport) {
val fqName = descriptor.importableFqName!! for (name in names) {
val fqName = descriptor.importableFqName!!
val alias = if (name != fqName.shortName()) name else null
val explicitImportPath = ImportPath(fqName, false) val explicitImportPath = ImportPath(fqName, false, alias)
if (explicitImportPath in importsToGenerate) continue if (explicitImportPath in importsToGenerate) continue
val parentFqName = fqName.parent() val parentFqName = fqName.parent()
val starImportPath = ImportPath(parentFqName, true) if (alias == null && canUseStarImport(descriptor, fqName) && ImportPath(parentFqName, true).isAllowedByRules()) {
if (canUseStarImport(descriptor, fqName) && starImportPath.isAllowedByRules()) { descriptorsByParentFqName.getOrPut(parentFqName) { LinkedHashSet() }.add(descriptor)
descriptorsByParentFqName.getOrPut(parentFqName) { HashSet() }.add(descriptor) } else {
} else { importsToGenerate.add(explicitImportPath)
importsToGenerate.add(explicitImportPath) }
} }
} }
@@ -205,7 +196,12 @@ class OptimizedImportsBuilder(
val newTargets = ref.resolve(newBindingContext) val newTargets = ref.resolve(newBindingContext)
if (!areTargetsEqual(oldTargets, newTargets)) { if (!areTargetsEqual(oldTargets, newTargets)) {
testLog?.append("Changed resolve of $ref\n") testLog?.append("Changed resolve of $ref\n")
(oldTargets + newTargets).forEach { lockImportForDescriptor(it) } (oldTargets + newTargets).forEach {
lockImportForDescriptor(
it,
data.descriptorsToImport.getOrElse(it) { listOf(it.name) }.intersect(names)
)
}
} }
} }
} }
@@ -214,18 +210,22 @@ class OptimizedImportsBuilder(
return sortedImportsToGenerate return sortedImportsToGenerate
} }
private fun lockImportForDescriptor(descriptor: DeclarationDescriptor) { private fun lockImportForDescriptor(descriptor: DeclarationDescriptor, names: Collection<Name>) {
val fqName = descriptor.importableFqName ?: return val fqName = descriptor.importableFqName ?: return
val explicitImportPath = ImportPath(fqName, false)
val starImportPath = ImportPath(fqName.parent(), true) val starImportPath = ImportPath(fqName.parent(), true)
val importPaths = file.importDirectives.map { it.importPath } val importPaths = file.importDirectives.map { it.importPath }
when {
explicitImportPath in importPaths -> for (name in names) {
importRules.add(ImportRule.Add(explicitImportPath)) val alias = if (name != fqName.shortName()) name else null
starImportPath in importPaths -> val explicitImportPath = ImportPath(fqName, false, alias)
importRules.add(ImportRule.Add(starImportPath)) when {
else -> // there is no import for this descriptor in the original import list, so do not allow to import it by star-import explicitImportPath in importPaths ->
importRules.add(ImportRule.DoNotAdd(starImportPath)) importRules.add(ImportRule.Add(explicitImportPath))
alias == null && starImportPath in importPaths ->
importRules.add(ImportRule.Add(starImportPath))
else -> // there is no import for this descriptor in the original import list, so do not allow to import it by star-import
importRules.add(ImportRule.DoNotAdd(starImportPath))
}
} }
} }
@@ -73,13 +73,13 @@ class KotlinImportOptimizer : ImportOptimizer {
private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() { private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() {
private val currentPackageName = file.packageFqName private val currentPackageName = file.packageFqName
private val withAlias: Set<FqName> = file.importDirectives private val aliases: Map<FqName, List<Name>> = file.importDirectives
.asSequence() .asSequence()
.filter { it.alias != null && it.aliasName != it.importPath?.fqName?.shortName()?.asString() } .filter { !it.isAllUnder && it.alias != null }
.mapNotNull(KtImportDirective::importedFqName) .mapNotNull { it.importPath }
.toSet() .groupBy(keySelector = { it.fqName }, valueTransform = { it.importedName as Name })
private val descriptorsToImport = LinkedHashMap<DeclarationDescriptor, Set<Name>>() private val descriptorsToImport = LinkedHashMap<DeclarationDescriptor, HashSet<Name>>()
private val abstractRefs = ArrayList<OptimizedImportsBuilder.AbstractReference>() private val abstractRefs = ArrayList<OptimizedImportsBuilder.AbstractReference>()
val data: OptimizedImportsBuilder.InputData val data: OptimizedImportsBuilder.InputData
@@ -111,13 +111,15 @@ class KotlinImportOptimizer : ImportOptimizer {
val importableFqName = target.importableFqName ?: continue val importableFqName = target.importableFqName ?: continue
val parentFqName = importableFqName.parent() val parentFqName = importableFqName.parent()
if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages
if (target !is PackageViewDescriptor && parentFqName == currentPackageName && importableFqName !in withAlias) continue
if (target !is PackageViewDescriptor && parentFqName == currentPackageName && (importableFqName !in aliases)) continue
if (!reference.canBeResolvedViaImport(target, bindingContext)) continue if (!reference.canBeResolvedViaImport(target, bindingContext)) continue
if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue
descriptorsToImport.compute(importableDescriptor) { _, u -> u?.plus(names) ?: names.toHashSet() } val descriptorNames = (aliases[importableFqName].orEmpty() + importableFqName.shortName()).intersect(names)
descriptorsToImport.getOrPut(importableDescriptor) { LinkedHashSet() } += descriptorNames
} }
} }
@@ -1,4 +1,5 @@
import name import name
import name as names import name as names
val a = name val a = name
val b = names
@@ -2,3 +2,4 @@ import name
import name as names import name as names
val a = name val a = name
val b = names
@@ -1,9 +1,8 @@
package test1 package test1
public class MyClass { class MyClass
}
public fun MyClass.iterator(): Iterator<MyClass> { operator fun MyClass.iterator(): Iterator<MyClass> {
return object: Iterator<MyClass> { return object: Iterator<MyClass> {
override fun next(): MyClass { override fun next(): MyClass {
throw Exception() throw Exception()
@@ -1,5 +1,5 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2 // NAME_COUNT_TO_USE_STAR_IMPORT: 2
import p1.* import p1.A
import p2.A import p2.A
fun f() { fun f() {
@@ -6,4 +6,3 @@ Additional checking of reference KtSimpleNameReference: A
Changed resolve of KtSimpleNameReference: A Changed resolve of KtSimpleNameReference: A
Additional checking of reference KtInvokeFunctionReference: A("") Additional checking of reference KtInvokeFunctionReference: A("")
Additional checking of reference KtInvokeFunctionReference: A(1) Additional checking of reference KtInvokeFunctionReference: A(1)
Trying to build import list again with import rules: +p2.A, +p1.*
@@ -1,20 +1,20 @@
/** /**
Comment 1 Comment 1
*/ */
package sometest package sometest
import java.io as JavaIO import java.io.File as JavaFile
import java.text.Annotation as TextAnnotation import java.lang.Runnable as Task
import java.util.ArrayList import java.util.ArrayList
import java.util.HashSet import java.util.HashSet
/** /**
Comment 2 Comment 2
*/ */
class Action { class Action {
fun test(hash : HashSet<Int>) { fun test(hash: HashSet<Int>) {
val some : TextAnnotation? = null val some: Task? = null
val test : ArrayList<Int>? = null val test: ArrayList<Int>? = null
JavaIO.File(StringBuilder().append("Hello").toString()) JavaFile(StringBuilder().append("Hello").toString())
} }
} }
@@ -1,20 +1,20 @@
/** /**
Comment 1 Comment 1
*/ */
package sometest package sometest
import java.util.ArrayList import java.util.ArrayList
import java.util.HashSet import java.util.HashSet
import java.io as JavaIO import java.io.File as JavaFile
import java.text.Annotation as TextAnnotation import java.lang.Runnable as Task
/** /**
Comment 2 Comment 2
*/ */
class Action { class Action {
fun test(hash : HashSet<Int>) { fun test(hash: HashSet<Int>) {
val some : TextAnnotation? = null val some: Task? = null
val test : ArrayList<Int>? = null val test: ArrayList<Int>? = null
JavaIO.File(StringBuilder().append("Hello").toString()) JavaFile(StringBuilder().append("Hello").toString())
} }
} }
@@ -0,0 +1,4 @@
Additional checking of reference Getter: JavaFile
Additional checking of reference KtSimpleNameReference: JavaFile
Changed resolve of KtSimpleNameReference: JavaFile
Additional checking of reference KtInvokeFunctionReference: JavaFile(StringBuilder().append("Hello").toString())
@@ -1,6 +1,4 @@
import java.util.ArrayList import java.util.ArrayList
import java.io as JavaIO
import java.util.ArrayList as SomeThing
class Action { class Action {
fun test() { fun test() {
+10
View File
@@ -0,0 +1,10 @@
// "Optimize imports" "true"
import A as B
<caret>import A as T
class A
fun foo() {
B()
}
@@ -0,0 +1,9 @@
// "Optimize imports" "true"
import A as B
class A
fun foo() {
B()
}
@@ -1,6 +1,4 @@
package second package second
import third.D as D_
class A class A
@@ -1,6 +1,4 @@
package second package second
import third.D as D_
class A class A
@@ -9144,6 +9144,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testFileRuntime() throws Exception { public void testFileRuntime() throws Exception {
runTest("idea/testData/quickfix/optimizeImports/fileRuntime.kt"); runTest("idea/testData/quickfix/optimizeImports/fileRuntime.kt");
} }
@TestMetadata("importAlias.kt")
public void testImportAlias() throws Exception {
runTest("idea/testData/quickfix/optimizeImports/importAlias.kt");
}
} }
@TestMetadata("idea/testData/quickfix/override") @TestMetadata("idea/testData/quickfix/override")