diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt index 984a5969cf5..69bf8db6270 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinNameSuggester.kt @@ -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 { + val result = LinkedHashSet() + + 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 diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceImportAlias/KotlinIntroduceImportAliasHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceImportAlias/KotlinIntroduceImportAliasHandler.kt index f95efda81e9..8fc579ae17b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceImportAlias/KotlinIntroduceImportAliasHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceImportAlias/KotlinIntroduceImportAliasHandler.kt @@ -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, 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(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 +) { 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, newName: String) { diff --git a/idea/testData/intentions/introduceImportAlias/addImportWithDefaultClassCompanion2.kt.after b/idea/testData/intentions/introduceImportAlias/addImportWithDefaultClassCompanion2.kt.after index f02b0e9f981..c6effba5aa0 100644 --- a/idea/testData/intentions/introduceImportAlias/addImportWithDefaultClassCompanion2.kt.after +++ b/idea/testData/intentions/introduceImportAlias/addImportWithDefaultClassCompanion2.kt.after @@ -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 }