Rewritten resolving conflicting imports on adding new import with star action (KT-18503)
#KT-18503 Fixed
This commit is contained in:
committed by
Nikolay Krasko
parent
ed5051a5e3
commit
5aa7216c13
@@ -220,26 +220,12 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addStarImport(target: DeclarationDescriptor): ImportDescriptorResult {
|
private fun addStarImport(targetDescriptor: DeclarationDescriptor): ImportDescriptorResult {
|
||||||
val targetFqName = target.importableFqName!!
|
val targetFqName = targetDescriptor.importableFqName!!
|
||||||
val parentFqName = targetFqName.parent()
|
val parentFqName = targetFqName.parent()
|
||||||
|
|
||||||
val moduleDescriptor = resolutionFacade.moduleDescriptor
|
val moduleDescriptor = resolutionFacade.moduleDescriptor
|
||||||
val imports = file.importDirectives
|
|
||||||
val scopeToImport = getMemberScope(parentFqName, moduleDescriptor) ?: return ImportDescriptorResult.FAIL
|
val scopeToImport = getMemberScope(parentFqName, moduleDescriptor) ?: return ImportDescriptorResult.FAIL
|
||||||
val importedScopes = imports
|
|
||||||
.asSequence()
|
|
||||||
.filter { it.isAllUnder }
|
|
||||||
.mapNotNull {
|
|
||||||
val importPath = it.importPath
|
|
||||||
if (importPath != null) {
|
|
||||||
val fqName = importPath.fqName
|
|
||||||
getMemberScope(fqName, moduleDescriptor)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
val filePackage = moduleDescriptor.getPackage(file.packageFqName)
|
val filePackage = moduleDescriptor.getPackage(file.packageFqName)
|
||||||
|
|
||||||
@@ -249,38 +235,57 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
|
|||||||
return !visibility.mustCheckInImports() || Visibilities.isVisibleIgnoringReceiver(descriptor, filePackage)
|
return !visibility.mustCheckInImports() || Visibilities.isVisibleIgnoringReceiver(descriptor, filePackage)
|
||||||
}
|
}
|
||||||
|
|
||||||
val classNamesToImport = scopeToImport
|
val kindFilter = DescriptorKindFilter.ALL.withoutKinds(DescriptorKindFilter.PACKAGES_MASK)
|
||||||
.getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS, { true })
|
val allNamesToImport = scopeToImport.getDescriptorsFiltered(kindFilter).filter(::isVisible).map { it.name }.toSet()
|
||||||
.filter(::isVisible)
|
|
||||||
.map { it.name }
|
|
||||||
|
|
||||||
val topLevelScope = resolutionFacade.getFileResolutionScope(file)
|
fun targetFqNameAndType(ref: KtReferenceExpression): Pair<FqName, Class<out Any>>? {
|
||||||
val conflictCandidates: List<ClassifierDescriptor> = classNamesToImport
|
val descriptors = ref.resolveTargets()
|
||||||
.flatMap {
|
val fqName: FqName? = descriptors.filter(::isVisible). map { it.importableFqName }.toSet().singleOrNull()
|
||||||
importedScopes.mapNotNull { scope -> scope.getContributedClassifier(it, NoLookupLocation.FROM_IDE) }
|
return if (fqName != null) {
|
||||||
|
Pair(fqName, descriptors.elementAt(0).javaClass)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
val futureCheckMap = HashMap<KtSimpleNameExpression, Pair<FqName, Class<out Any>>>()
|
||||||
|
file.accept(object : KtVisitorVoid() {
|
||||||
|
override fun visitElement(element: PsiElement): Unit = element.acceptChildren(this)
|
||||||
|
override fun visitImportList(importList: KtImportList) {}
|
||||||
|
override fun visitPackageDirective(directive: KtPackageDirective) {}
|
||||||
|
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||||
|
val refName = expression.getReferencedNameAsName()
|
||||||
|
if (allNamesToImport.contains(refName)) {
|
||||||
|
val target = targetFqNameAndType(expression)
|
||||||
|
if (target != null) {
|
||||||
|
futureCheckMap += Pair(expression, target)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.filter { importedClass ->
|
}
|
||||||
isVisible(importedClass)
|
})
|
||||||
// check that class is really imported
|
|
||||||
&& topLevelScope.findClassifier(importedClass.name, NoLookupLocation.FROM_IDE) == importedClass
|
|
||||||
// and not yet imported explicitly
|
|
||||||
&& imports.all { it.importPath != ImportPath(importedClass.importableFqName!!, false) }
|
|
||||||
}
|
|
||||||
val conflicts = detectNeededImports(conflictCandidates)
|
|
||||||
|
|
||||||
val addedImport = addImport(parentFqName, true)
|
val addedImport = addImport(parentFqName, true)
|
||||||
|
|
||||||
val newTopLevelScope = resolutionFacade.getFileResolutionScope(file)
|
if (!isAlreadyImported(targetDescriptor, resolutionFacade.getFileResolutionScope(file), targetFqName)) {
|
||||||
if (!isAlreadyImported(target, newTopLevelScope, targetFqName)) {
|
|
||||||
addedImport.delete()
|
addedImport.delete()
|
||||||
return ImportDescriptorResult.FAIL
|
return ImportDescriptorResult.FAIL
|
||||||
}
|
}
|
||||||
|
|
||||||
for (conflict in conflicts) {
|
dropRedundantExplicitImports(parentFqName)
|
||||||
addImport(DescriptorUtils.getFqNameSafe(conflict), false)
|
|
||||||
|
val conflicts = futureCheckMap
|
||||||
|
.mapNotNull { (expr, fqNameAndType) ->
|
||||||
|
if (targetFqNameAndType(expr) != fqNameAndType) fqNameAndType.first else null
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
fun isNotImported(fqName: FqName): Boolean {
|
||||||
|
return file.importDirectives.none { directive ->
|
||||||
|
!directive.isAllUnder && directive.alias == null && directive.importedFqName == fqName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dropRedundantExplicitImports(parentFqName)
|
for (conflict in conflicts.filter(::isNotImported)) {
|
||||||
|
addImport(conflict, false)
|
||||||
|
}
|
||||||
|
|
||||||
return ImportDescriptorResult.IMPORT_ADDED
|
return ImportDescriptorResult.IMPORT_ADDED
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ package p
|
|||||||
|
|
||||||
import dependency.*
|
import dependency.*
|
||||||
|
|
||||||
val d = Date(1, 2)
|
val d = Date("") //import dependency.Date is not necessary
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
package p
|
package p
|
||||||
|
|
||||||
import dependency.*
|
import dependency.*
|
||||||
import dependency.Date
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
val d = Date(1, 2)
|
val d = Date("") //import dependency.Date is not necessary
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package dependency
|
||||||
|
|
||||||
|
class Date
|
||||||
|
|
||||||
|
fun Date(s: String) = Date()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// IMPORT: java.util.Calendar
|
||||||
|
package p
|
||||||
|
|
||||||
|
import dependency.*
|
||||||
|
|
||||||
|
val d = Date()
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// IMPORT: java.util.Calendar
|
||||||
|
package p
|
||||||
|
|
||||||
|
import dependency.*
|
||||||
|
import dependency.Date
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val d = Date()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package conflicts.extensions
|
||||||
|
|
||||||
|
fun Byte.inv(): Byte = (255 - this).toByte()
|
||||||
|
|
||||||
|
fun foo1() {}
|
||||||
|
fun foo2() {}
|
||||||
|
fun foo3() {}
|
||||||
|
fun foo4() {}
|
||||||
|
fun foo5() {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package conflicts.extensions.deps
|
||||||
|
|
||||||
|
fun Byte.inv(): Byte = (255 - this).toByte()
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
//IMPORT: conflicts.extensions.foo5
|
||||||
|
package p
|
||||||
|
|
||||||
|
import conflicts.extensions.foo1
|
||||||
|
import conflicts.extensions.foo2
|
||||||
|
import conflicts.extensions.foo3
|
||||||
|
import conflicts.extensions.foo4
|
||||||
|
import conflicts.extensions.deps.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val b: Byte = 1
|
||||||
|
b.inv()
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
//IMPORT: conflicts.extensions.foo5
|
||||||
|
package p
|
||||||
|
|
||||||
|
import conflicts.extensions.*
|
||||||
|
import conflicts.extensions.deps.*
|
||||||
|
import conflicts.extensions.deps.inv
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val b: Byte = 1
|
||||||
|
b.inv()
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package conflicts.extensions
|
||||||
|
|
||||||
|
fun Byte.inv(): Byte = (255 - this).toByte()
|
||||||
|
|
||||||
|
fun foo1() {}
|
||||||
|
fun foo2() {}
|
||||||
|
fun foo3() {}
|
||||||
|
fun foo4() {}
|
||||||
|
fun foo5() {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package conflicts.extensions.deps
|
||||||
|
|
||||||
|
fun Byte.inv(max: Int): Byte = (max - this).toByte()
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
//IMPORT: conflicts.extensions.foo5
|
||||||
|
package p
|
||||||
|
|
||||||
|
import conflicts.extensions.foo1
|
||||||
|
import conflicts.extensions.foo2
|
||||||
|
import conflicts.extensions.foo3
|
||||||
|
import conflicts.extensions.foo4
|
||||||
|
import conflicts.extensions.deps.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val b: Byte = 1
|
||||||
|
b.inv(255)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//IMPORT: conflicts.extensions.foo5
|
||||||
|
package p
|
||||||
|
|
||||||
|
import conflicts.extensions.*
|
||||||
|
import conflicts.extensions.deps.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val b: Byte = 1
|
||||||
|
b.inv(255)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package dependency
|
||||||
|
|
||||||
|
typealias Date = String
|
||||||
|
|
||||||
|
fun util1() = 1
|
||||||
|
fun util2() = 2
|
||||||
|
fun util3() = 3
|
||||||
|
fun util4() = 4
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// IMPORT: dependency.util4
|
||||||
|
package p
|
||||||
|
|
||||||
|
import dependency.util1
|
||||||
|
import dependency.util2
|
||||||
|
import dependency.util3
|
||||||
|
import dependency.Date
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val aMap: AbstractMap<Int, Int>? = null
|
||||||
|
val calendar: Calendar? = null
|
||||||
|
|
||||||
|
fun klass(): Date {
|
||||||
|
util1()
|
||||||
|
util2()
|
||||||
|
util3()
|
||||||
|
return Date()
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// IMPORT: dependency.util4
|
||||||
|
package p
|
||||||
|
|
||||||
|
import dependency.*
|
||||||
|
import dependency.Date
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val aMap: AbstractMap<Int, Int>? = null
|
||||||
|
val calendar: Calendar? = null
|
||||||
|
|
||||||
|
fun klass(): Date {
|
||||||
|
util1()
|
||||||
|
util2()
|
||||||
|
util3()
|
||||||
|
return Date()
|
||||||
|
}
|
||||||
@@ -89,6 +89,12 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
|
|||||||
runTest("idea/testData/addImport/ConflictingNameAppearsAndHasUsage6.kt");
|
runTest("idea/testData/addImport/ConflictingNameAppearsAndHasUsage6.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ConflictingNameAppearsAndHasUsage7.kt")
|
||||||
|
public void testConflictingNameAppearsAndHasUsage7() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictingNameAppearsAndHasUsage7.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("ConflictingNameAppearsButUsageIsQualified.kt")
|
@TestMetadata("ConflictingNameAppearsButUsageIsQualified.kt")
|
||||||
public void testConflictingNameAppearsButUsageIsQualified() throws Exception {
|
public void testConflictingNameAppearsButUsageIsQualified() throws Exception {
|
||||||
runTest("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt");
|
runTest("idea/testData/addImport/ConflictingNameAppearsButUsageIsQualified.kt");
|
||||||
@@ -129,6 +135,24 @@ public class AddImportTestGenerated extends AbstractAddImportTest {
|
|||||||
runTest("idea/testData/addImport/ConflictingNameNoAllUnderImport3.kt");
|
runTest("idea/testData/addImport/ConflictingNameNoAllUnderImport3.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ConflictsExtensions.kt")
|
||||||
|
public void testConflictsExtensions() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictsExtensions.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ConflictsExtensions1.kt")
|
||||||
|
public void testConflictsExtensions1() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/ConflictsExtensions1.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("DoNotDropConflictingOnStar.kt")
|
||||||
|
public void testDoNotDropConflictingOnStar() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/addImport/DoNotDropConflictingOnStar.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("DropExplicitImports.kt")
|
@TestMetadata("DropExplicitImports.kt")
|
||||||
public void testDropExplicitImports() throws Exception {
|
public void testDropExplicitImports() throws Exception {
|
||||||
runTest("idea/testData/addImport/DropExplicitImports.kt");
|
runTest("idea/testData/addImport/DropExplicitImports.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user