KTIJ-26423 [kotlin] Properly resolve conflicting classes in OptimizedImportsBuilder

This is not all the conflicts resolution that is
required for 100% correct Import Optimizer
implementation, but it suffices most of the time.

GitOrigin-RevId: 7732d85966c678efe2ffe7f78328daed50622af9
This commit is contained in:
Roman Golyshev
2024-08-12 09:58:57 +00:00
committed by intellij-monorepo-bot
parent 54705dd9c0
commit d2ee644e48
10 changed files with 169 additions and 2 deletions
@@ -0,0 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.imports
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.components.KaScopeContext
import org.jetbrains.kotlin.analysis.api.scopes.KaScope
import org.jetbrains.kotlin.analysis.api.symbols.KaClassifierSymbol
import org.jetbrains.kotlin.name.Name
internal class HierarchicalScope private constructor(private val scopes: List<KaScope>) {
/**
* Finds classifier symbols corresponding to the given name across the scope layers.
*
* Symbols from higher priority scope layers are returned first.
*
* Each list in the sequence represents classifiers found in a single scopes layer.
*/
fun findClassifiers(name: Name): Sequence<List<KaClassifierSymbol>> {
return scopes.asSequence()
.map { it.classifiers(name).toList() }
.filter { it.isNotEmpty() }
}
companion object {
fun KaSession.createFrom(scopeContext: KaScopeContext): HierarchicalScope {
val scopeGroupsSorted = scopeContext.scopes
.groupBy({ it.kind }, { it.scope })
.toSortedMap(compareByDescending { it.indexInTower })
val scopes = scopeGroupsSorted.values.map { it.asCompositeScope() }
return HierarchicalScope(scopes)
}
}
}
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.resolve.ImportPath
import kotlin.collections.filter
internal fun KaSession.buildOptimizedImports(
file: KtFile,
@@ -51,8 +52,8 @@ internal class OptimizedImportsBuilder(
val parentFqName = resultFqName.parent()
if (
alias == null
&& canUseStarImport(importableSymbol, resultFqName) &&
alias == null &&
canUseStarImport(importableSymbol, resultFqName) &&
ImportPath(parentFqName, isAllUnder = true).isAllowedByRules()
) {
symbolsByParentFqName.getOrPut(parentFqName) { hashSetOf() }.add(importableSymbol)
@@ -88,6 +89,30 @@ internal class OptimizedImportsBuilder(
}
}
val importingScopeContext = buildScopeContextByImports(file, importsToGenerate.filter { it.isAllUnder })
val importingScopes = HierarchicalScope.run { createFrom(importingScopeContext) }
for (fqName in classNamesToCheck) {
val foundClassifiers = importingScopes.findClassifiers(fqName.shortName()).firstOrNull()
val singleFoundClassifier = foundClassifiers?.singleOrNull()
if (singleFoundClassifier?.importableFqName != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val parentFqName = fqName.parent()
val siblingsToImport = symbolsByParentFqName.getValue(parentFqName)
for (descriptor in siblingsToImport.filter { it.run {computeImportableName() } == fqName }) {
siblingsToImport.remove(descriptor)
}
if (siblingsToImport.isEmpty()) { // star import is not really needed
importsToGenerate.remove(ImportPath(parentFqName, true))
}
}
}
val sortedImportsToGenerate = importsToGenerate.sortedWith(KotlinImportPathComparator.create(file))
val oldImports = file.importDirectives
@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.imports
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.components.KaScopeContext
import org.jetbrains.kotlin.psi.KtBlockCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.ImportPath
internal fun KaSession.buildScopeContextByImports(originalFile: KtFile, importsToGenerate: Collection<ImportPath>): KaScopeContext {
val fileWithImports = buildFileWithImports(originalFile, importsToGenerate)
return fileWithImports.importingScopeContext
}
private fun buildFileWithImports(
originalFile: KtFile,
importsToGenerate: Collection<ImportPath>
): KtFile {
val imports = buildString {
for (importPath in importsToGenerate) {
append("import ")
append(importPath)
append("\n")
}
}
// TODO this code fragment misses a package declaration
val fileWithImports = KtBlockCodeFragment(originalFile.project, "Dummy_" + originalFile.name, "", imports, originalFile)
return fileWithImports
}
@@ -579,6 +579,11 @@ public abstract class FirJvmOptimizeImportsTestGenerated extends AbstractFirJvmO
runTest("../../idea/tests/testData/editor/optimizeImports/common/Enums.kt");
}
@TestMetadata("ExplicitImportIsNotRemovedToAvoidConflict.kt")
public void testExplicitImportIsNotRemovedToAvoidConflict() throws Exception {
runTest("../../idea/tests/testData/editor/optimizeImports/common/ExplicitImportIsNotRemovedToAvoidConflict.kt");
}
@TestMetadata("ExtensionFunWithThisReference.kt")
public void testExtensionFunWithThisReference() throws Exception {
runTest("../../idea/tests/testData/editor/optimizeImports/common/ExtensionFunWithThisReference.kt");
@@ -311,6 +311,11 @@ public abstract class JsOptimizeImportsTestGenerated extends AbstractJsOptimizeI
runTest("testData/editor/optimizeImports/common/Enums.kt");
}
@TestMetadata("ExplicitImportIsNotRemovedToAvoidConflict.kt")
public void testExplicitImportIsNotRemovedToAvoidConflict() throws Exception {
runTest("testData/editor/optimizeImports/common/ExplicitImportIsNotRemovedToAvoidConflict.kt");
}
@TestMetadata("ExtensionFunWithThisReference.kt")
public void testExtensionFunWithThisReference() throws Exception {
runTest("testData/editor/optimizeImports/common/ExtensionFunWithThisReference.kt");
@@ -584,6 +584,11 @@ public abstract class JvmOptimizeImportsTestGenerated extends AbstractJvmOptimiz
runTest("testData/editor/optimizeImports/common/Enums.kt");
}
@TestMetadata("ExplicitImportIsNotRemovedToAvoidConflict.kt")
public void testExplicitImportIsNotRemovedToAvoidConflict() throws Exception {
runTest("testData/editor/optimizeImports/common/ExplicitImportIsNotRemovedToAvoidConflict.kt");
}
@TestMetadata("ExtensionFunWithThisReference.kt")
public void testExtensionFunWithThisReference() throws Exception {
runTest("testData/editor/optimizeImports/common/ExtensionFunWithThisReference.kt");
@@ -0,0 +1,7 @@
package dependencyFoo
class CommonClass
fun fooOne() {}
fun fooTwo() {}
fun fooThree() {}
@@ -0,0 +1,7 @@
package dependencyBar
class CommonClass
fun barOne() {}
fun barTwo() {}
fun barThree() {}
@@ -0,0 +1,22 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
package test
import dependencyBar.*
import dependencyFoo.*
import dependencyFoo.CommonClass
fun barUsage() {
barOne()
barTwo()
barThree()
}
fun fooUsage() {
fooOne()
fooTwo()
fooThree()
}
fun test() {
CommonClass()
}
@@ -0,0 +1,22 @@
// NAME_COUNT_TO_USE_STAR_IMPORT: 2
package test
import dependencyBar.*
import dependencyFoo.*
import dependencyFoo.CommonClass
fun barUsage() {
barOne()
barTwo()
barThree()
}
fun fooUsage() {
fooOne()
fooTwo()
fooThree()
}
fun test() {
CommonClass()
}