Improve: intention "Introduce Import Alias" should suggest new names for the new alias

#KT-30456 Fixed
This commit is contained in:
Dmitry Gridin
2019-03-18 22:57:51 +07:00
parent ff34788eb3
commit 9659453351
3 changed files with 70 additions and 36 deletions
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.lexer.KotlinLexer
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getOutermostParenthesizerOrThis
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
@@ -32,6 +33,8 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart
import java.util.*
@@ -107,6 +110,35 @@ object KotlinNameSuggester {
return result
}
fun suggestNamesByFqName(
fqName: FqName,
ignoreCompanion: Boolean = true,
validator: (String) -> Boolean = { true },
defaultName: () -> String? = { null }
): Collection<String> {
val result = LinkedHashSet<String>()
var name = ""
fqName.asString().split('.').asReversed().forEach {
if (ignoreCompanion && it == "Companion") return@forEach
name = name.withPrefix(it)
result.addName(name, validator)
}
if (result.isEmpty()) {
result.addName(defaultName(), validator)
}
return result
}
private fun String.withPrefix(prefix: String): String {
if (isEmpty()) return prefix
val c = this[0]
return (if (c in 'a'..'z') prefix.decapitalizeAsciiOnly()
else prefix.capitalizeAsciiOnly()) + capitalizeAsciiOnly()
}
private val COMMON_TYPE_PARAMETER_NAMES = listOf("T", "U", "V", "W", "X", "Y", "Z")
private const val MAX_NUMBER_OF_SUGGESTED_NAME_CHECKS = 1000
@@ -5,25 +5,22 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceImportAlias
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.RefactoringActionHandler
import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.rename.KotlinRenameDispatcherHandler
import org.jetbrains.kotlin.idea.refactoring.rename.findElementForRename
import org.jetbrains.kotlin.idea.refactoring.selectElement
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
@@ -33,22 +30,24 @@ import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinFunctionShortNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinPropertyShortNameIndex
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
import org.jetbrains.kotlin.idea.util.getAllAccessibleFunctions
import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.PropertyImportedFromObject
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
import org.jetbrains.kotlin.resolve.scopes.utils.findPackage
import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor
@@ -70,13 +69,27 @@ object KotlinIntroduceImportAliasHandler : RefactoringActionHandler {
}
}
val suggestedName = suggestedName(element.mainReference.value, file.getResolutionScope())
ImportInsertHelperImpl.addImport(project, file, fqName, false, Name.identifier(suggestedName))
replaceUsages(usages, suggestedName)
val oldName = element.mainReference.value
val scope = file.getResolutionScope()
val validator = fun(name: String): Boolean {
if (oldName == name) return false
val identifier = Name.identifier(name)
return scope.getAllAccessibleFunctions(identifier).isEmpty()
&& scope.getAllAccessibleVariables(identifier).isEmpty()
&& scope.findClassifier(identifier, NoLookupLocation.FROM_IDE) == null
&& scope.findPackage(identifier) == null
}
val suggestionsName = KotlinNameSuggester.suggestNamesByFqName(fqName, validator = validator)
val newName = suggestionsName.first()
val newDirective = ImportInsertHelperImpl.addImport(project, file, fqName, false, Name.identifier(newName))
replaceUsages(usages, newName)
cleanImport(file, fqName)
if (!ApplicationManager.getApplication().isUnitTestMode) {
invokeRename(project, editor, file)
invokeRename(project, editor, newDirective.alias, suggestionsName)
}
}
@@ -88,7 +101,7 @@ object KotlinIntroduceImportAliasHandler : RefactoringActionHandler {
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
throw AssertionError("${KotlinIntroduceImportAliasHandler.REFACTORING_NAME} can only be invoked from editor")
throw AssertionError("$REFACTORING_NAME can only be invoked from editor")
}
}
@@ -110,26 +123,15 @@ private fun findPsiElements(project: Project, file: KtFile, descriptor: Declarat
}.filter { fqName == it.fqName }
}
private fun suggestedName(oldName: String, scope: LexicalScope): String =
KotlinNameSuggester.suggestNameByName(oldName, fun(name: String): Boolean {
if (oldName == name) return false
val identifier = Name.identifier(name)
return scope.findVariable(identifier, NoLookupLocation.FROM_IDE) == null
&& scope.findFunction(identifier, NoLookupLocation.FROM_IDE) == null
&& scope.findClassifier(identifier, NoLookupLocation.FROM_IDE) == null
&& scope.findPackage(identifier) == null
})
private fun invokeRename(project: Project, editor: Editor, file: KtFile) {
val elementToRename = file.findElementForRename<KtSimpleNameExpression>(editor.caretModel.offset) ?: return
val dataContext = SimpleDataContext.getSimpleContext(
CommonDataKeys.PSI_ELEMENT.name,
elementToRename,
(editor as? EditorEx)?.dataContext
)
private fun invokeRename(
project: Project,
editor: Editor,
elementToRename: PsiNamedElement?,
suggestionsName: Collection<String>
) {
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
KotlinRenameDispatcherHandler().invoke(project, editor, file, dataContext)
val rename = VariableInplaceRenamer(elementToRename, editor, project)
rename.performInplaceRefactoring(LinkedHashSet(suggestionsName))
}
private fun replaceUsages(usages: List<UsageContext>, newName: String) {
@@ -1,7 +1,7 @@
import kotlin.Int.Companion as Companion1
import kotlin.Int.Companion as Int1
// WITH_RUNTIME
fun foo() {
val max = Companion1.MAX_VALUE
val max2 = Companion1.MAX_VALUE
val max = Int1.MAX_VALUE
val max2 = Int1.MAX_VALUE
}