Move part of code that related to 'Optimize imports' action to ide-common module

This commit is contained in:
Mikhail Zarechenskiy
2016-04-26 20:34:44 +03:00
parent b677e9d2ea
commit 69b4837830
4 changed files with 153 additions and 141 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,48 @@
package org.jetbrains.kotlin.idea.imports
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.getFileResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.types.KotlinType
import java.util.*
object ImportPathComparator : Comparator<ImportPath> {
override fun compare(import1: ImportPath, import2: ImportPath): Int {
// alias imports placed last
if (import1.hasAlias() != import2.hasAlias()) {
return if (import1.hasAlias()) +1 else -1
}
// standard library imports last
val stdlib1 = isJavaOrKotlinStdlibImport(import1)
val stdlib2 = isJavaOrKotlinStdlibImport(import2)
if (stdlib1 != stdlib2) {
return if (stdlib1) +1 else -1
}
return import1.toString().compareTo(import2.toString())
}
private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean {
val s = path.pathStr
return s.startsWith("java.") || s.startsWith("javax.")|| s.startsWith("kotlin.")
}
}
val DeclarationDescriptor.importableFqName: FqName?
get() {
@@ -62,3 +96,118 @@ fun KtReferenceExpression.getImportableTargets(bindingContext: BindingContext):
?: getReferenceTargets(bindingContext)
return targets.map { it.getImportableDescriptor() }.toSet()
}
fun prepareOptimizedImports(
file: KtFile,
descriptorsToImport: Collection<DeclarationDescriptor>,
nameCountToUseStarImport: Int,
nameCountToUseStarImportForMembers: Int,
isInPackagesToUseStarImport: (FqName) -> Boolean
): List<ImportPath>? {
val importInsertHelper = ImportInsertHelper.getInstance(file.project)
val aliasImports = buildAliasImportMap(file)
val importsToGenerate = HashSet<ImportPath>()
val descriptorsByParentFqName = hashMapOf<FqName, MutableSet<DeclarationDescriptor>>()
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqName!!
val container = descriptor.containingDeclaration
val parentFqName = fqName.parent()
val canUseStarImport = when {
parentFqName.isRoot -> false
(container as? ClassDescriptor)?.kind == ClassKind.OBJECT -> false
else -> true
}
if (canUseStarImport) {
val descriptors = descriptorsByParentFqName.getOrPut(parentFqName) { hashSetOf() }
descriptors.add(descriptor)
}
else {
importsToGenerate.add(ImportPath(fqName, false))
}
}
val classNamesToCheck = HashSet<FqName>()
fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)
for (parentFqName in descriptorsByParentFqName.keys) {
val descriptors = descriptorsByParentFqName[parentFqName]!!
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
val isMember = descriptors.first().containingDeclaration is ClassDescriptor
val nameCountToUseStar = if (isMember)
nameCountToUseStarImportForMembers
else
nameCountToUseStarImport
val explicitImports = fqNames.size < nameCountToUseStar && !isInPackagesToUseStarImport(parentFqName)
if (explicitImports) {
for (fqName in fqNames) {
if (!isImportedByDefault(fqName)) {
importsToGenerate.add(ImportPath(fqName, false))
}
}
}
else {
for (descriptor in descriptors) {
if (descriptor is ClassDescriptor) {
classNamesToCheck.add(descriptor.importableFqName!!)
}
}
if (!fqNames.all(::isImportedByDefault)) {
importsToGenerate.add(ImportPath(parentFqName, true))
}
}
}
// now check that there are no conflicts and all classes are really imported
val fileWithImportsText = buildString {
append("package ").append(file.packageFqName.toUnsafe().render()).append("\n")
importsToGenerate.filter { it.isAllUnder }.map { "import " + it.pathStr }.joinTo(this, "\n")
}
val fileWithImports = KtPsiFactory(file).createAnalyzableFile("Dummy.kt", fileWithImportsText, file)
val scope = fileWithImports.getResolutionFacade().getFileResolutionScope(fileWithImports)
for (fqName in classNamesToCheck) {
if (scope.findClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqName != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val parentFqName = fqName.parent()
val parentDescriptors = descriptorsByParentFqName[parentFqName]!!
for (descriptor in parentDescriptors.filter { it.importableFqName == fqName }) {
parentDescriptors.remove(descriptor)
}
if (parentDescriptors.isEmpty()) { // star import is not really needed
importsToGenerate.remove(ImportPath(parentFqName, true))
}
}
}
//TODO: drop unused aliases?
aliasImports.mapTo(importsToGenerate) { ImportPath(it.value, false, it.key) }
val sortedImportsToGenerate = importsToGenerate.sortedWith(importInsertHelper.importSortComparator)
// check if no changes to imports required
val oldImports = file.importDirectives
if (oldImports.size == sortedImportsToGenerate.size && oldImports.map { it.importPath } == sortedImportsToGenerate) return null
return sortedImportsToGenerate
}
private fun buildAliasImportMap(file: KtFile): Map<Name, FqName> {
val imports = file.importDirectives
val aliasImports = HashMap<Name, FqName>()
for (import in imports) {
val path = import.importPath ?: continue
val aliasName = path.alias
if (aliasName != null && aliasName != path.fqnPart().shortName() /* we do not keep trivial aliases */) {
aliasImports.put(aliasName, path.fqnPart())
}
}
return aliasImports
}
@@ -166,121 +166,6 @@ class KotlinImportOptimizer() : ImportOptimizer {
}
}
fun prepareOptimizedImports(
file: KtFile,
descriptorsToImport: Collection<DeclarationDescriptor>,
nameCountToUseStarImport: Int,
nameCountToUseStarImportForMembers: Int,
isInPackagesToUseStarImport: (FqName) -> Boolean
): List<ImportPath>? {
val importInsertHelper = ImportInsertHelper.getInstance(file.project)
val aliasImports = buildAliasImportMap(file)
val importsToGenerate = HashSet<ImportPath>()
val descriptorsByParentFqName = hashMapOf<FqName, MutableSet<DeclarationDescriptor>>()
for (descriptor in descriptorsToImport) {
val fqName = descriptor.importableFqName!!
val container = descriptor.containingDeclaration
val parentFqName = fqName.parent()
val canUseStarImport = when {
parentFqName.isRoot -> false
(container as? ClassDescriptor)?.kind == ClassKind.OBJECT -> false
else -> true
}
if (canUseStarImport) {
val descriptors = descriptorsByParentFqName.getOrPut(parentFqName) { hashSetOf() }
descriptors.add(descriptor)
}
else {
importsToGenerate.add(ImportPath(fqName, false))
}
}
val classNamesToCheck = HashSet<FqName>()
fun isImportedByDefault(fqName: FqName) = importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)
for (parentFqName in descriptorsByParentFqName.keys) {
val descriptors = descriptorsByParentFqName[parentFqName]!!
val fqNames = descriptors.map { it.importableFqName!! }.toSet()
val isMember = descriptors.first().containingDeclaration is ClassDescriptor
val nameCountToUseStar = if (isMember)
nameCountToUseStarImportForMembers
else
nameCountToUseStarImport
val explicitImports = fqNames.size < nameCountToUseStar && !isInPackagesToUseStarImport(parentFqName)
if (explicitImports) {
for (fqName in fqNames) {
if (!isImportedByDefault(fqName)) {
importsToGenerate.add(ImportPath(fqName, false))
}
}
}
else {
for (descriptor in descriptors) {
if (descriptor is ClassDescriptor) {
classNamesToCheck.add(descriptor.importableFqName!!)
}
}
if (!fqNames.all(::isImportedByDefault)) {
importsToGenerate.add(ImportPath(parentFqName, true))
}
}
}
// now check that there are no conflicts and all classes are really imported
val fileWithImportsText = buildString {
append("package ").append(file.packageFqName.toUnsafe().render()).append("\n")
importsToGenerate.filter { it.isAllUnder }.map { "import " + it.pathStr }.joinTo(this, "\n")
}
val fileWithImports = KtPsiFactory(file).createAnalyzableFile("Dummy.kt", fileWithImportsText, file)
val scope = fileWithImports.getResolutionFacade().getFileResolutionScope(fileWithImports)
for (fqName in classNamesToCheck) {
if (scope.findClassifier(fqName.shortName(), NoLookupLocation.FROM_IDE)?.importableFqName != fqName) {
// add explicit import if failed to import with * (or from current package)
importsToGenerate.add(ImportPath(fqName, false))
val parentFqName = fqName.parent()
val parentDescriptors = descriptorsByParentFqName[parentFqName]!!
for (descriptor in parentDescriptors.filter { it.importableFqName == fqName }) {
parentDescriptors.remove(descriptor)
}
if (parentDescriptors.isEmpty()) { // star import is not really needed
importsToGenerate.remove(ImportPath(parentFqName, true))
}
}
}
//TODO: drop unused aliases?
aliasImports.mapTo(importsToGenerate) { ImportPath(it.value, false, it.key) }
val sortedImportsToGenerate = importsToGenerate.sortedWith(importInsertHelper.importSortComparator)
// check if no changes to imports required
val oldImports = file.importDirectives
if (oldImports.size == sortedImportsToGenerate.size && oldImports.map { it.importPath } == sortedImportsToGenerate) return null
return sortedImportsToGenerate
}
private fun buildAliasImportMap(file: KtFile): Map<Name, FqName> {
val imports = file.importDirectives
val aliasImports = HashMap<Name, FqName>()
for (import in imports) {
val path = import.importPath ?: continue
val aliasName = path.alias
if (aliasName != null && aliasName != path.fqnPart().shortName() /* we do not keep trivial aliases */) {
aliasImports.put(aliasName, path.fqnPart())
}
}
return aliasImports
}
fun replaceImports(file: KtFile, imports: List<ImportPath>) {
val importList = file.importList!!
val oldImports = importList.imports
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.imports.ImportPathComparator
import org.jetbrains.kotlin.idea.imports.getImportableTargets
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.platform
@@ -57,29 +58,6 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
override val importSortComparator: Comparator<ImportPath>
get() = ImportPathComparator
private object ImportPathComparator : Comparator<ImportPath> {
override fun compare(import1: ImportPath, import2: ImportPath): Int {
// alias imports placed last
if (import1.hasAlias() != import2.hasAlias()) {
return if (import1.hasAlias()) +1 else -1
}
// standard library imports last
val stdlib1 = isJavaOrKotlinStdlibImport(import1)
val stdlib2 = isJavaOrKotlinStdlibImport(import2)
if (stdlib1 != stdlib2) {
return if (stdlib1) +1 else -1
}
return import1.toString().compareTo(import2.toString())
}
private fun isJavaOrKotlinStdlibImport(path: ImportPath): Boolean {
val s = path.pathStr
return s.startsWith("java.") || s.startsWith("javax.")|| s.startsWith("kotlin.")
}
}
override fun isImportedWithDefault(importPath: ImportPath, contextFile: KtFile): Boolean {
val defaultImports = contextFile.platform.defaultModuleParameters.defaultImports
return importPath.isImported(defaultImports)