diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt index 07bba673902..9ab68dddddf 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/OptimizedImportsBuilder.kt @@ -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.parentsWithSelf import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes -import java.util.* class OptimizedImportsBuilder( private val file: KtFile, @@ -88,16 +87,6 @@ class OptimizedImportsBuilder( private val importRules = HashSet() fun buildOptimizedImports(): List? { - // 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) { val importRulesBefore = importRules.size val result = tryBuildOptimizedImports() @@ -124,18 +113,20 @@ class OptimizedImportsBuilder( .mapTo(importsToGenerate) { it.importPath } val descriptorsByParentFqName = HashMap>() - for (descriptor in data.descriptorsToImport.keys) { - val fqName = descriptor.importableFqName!! + for ((descriptor, names) in data.descriptorsToImport) { + for (name in names) { + val fqName = descriptor.importableFqName!! + val alias = if (name != fqName.shortName()) name else null - val explicitImportPath = ImportPath(fqName, false) - if (explicitImportPath in importsToGenerate) continue + val explicitImportPath = ImportPath(fqName, false, alias) + if (explicitImportPath in importsToGenerate) continue - val parentFqName = fqName.parent() - val starImportPath = ImportPath(parentFqName, true) - if (canUseStarImport(descriptor, fqName) && starImportPath.isAllowedByRules()) { - descriptorsByParentFqName.getOrPut(parentFqName) { HashSet() }.add(descriptor) - } else { - importsToGenerate.add(explicitImportPath) + val parentFqName = fqName.parent() + if (alias == null && canUseStarImport(descriptor, fqName) && ImportPath(parentFqName, true).isAllowedByRules()) { + descriptorsByParentFqName.getOrPut(parentFqName) { LinkedHashSet() }.add(descriptor) + } else { + importsToGenerate.add(explicitImportPath) + } } } @@ -205,7 +196,12 @@ class OptimizedImportsBuilder( val newTargets = ref.resolve(newBindingContext) if (!areTargetsEqual(oldTargets, newTargets)) { 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 } - private fun lockImportForDescriptor(descriptor: DeclarationDescriptor) { + private fun lockImportForDescriptor(descriptor: DeclarationDescriptor, names: Collection) { val fqName = descriptor.importableFqName ?: return - val explicitImportPath = ImportPath(fqName, false) val starImportPath = ImportPath(fqName.parent(), true) val importPaths = file.importDirectives.map { it.importPath } - when { - explicitImportPath in importPaths -> - importRules.add(ImportRule.Add(explicitImportPath)) - 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)) + + for (name in names) { + val alias = if (name != fqName.shortName()) name else null + val explicitImportPath = ImportPath(fqName, false, alias) + when { + explicitImportPath in importPaths -> + 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)) + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt index 3de3bdff9b6..25bd3306744 100644 --- a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt @@ -73,13 +73,13 @@ class KotlinImportOptimizer : ImportOptimizer { private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() { private val currentPackageName = file.packageFqName - private val withAlias: Set = file.importDirectives + private val aliases: Map> = file.importDirectives .asSequence() - .filter { it.alias != null && it.aliasName != it.importPath?.fqName?.shortName()?.asString() } - .mapNotNull(KtImportDirective::importedFqName) - .toSet() + .filter { !it.isAllUnder && it.alias != null } + .mapNotNull { it.importPath } + .groupBy(keySelector = { it.fqName }, valueTransform = { it.importedName as Name }) - private val descriptorsToImport = LinkedHashMap>() + private val descriptorsToImport = LinkedHashMap>() private val abstractRefs = ArrayList() val data: OptimizedImportsBuilder.InputData @@ -111,13 +111,15 @@ class KotlinImportOptimizer : ImportOptimizer { val importableFqName = target.importableFqName ?: continue val parentFqName = importableFqName.parent() 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 (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 } } diff --git a/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt b/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt index d1ab5684ebe..7d54fc33e8f 100644 --- a/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt +++ b/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt @@ -1,4 +1,5 @@ import name import name as names -val a = name \ No newline at end of file +val a = name +val b = names \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt.after b/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt.after index 60f552c81c3..7d54fc33e8f 100644 --- a/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt.after +++ b/idea/testData/editor/optimizeImports/common/ConflictWithAlias.kt.after @@ -2,3 +2,4 @@ import name import name as names val a = name +val b = names \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/common/IteratorFunction.dependency.kt b/idea/testData/editor/optimizeImports/common/IteratorFunction.dependency.kt index d9004a82ed3..249a7b2661d 100644 --- a/idea/testData/editor/optimizeImports/common/IteratorFunction.dependency.kt +++ b/idea/testData/editor/optimizeImports/common/IteratorFunction.dependency.kt @@ -1,9 +1,8 @@ package test1 -public class MyClass { -} +class MyClass -public fun MyClass.iterator(): Iterator { +operator fun MyClass.iterator(): Iterator { return object: Iterator { override fun next(): MyClass { throw Exception() diff --git a/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.after b/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.after index 7c87269930e..55be2b246f2 100644 --- a/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.after +++ b/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.after @@ -1,5 +1,5 @@ // NAME_COUNT_TO_USE_STAR_IMPORT: 2 -import p1.* +import p1.A import p2.A fun f() { diff --git a/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.log b/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.log index 22546bfe039..6a2daa08bc9 100644 --- a/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.log +++ b/idea/testData/editor/optimizeImports/common/TwoConstructors.kt.log @@ -6,4 +6,3 @@ Additional checking of reference KtSimpleNameReference: A Changed resolve of KtSimpleNameReference: A Additional checking of reference KtInvokeFunctionReference: A("") Additional checking of reference KtInvokeFunctionReference: A(1) -Trying to build import list again with import rules: +p2.A, +p1.* diff --git a/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt index e55e48c777f..1ab78c34e71 100644 --- a/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt +++ b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt @@ -1,20 +1,20 @@ /** Comment 1 -*/ + */ package sometest -import java.io as JavaIO -import java.text.Annotation as TextAnnotation +import java.io.File as JavaFile +import java.lang.Runnable as Task import java.util.ArrayList import java.util.HashSet /** Comment 2 -*/ + */ class Action { - fun test(hash : HashSet) { - val some : TextAnnotation? = null - val test : ArrayList? = null - JavaIO.File(StringBuilder().append("Hello").toString()) + fun test(hash: HashSet) { + val some: Task? = null + val test: ArrayList? = null + JavaFile(StringBuilder().append("Hello").toString()) } } \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.after b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.after index cbd7711e861..b6e843d22f0 100644 --- a/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.after +++ b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.after @@ -1,20 +1,20 @@ /** Comment 1 -*/ + */ package sometest import java.util.ArrayList import java.util.HashSet -import java.io as JavaIO -import java.text.Annotation as TextAnnotation +import java.io.File as JavaFile +import java.lang.Runnable as Task /** Comment 2 -*/ + */ class Action { - fun test(hash : HashSet) { - val some : TextAnnotation? = null - val test : ArrayList? = null - JavaIO.File(StringBuilder().append("Hello").toString()) + fun test(hash: HashSet) { + val some: Task? = null + val test: ArrayList? = null + JavaFile(StringBuilder().append("Hello").toString()) } } \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.log b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.log new file mode 100644 index 00000000000..0f7bd155da0 --- /dev/null +++ b/idea/testData/editor/optimizeImports/jvm/AlreadyOptimized.kt.log @@ -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()) diff --git a/idea/testData/editor/optimizeImports/jvm/WithAliases.kt.after b/idea/testData/editor/optimizeImports/jvm/WithAliases.kt.after index f64789d4a20..a8214bf5e72 100644 --- a/idea/testData/editor/optimizeImports/jvm/WithAliases.kt.after +++ b/idea/testData/editor/optimizeImports/jvm/WithAliases.kt.after @@ -1,6 +1,4 @@ import java.util.ArrayList -import java.io as JavaIO -import java.util.ArrayList as SomeThing class Action { fun test() { diff --git a/idea/testData/quickfix/optimizeImports/importAlias.kt b/idea/testData/quickfix/optimizeImports/importAlias.kt new file mode 100644 index 00000000000..db077ab536f --- /dev/null +++ b/idea/testData/quickfix/optimizeImports/importAlias.kt @@ -0,0 +1,10 @@ +// "Optimize imports" "true" + +import A as B +import A as T + +class A + +fun foo() { + B() +} diff --git a/idea/testData/quickfix/optimizeImports/importAlias.kt.after b/idea/testData/quickfix/optimizeImports/importAlias.kt.after new file mode 100644 index 00000000000..4ae430668e0 --- /dev/null +++ b/idea/testData/quickfix/optimizeImports/importAlias.kt.after @@ -0,0 +1,9 @@ +// "Optimize imports" "true" + +import A as B + +class A + +fun foo() { + B() +} diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveClassWithImportsRetained/after/second/A.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveClassWithImportsRetained/after/second/A.kt index 9be8812f121..4f8872eb705 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveClassWithImportsRetained/after/second/A.kt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveClassWithImportsRetained/after/second/A.kt @@ -1,6 +1,4 @@ package second -import third.D as D_ - class A diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFunctionWithImportsRetained/after/second/A.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFunctionWithImportsRetained/after/second/A.kt index 9be8812f121..4f8872eb705 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFunctionWithImportsRetained/after/second/A.kt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFunctionWithImportsRetained/after/second/A.kt @@ -1,6 +1,4 @@ package second -import third.D as D_ - class A diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index e13e3ea85bb..d6321ad908f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -9144,6 +9144,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { public void testFileRuntime() throws Exception { 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")