Optimize imports to prevent accedential changes in resolve because of import priorities

#KT-11640 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-09-15 15:30:56 +03:00
parent 7628fa1608
commit 95a3a29382
40 changed files with 651 additions and 191 deletions
@@ -26,17 +26,16 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.references.KtInvokeFunctionReference
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
@@ -45,10 +44,9 @@ import org.jetbrains.kotlin.resolve.scopes.utils.*
import java.util.*
class KotlinImportOptimizer() : ImportOptimizer {
override fun supports(file: PsiFile?) = file is KtFile
override fun processFile(file: PsiFile?) = Runnable() {
override fun processFile(file: PsiFile?) = Runnable {
OptimizeProcess(file as KtFile).execute()
}
@@ -69,12 +67,13 @@ class KotlinImportOptimizer() : ImportOptimizer {
}
}
private class CollectUsedDescriptorsVisitor(val file: KtFile) : KtVisitorVoid() {
private val _descriptors = HashSet<DeclarationDescriptor>()
private class CollectUsedDescriptorsVisitor(file: KtFile) : KtVisitorVoid() {
private val currentPackageName = file.packageFqName
private val descriptorsToImport = HashSet<DeclarationDescriptor>()
private val abstractRefs = ArrayList<OptimizedImportsBuilder.AbstractReference>()
val descriptors: Set<DeclarationDescriptor>
get() = _descriptors
val data: OptimizedImportsBuilder.InputData
get() = OptimizedImportsBuilder.InputData(descriptorsToImport, abstractRefs)
override fun visitElement(element: PsiElement) {
ProgressIndicatorProvider.checkCanceled()
@@ -90,14 +89,16 @@ class KotlinImportOptimizer() : ImportOptimizer {
override fun visitKtElement(element: KtElement) {
for (reference in element.references) {
if (reference !is KtReference) continue
abstractRefs.add(AbstractReferenceImpl(reference))
val referencedName = (element as? KtNameReferenceExpression)?.getReferencedNameAsName() //TODO: other types of references
val names = reference.resolvesByNames
val bindingContext = element.analyze()
//class qualifiers that refer to companion objects should be considered (containing) class references
val targets = bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) }
?: reference.resolveToDescriptors(bindingContext)
val targets = reference.targets(bindingContext)
for (target in targets) {
val importableDescriptor = target.getImportableDescriptor()
if (importableDescriptor.name.asString() !in names) continue // resolved via alias
val importableFqName = target.importableFqName ?: continue
val parentFqName = importableFqName.parent()
if (target is PackageViewDescriptor && parentFqName == FqName.ROOT) continue // no need to import top-level packages
@@ -105,13 +106,9 @@ class KotlinImportOptimizer() : ImportOptimizer {
if (!reference.canBeResolvedViaImport(target)) continue
val importableDescriptor = target.getImportableDescriptor()
if (referencedName != null && importableDescriptor.name != referencedName) continue // resolved via alias
if (isAccessibleAsMember(importableDescriptor, element, bindingContext)) continue
_descriptors.add(importableDescriptor)
descriptorsToImport.add(importableDescriptor)
}
}
@@ -145,25 +142,43 @@ class KotlinImportOptimizer() : ImportOptimizer {
}
return false
}
private class AbstractReferenceImpl(private val reference: KtReference) : OptimizedImportsBuilder.AbstractReference {
override val element: KtElement
get() = reference.element
override val dependsOnNames: Collection<Name>
get() {
val resolvesByNames = reference.resolvesByNames
if (reference is KtInvokeFunctionReference) {
val additionalNames = (reference.element.calleeExpression as? KtNameReferenceExpression)?.mainReference?.resolvesByNames
if (additionalNames != null) {
return (resolvesByNames + additionalNames).map { Name.identifier(it) }
}
}
return resolvesByNames.map { Name.identifier(it) }
}
override fun resolve(bindingContext: BindingContext) = reference.resolveToDescriptors(bindingContext)
override fun toString() = reference.toString()
}
}
companion object {
fun collectDescriptorsToImport(file: KtFile): Set<DeclarationDescriptor> {
fun collectDescriptorsToImport(file: KtFile): OptimizedImportsBuilder.InputData {
val visitor = CollectUsedDescriptorsVisitor(file)
file.accept(visitor)
return visitor.descriptors
return visitor.data
}
fun prepareOptimizedImports(file: KtFile,
descriptorsToImport: Collection<DeclarationDescriptor>): List<ImportPath>? {
fun prepareOptimizedImports(file: KtFile, data: OptimizedImportsBuilder.InputData): List<ImportPath>? {
val settings = KotlinCodeStyleSettings.getInstance(file.project)
return prepareOptimizedImports(
file,
descriptorsToImport,
val options = OptimizedImportsBuilder.Options(
settings.NAME_COUNT_TO_USE_STAR_IMPORT,
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS) { fqName ->
fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS
}
settings.NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS,
isInPackagesToUseStarImport = { fqName -> fqName.asString() in settings.PACKAGES_TO_USE_STAR_IMPORTS })
return OptimizedImportsBuilder(file, data, options).buildOptimizedImports()
}
fun replaceImports(file: KtFile, imports: List<ImportPath>) {
@@ -179,5 +194,11 @@ class KotlinImportOptimizer() : ImportOptimizer {
import.delete()
}
}
private fun KtReference.targets(bindingContext: BindingContext): Collection<DeclarationDescriptor> {
//class qualifiers that refer to companion objects should be considered (containing) class references
return bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) }
?: resolveToDescriptors(bindingContext)
}
}
}
@@ -41,9 +41,9 @@ import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.util.DocumentUtil
import com.intellij.util.Processor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer
import org.jetbrains.kotlin.idea.imports.OptimizedImportsBuilder
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCodeFragment
@@ -59,7 +59,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
if (!file.manager.isInProject(file)) return null
if (file.importDirectives.isEmpty()) return null
val descriptorsToImport = KotlinImportOptimizer.collectDescriptorsToImport(file)
val data = KotlinImportOptimizer.collectDescriptorsToImport(file)
val directives = file.importDirectives
val explicitlyImportedFqNames = directives
@@ -71,7 +71,7 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
val fqNames = HashSet<FqName>()
val parentFqNames = HashSet<FqName>()
for (descriptor in descriptorsToImport) {
for (descriptor in data.descriptorsToImport) {
val fqName = descriptor.importableFqName!!
fqNames.add(fqName)
@@ -119,15 +119,15 @@ class KotlinUnusedImportInspection : AbstractKotlinInspection() {
}
if (isOnTheFly) {
scheduleOptimizeImportsOnTheFly(file, descriptorsToImport)
scheduleOptimizeImportsOnTheFly(file, data)
}
return problems.toTypedArray()
}
private fun scheduleOptimizeImportsOnTheFly(file: KtFile, descriptorsToImport: Set<DeclarationDescriptor>) {
private fun scheduleOptimizeImportsOnTheFly(file: KtFile, data: OptimizedImportsBuilder.InputData) {
if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return
val optimizedImports = KotlinImportOptimizer.prepareOptimizedImports(file, descriptorsToImport) ?: return // return if already optimized
val optimizedImports = KotlinImportOptimizer.prepareOptimizedImports(file, data) ?: return // return if already optimized
// unwrap progress indicator
val progress = generateSequence(ProgressManager.getInstance().progressIndicator) {