Move part of code that related to 'Optimize imports' action to ide-common module
This commit is contained in:
+1
-1
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -19,14 +19,48 @@
|
|||||||
package org.jetbrains.kotlin.idea.imports
|
package org.jetbrains.kotlin.idea.imports
|
||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
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.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.psi.KtReferenceExpression
|
||||||
|
import org.jetbrains.kotlin.renderer.render
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.ImportPath
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
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?
|
val DeclarationDescriptor.importableFqName: FqName?
|
||||||
get() {
|
get() {
|
||||||
@@ -62,3 +96,118 @@ fun KtReferenceExpression.getImportableTargets(bindingContext: BindingContext):
|
|||||||
?: getReferenceTargets(bindingContext)
|
?: getReferenceTargets(bindingContext)
|
||||||
return targets.map { it.getImportableDescriptor() }.toSet()
|
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>) {
|
fun replaceImports(file: KtFile, imports: List<ImportPath>) {
|
||||||
val importList = file.importList!!
|
val importList = file.importList!!
|
||||||
val oldImports = importList.imports
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
|
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
|
||||||
import org.jetbrains.kotlin.idea.core.targetDescriptors
|
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.getImportableTargets
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
import org.jetbrains.kotlin.idea.project.platform
|
import org.jetbrains.kotlin.idea.project.platform
|
||||||
@@ -57,29 +58,6 @@ class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper(
|
|||||||
override val importSortComparator: Comparator<ImportPath>
|
override val importSortComparator: Comparator<ImportPath>
|
||||||
get() = ImportPathComparator
|
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 {
|
override fun isImportedWithDefault(importPath: ImportPath, contextFile: KtFile): Boolean {
|
||||||
val defaultImports = contextFile.platform.defaultModuleParameters.defaultImports
|
val defaultImports = contextFile.platform.defaultModuleParameters.defaultImports
|
||||||
return importPath.isImported(defaultImports)
|
return importPath.isImported(defaultImports)
|
||||||
|
|||||||
Reference in New Issue
Block a user