181: Misc: Refine nullability to match changes in the platform

This commit is contained in:
Alexey Sedunov
2018-01-23 18:21:52 +03:00
committed by Nikolay Krasko
parent 8ce65dc715
commit 7988fcabcc
7 changed files with 1147 additions and 0 deletions
@@ -0,0 +1,141 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.SmartList
import java.util.*
class RenameKotlinClassifierProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement): Boolean {
return element is KtClassOrObject || element is KtLightClass || element is KtConstructor<*> || element is KtTypeAlias
}
override fun isToSearchInComments(psiElement: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS
override fun setToSearchInComments(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS = enabled
}
override fun isToSearchForTextOccurrences(element: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS
override fun setToSearchForTextOccurrences(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS = enabled
}
override fun substituteElementToRename(element: PsiElement, editor: Editor?) = getClassOrObject(element)
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>) {
super.prepareRenaming(element, newName, allRenames)
val classOrObject = getClassOrObject(element) as? KtClassOrObject ?: return
val file = classOrObject.containingKtFile
val virtualFile = file.virtualFile
if (virtualFile != null) {
val nameWithoutExtensions = virtualFile.nameWithoutExtension
if (nameWithoutExtensions == classOrObject.name) {
val newFileName = newName + "." + virtualFile.extension
allRenames.put(file, newFileName)
RenamePsiElementProcessor.forElement(file).prepareRenaming(file, newFileName, allRenames)
}
}
}
override fun findReferences(element: PsiElement): Collection<PsiReference> {
if (element is KtObjectDeclaration && element.isCompanion()) {
return super.findReferences(element).filter { !it.isCompanionObjectClassReference() }
}
return super.findReferences(element)
}
private fun PsiReference.isCompanionObjectClassReference(): Boolean {
if (this !is KtSimpleNameReference) {
return false
}
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
return bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element] != null
}
override fun findCollisions(
element: PsiElement,
newName: String,
allRenames: MutableMap<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return
val descriptor = declaration.unsafeResolveToDescriptor() as ClassifierDescriptor
val collisions = SmartList<UsageInfo>()
checkRedeclarations(descriptor, newName, collisions)
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
checkNewNameUsagesRetargeting(declaration, newName, collisions)
result += collisions
}
private fun getClassOrObject(element: PsiElement?): PsiElement? = when (element) {
is KtLightClass ->
when (element) {
is KtLightClassForSourceDeclaration -> element.kotlinOrigin
is KtLightClassForFacade -> element
else -> throw AssertionError("Should not be suggested to rename element of type " + element::class.java + " " + element)
}
is KtConstructor<*> ->
element.getContainingClassOrObject()
is KtClassOrObject, is KtTypeAlias -> element
else -> null
}
override fun renameElement(element: PsiElement, newName: String, usages: Array<out UsageInfo>, listener: RefactoringElementListener?) {
val simpleUsages = ArrayList<UsageInfo>(usages.size)
val ambiguousImportUsages = com.intellij.util.SmartList<UsageInfo>()
for (usage in usages) {
if (usage.isAmbiguousImportUsage()) {
ambiguousImportUsages += usage
}
else {
simpleUsages += usage
}
}
element.ambiguousImportUsages = ambiguousImportUsages
super.renameElement(element, newName, simpleUsages.toTypedArray(), listener)
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.refactoring.rename.RenamePsiFileProcessor
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.psi.KtFile
class RenameKotlinFileProcessor : RenamePsiFileProcessor() {
class FileRenamingPsiClassWrapper(
private val psiClass: KtLightClass,
private val file: KtFile
) : KtLightClass by psiClass {
override fun isValid() = file.isValid
}
override fun canProcessElement(element: PsiElement) = element is KtFile && ProjectRootsUtil.isInProjectSource(element)
override fun prepareRenaming(element: PsiElement,
newName: String,
allRenames: MutableMap<PsiElement, String>,
scope: SearchScope) {
val jetFile = element as? KtFile ?: return
if (FileTypeManager.getInstance().getFileTypeByFileName(newName) != KotlinFileType.INSTANCE) {
return
}
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
val fileInfo = JvmFileClassUtil.getFileClassInfoNoResolve(jetFile)
if (!fileInfo.withJvmName) {
val facadeFqName = fileInfo.facadeClassFqName
val project = jetFile.project
val facadeClass = JavaPsiFacade.getInstance(project)
.findClass(facadeFqName.asString(), GlobalSearchScope.moduleScope(module)) as? KtLightClass
if (facadeClass != null) {
allRenames[FileRenamingPsiClassWrapper(facadeClass, jetFile)] = PackagePartClassUtils.getFilePartShortName(newName)
}
}
}
}
@@ -0,0 +1,245 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pass
import com.intellij.psi.*
import com.intellij.psi.search.SearchScope
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.rename.RenameDialog
import com.intellij.refactoring.rename.RenameJavaMethodProcessor
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.refactoring.rename.RenameUtil
import com.intellij.refactoring.util.RefactoringUtil
import com.intellij.usageView.UsageInfo
import com.intellij.util.SmartList
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.Pass
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethods
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethodsWithPopup
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.search.declarationsSearch.findDeepestSuperMethodsKotlinAware
import org.jetbrains.kotlin.idea.search.declarationsSearch.findDeepestSuperMethodsNoWrapping
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingMethod
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import java.lang.IllegalStateException
import java.util.*
class RenameKotlinFunctionProcessor : RenameKotlinPsiProcessor() {
private val javaMethodProcessorInstance = RenameJavaMethodProcessor()
override fun canProcessElement(element: PsiElement): Boolean {
return element is KtNamedFunction || (element is KtLightMethod && element.kotlinOrigin is KtNamedFunction) || element is FunctionWithSupersWrapper
}
override fun isToSearchInComments(psiElement: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD
override fun setToSearchInComments(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD = enabled
}
override fun isToSearchForTextOccurrences(element: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD
override fun setToSearchForTextOccurrences(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD = enabled
}
private fun getJvmName(element: PsiElement): String? {
val descriptor = (element.unwrapped as? KtFunction)?.unsafeResolveToDescriptor() as? FunctionDescriptor ?: return null
return DescriptorUtils.getJvmName(descriptor)
}
override fun findReferences(element: PsiElement): Collection<PsiReference> {
val allReferences = super.findReferences(element)
return when {
getJvmName(element) == null -> allReferences
element is KtElement -> allReferences.filter { it is KtReference }
element is KtLightElement<*, *> -> allReferences.filterNot { it is KtReference }
else -> emptyList()
}
}
override fun findCollisions(
element: PsiElement,
newName: String,
allRenames: Map<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
val declaration = element.unwrapped as? KtNamedFunction ?: return
val descriptor = declaration.unsafeResolveToDescriptor()
checkConflictsAndReplaceUsageInfos(element, allRenames, result)
result += SmartList<UsageInfo>().also { collisions ->
checkRedeclarations(descriptor, newName, collisions)
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
checkNewNameUsagesRetargeting(declaration, newName, collisions)
}
}
class FunctionWithSupersWrapper(
val originalDeclaration: KtNamedFunction,
val supers: List<PsiElement>
) : KtLightElement<KtNamedFunction, KtNamedFunction>, PsiNamedElement by originalDeclaration {
override val kotlinOrigin: KtNamedFunction?
get() = originalDeclaration
override val clsDelegate: KtNamedFunction
get() = originalDeclaration
}
private fun substituteForExpectOrActual(element: PsiElement?) = (element?.namedUnwrappedElement as? KtNamedDeclaration)?.liftToExpected()
override fun substituteElementToRename(element: PsiElement, editor: Editor?): PsiElement? {
substituteForExpectOrActual(element)?.let { return it }
val wrappedMethod = wrapPsiMethod(element) ?: return element
val deepestSuperMethods = findDeepestSuperMethodsKotlinAware(wrappedMethod)
val substitutedJavaElement = when {
deepestSuperMethods.isEmpty() -> return element
wrappedMethod.isConstructor || deepestSuperMethods.size == 1 || element !is KtNamedFunction -> {
javaMethodProcessorInstance.substituteElementToRename(wrappedMethod, editor)
}
else -> {
val chosenElements = checkSuperMethods(element, null, "rename")
if (chosenElements.size > 1) FunctionWithSupersWrapper(element, chosenElements) else wrappedMethod
}
}
if (substitutedJavaElement is KtLightMethod && element is KtDeclaration) {
return substitutedJavaElement.kotlinOrigin as? KtNamedFunction
}
return substitutedJavaElement
}
override fun substituteElementToRename(element: PsiElement, editor: Editor, renameCallback: Pass<PsiElement>) {
fun preprocessAndPass(substitutedJavaElement: PsiElement) {
val elementToProcess = if (substitutedJavaElement is KtLightMethod && element is KtDeclaration) {
substitutedJavaElement.kotlinOrigin as? KtNamedFunction
}
else {
substitutedJavaElement
}
renameCallback.pass(elementToProcess)
}
substituteForExpectOrActual(element)?.let { return preprocessAndPass(it) }
val wrappedMethod = wrapPsiMethod(element)
val deepestSuperMethods = if (wrappedMethod != null) {
findDeepestSuperMethodsKotlinAware(wrappedMethod)
} else {
findDeepestSuperMethodsNoWrapping(element)
}
when {
deepestSuperMethods.isEmpty() -> preprocessAndPass(element)
wrappedMethod != null && (wrappedMethod.isConstructor || element !is KtNamedFunction) -> {
javaMethodProcessorInstance.substituteElementToRename(wrappedMethod, editor, Pass(::preprocessAndPass))
}
else -> {
val declaration = element.unwrapped as? KtNamedFunction ?: return
checkSuperMethodsWithPopup(declaration, deepestSuperMethods.toList(), "Rename", editor) {
preprocessAndPass(if (it.size > 1) FunctionWithSupersWrapper(declaration, it) else wrappedMethod ?: element)
}
}
}
}
override fun createRenameDialog(project: Project, element: PsiElement, nameSuggestionContext: PsiElement?, editor: Editor?): RenameDialog {
val elementForDialog = (element as? FunctionWithSupersWrapper)?.originalDeclaration ?: element
return object : RenameDialog(project, elementForDialog, nameSuggestionContext, editor) {
override fun createRenameProcessor(newName: String) = RenameProcessor(getProject(), element, newName, isSearchInComments, isSearchInNonJavaFiles)
}
}
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
super.prepareRenaming(element, newName, allRenames, scope)
if (element is KtLightMethod && getJvmName(element) == null) {
(element.kotlinOrigin as? KtNamedFunction)?.let { allRenames[it] = newName }
}
if (element is FunctionWithSupersWrapper) {
allRenames.remove(element)
}
for (declaration in ((element as? FunctionWithSupersWrapper)?.supers ?: listOf(element))) {
val psiMethod = wrapPsiMethod(declaration) ?: continue
allRenames[declaration] = newName
if (psiMethod.containingClass != null) {
psiMethod.forEachOverridingMethod { it ->
val overrider = (it as? PsiMirrorElement)?.prototype as? PsiMethod ?: it
if (overrider is SyntheticElement) return@forEachOverridingMethod true
val overriderName = overrider.name
val baseName = psiMethod.name
val newOverriderName = RefactoringUtil.suggestNewOverriderName(overriderName, baseName, newName)
if (newOverriderName != null) {
RenameProcessor.assertNonCompileElement(overrider)
allRenames.put(overrider, newOverriderName)
}
return@forEachOverridingMethod true
}
javaMethodProcessorInstance.prepareRenaming(psiMethod, newName, allRenames, scope)
}
}
}
override fun renameElement(element: PsiElement, newName: String, usages: Array<UsageInfo>, listener: RefactoringElementListener?) {
val simpleUsages = ArrayList<UsageInfo>(usages.size)
val ambiguousImportUsages = SmartList<UsageInfo>()
for (usage in usages) {
if (usage is LostDefaultValuesInOverridingFunctionUsageInfo) {
usage.apply()
continue
}
if (usage.isAmbiguousImportUsage()) {
ambiguousImportUsages += usage
}
else {
simpleUsages += usage
}
}
element.ambiguousImportUsages = ambiguousImportUsages
RenameUtil.doRenameGenericNamedElement(element, newName, simpleUsages.toTypedArray(), listener)
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
(element.unwrapped as? KtNamedDeclaration)?.let(::dropOverrideKeywordIfNecessary)
}
private fun wrapPsiMethod(element: PsiElement?): PsiMethod? = when (element) {
is PsiMethod -> element
is KtNamedFunction, is KtSecondaryConstructor -> runReadAction {
LightClassUtil.getLightClassMethod(element as KtFunction)
}
else -> throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.psi.PsiElement
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.utils.SmartList
class RenameKotlinParameterProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement) = element is KtParameter && element.ownerFunction is KtFunction
override fun isToSearchInComments(psiElement: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE
override fun setToSearchInComments(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled
}
override fun findCollisions(
element: PsiElement,
newName: String,
allRenames: MutableMap<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return
val descriptor = declaration.unsafeResolveToDescriptor() as VariableDescriptor
val collisions = SmartList<UsageInfo>()
checkRedeclarations(descriptor, newName, collisions)
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
checkNewNameUsagesRetargeting(declaration, newName, collisions)
result += collisions
}
override fun renameElement(element: PsiElement, newName: String, usages: Array<out UsageInfo>, listener: RefactoringElementListener?) {
super.renameElement(element, newName, usages, listener)
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
}
}
@@ -0,0 +1,478 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.navigation.NavigationItem
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.Pass
import com.intellij.psi.*
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.DirectClassInheritorsSearch
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.refactoring.util.MoveRenameUsageInfo
import com.intellij.refactoring.util.RefactoringUtil
import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewUtil
import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.core.isEnumCompanionPropertyWithEntryConflict
import org.jetbrains.kotlin.idea.core.unquote
import org.jetbrains.kotlin.idea.refactoring.checkSuperMethodsWithPopup
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.findPropertyByName
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.SmartList
class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement): Boolean {
val namedUnwrappedElement = element.namedUnwrappedElement
return namedUnwrappedElement is KtProperty || (namedUnwrappedElement is KtParameter && namedUnwrappedElement.hasValOrVar())
}
override fun isToSearchInComments(psiElement: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD
override fun setToSearchInComments(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_FIELD = enabled
}
override fun isToSearchForTextOccurrences(element: PsiElement) = JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_FIELD
override fun setToSearchForTextOccurrences(element: PsiElement, enabled: Boolean) {
JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_FIELD = enabled
}
private fun getJvmNames(element: PsiElement): Pair<String?, String?> {
val descriptor = (element.unwrapped as? KtDeclaration)?.unsafeResolveToDescriptor() as? PropertyDescriptor ?: return null to null
val getterName = descriptor.getter?.let { DescriptorUtils.getJvmName(it) }
val setterName = descriptor.setter?.let { DescriptorUtils.getJvmName(it) }
return getterName to setterName
}
override fun findReferences(element: PsiElement): Collection<PsiReference> {
val allReferences = super.findReferences(element).filterNot { it is KtDestructuringDeclarationReference }
val (getterJvmName, setterJvmName) = getJvmNames(element)
return when {
getterJvmName == null && setterJvmName == null -> allReferences
element is KtElement -> allReferences.filter {
it is KtReference
|| (getterJvmName == null && (it.resolve() as? PsiNamedElement)?.name != setterJvmName)
|| (setterJvmName == null && (it.resolve() as? PsiNamedElement)?.name != getterJvmName)
}
element is KtLightDeclaration<*, *> -> {
val name = element.name
if (name == getterJvmName || name == setterJvmName) allReferences.filterNot { it is KtReference } else allReferences
}
else -> emptyList()
}
}
private fun checkAccidentalOverrides(
declaration: KtNamedDeclaration,
newName: String,
descriptor: VariableDescriptor,
result: MutableList<UsageInfo>
) {
fun reportAccidentalOverride(candidate: PsiNamedElement) {
val what = UsageViewUtil.getType(declaration).capitalize()
val withWhat = candidate.renderDescription()
val where = candidate.representativeContainer()?.renderDescription() ?: return
val message = "$what after rename will clash with existing $withWhat in $where"
result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message)
}
if (descriptor !is PropertyDescriptor) return
val initialClass = declaration.containingClassOrObject ?: return
val initialClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return
val prototype = object : PropertyDescriptor by descriptor {
override fun getName() = Name.guessByFirstCharacter(newName)
}
DFS.dfs(
listOf(initialClassDescriptor),
DFS.Neighbors<ClassDescriptor> { DescriptorUtils.getSuperclassDescriptors(it) },
object : DFS.AbstractNodeHandler<ClassDescriptor, Unit>() {
override fun beforeChildren(current: ClassDescriptor): Boolean {
if (current == initialClassDescriptor) return true
(current.findCallableMemberBySignature(prototype))?.let { candidateDescriptor ->
val candidate = candidateDescriptor.source.getPsi() as? PsiNamedElement ?: return false
reportAccidentalOverride(candidate)
return false
}
return true
}
override fun result() {
}
}
)
if (!declaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) {
val initialPsiClass = initialClass.toLightClass() ?: return
val prototypes = declaration.toLightMethods().mapNotNull {
it as KtLightMethod
val methodName = accessorNameByPropertyName(newName, it) ?: return@mapNotNull null
object : KtLightMethod by it {
override fun getName() = methodName
}
}
DFS.dfs(
listOf(initialPsiClass),
DFS.Neighbors<PsiClass> { DirectClassInheritorsSearch.search(it) },
object : DFS.AbstractNodeHandler<PsiClass, Unit>() {
override fun beforeChildren(current: PsiClass): Boolean {
if (current == initialPsiClass) return true
if (current is KtLightClass) {
val property = current.kotlinOrigin?.findPropertyByName(newName) ?: return true
reportAccidentalOverride(property)
return false
}
for (psiMethod in prototypes) {
current.findMethodBySignature(psiMethod, false)?.let {
val candidate = it.unwrapped as? PsiNamedElement ?: return true
reportAccidentalOverride(candidate)
return false
}
}
return true
}
override fun result() {
}
}
)
}
}
override fun findCollisions(
element: PsiElement,
newName: String,
allRenames: MutableMap<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return
val descriptor = declaration.unsafeResolveToDescriptor() as VariableDescriptor
val collisions = SmartList<UsageInfo>()
checkRedeclarations(descriptor, newName, collisions)
checkAccidentalOverrides(declaration, newName, descriptor, collisions)
checkOriginalUsagesRetargeting(declaration, newName, result, collisions)
checkNewNameUsagesRetargeting(declaration, newName, collisions)
result += collisions
}
private fun chooseCallableToRename(callableDeclaration: KtCallableDeclaration): KtCallableDeclaration? {
val deepestSuperDeclaration = findDeepestOverriddenDeclaration(callableDeclaration)
if (deepestSuperDeclaration == null || deepestSuperDeclaration == callableDeclaration) {
return callableDeclaration
}
if (ApplicationManager.getApplication()!!.isUnitTestMode) return deepestSuperDeclaration
val containsText: String? =
deepestSuperDeclaration.fqName?.parent()?.asString() ?:
(deepestSuperDeclaration.parent as? KtClassOrObject)?.name
val result = Messages.showYesNoCancelDialog(
deepestSuperDeclaration.project,
if (containsText != null) "Do you want to rename base property from \n$containsText" else "Do you want to rename base property",
"Rename warning",
Messages.getQuestionIcon())
return when (result) {
Messages.YES -> deepestSuperDeclaration
Messages.NO -> callableDeclaration
else -> /* Cancel rename */ null
}
}
override fun substituteElementToRename(element: PsiElement, editor: Editor?): PsiElement? {
val namedUnwrappedElement = element.namedUnwrappedElement ?: return null
val callableDeclaration = namedUnwrappedElement as? KtCallableDeclaration
?: throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
val declarationToRename = chooseCallableToRename(callableDeclaration) ?: return null
val (getterJvmName, setterJvmName) = getJvmNames(namedUnwrappedElement)
if (element is KtLightMethod) {
val name = element.name
if (element.name != getterJvmName && element.name != setterJvmName) return declarationToRename
return declarationToRename.toLightMethods().firstOrNull { it.name == name }
}
return declarationToRename
}
override fun substituteElementToRename(element: PsiElement, editor: Editor, renameCallback: Pass<PsiElement>) {
val namedUnwrappedElement = element.namedUnwrappedElement ?: return
val callableDeclaration = namedUnwrappedElement as? KtCallableDeclaration
?: throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
fun preprocessAndPass(substitutedJavaElement: PsiElement) {
val (getterJvmName, setterJvmName) = getJvmNames(namedUnwrappedElement)
val elementToProcess = if (element is KtLightMethod) {
val name = element.name
if (element.name != getterJvmName && element.name != setterJvmName) {
substitutedJavaElement
}
else {
substitutedJavaElement.toLightMethods().firstOrNull { it.name == name }
}
}
else substitutedJavaElement
renameCallback.pass(elementToProcess)
}
val deepestSuperDeclaration = findDeepestOverriddenDeclaration(callableDeclaration)
if (deepestSuperDeclaration == null || deepestSuperDeclaration == callableDeclaration) {
return preprocessAndPass(callableDeclaration)
}
val superPsiMethods = listOfNotNull(deepestSuperDeclaration.getRepresentativeLightMethod())
checkSuperMethodsWithPopup(callableDeclaration, superPsiMethods, "Rename", editor) {
preprocessAndPass(if (it.size > 1) deepestSuperDeclaration else callableDeclaration)
}
}
class PropertyMethodWrapper(private val propertyMethod: PsiMethod) : PsiNamedElement by propertyMethod, NavigationItem by propertyMethod {
override fun getName() = propertyMethod.name
override fun setName(name: String) = this
override fun copy() = this
}
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
super.prepareRenaming(element, newName, allRenames, scope)
val namedUnwrappedElement = element.namedUnwrappedElement
val propertyMethods = when(namedUnwrappedElement) {
is KtProperty -> runReadAction { LightClassUtil.getLightClassPropertyMethods(namedUnwrappedElement) }
is KtParameter -> runReadAction { LightClassUtil.getLightClassPropertyMethods(namedUnwrappedElement) }
else -> throw IllegalStateException("Can't be for element $element there because of canProcessElement()")
}
val newPropertyName = if (element is KtLightMethod) propertyNameByAccessor(newName, element) else newName
val (getterJvmName, setterJvmName) = getJvmNames(namedUnwrappedElement)
val getter = propertyMethods.getter as? KtLightMethod
val setter = propertyMethods.setter as? KtLightMethod
if (newPropertyName != null
&& getter != null && setter != null
&& (element == getter || element == setter)
&& propertyNameByAccessor(getter.name, getter) == propertyNameByAccessor(setter.name, setter)) {
val accessorToRename = if (element == getter) setter else getter
val newAccessorName = if (element == getter) JvmAbi.setterName(newPropertyName) else JvmAbi.getterName(newPropertyName)
if (ApplicationManager.getApplication().isUnitTestMode
|| Messages.showYesNoDialog("Do you want to rename ${accessorToRename.name}() as well?",
"Rename",
Messages.getQuestionIcon()) == Messages.YES) {
allRenames[accessorToRename] = newAccessorName
}
}
for (propertyMethod in propertyMethods) {
if (element is KtDeclaration && newPropertyName != null) {
val wrapper = PropertyMethodWrapper(propertyMethod)
when {
JvmAbi.isGetterName(propertyMethod.name) && getterJvmName == null ->
allRenames[wrapper] = JvmAbi.getterName(newPropertyName)
JvmAbi.isSetterName(propertyMethod.name) && setterJvmName == null ->
allRenames[wrapper] = JvmAbi.setterName(newPropertyName)
}
}
addRenameElements(propertyMethod, (element as PsiNamedElement).name, newPropertyName, allRenames, scope)
}
}
private enum class UsageKind {
SIMPLE_PROPERTY_USAGE,
GETTER_USAGE,
SETTER_USAGE
}
override tailrec fun renameElement(element: PsiElement, newName: String, usages: Array<UsageInfo>, listener: RefactoringElementListener?) {
val newNameUnquoted = newName.unquote()
if (element is KtLightMethod) {
if (element.modifierList.findAnnotation(DescriptorUtils.JVM_NAME.asString()) != null) {
return super.renameElement(element, newName, usages, listener)
}
val origin = element.kotlinOrigin
val newPropertyName = propertyNameByAccessor(newNameUnquoted, element)
// Kotlin references to Kotlin property should not use accessor name
if (newPropertyName != null && (origin is KtProperty || origin is KtParameter)) {
val (ktUsages, otherUsages) = usages.partition { it.reference is KtSimpleNameReference }
super.renameElement(element, newName, otherUsages.toTypedArray(), listener)
renameElement(origin, newPropertyName.quoteIfNeeded(), ktUsages.toTypedArray(), listener)
return
}
}
if (element !is KtProperty && element !is KtParameter) {
super.renameElement(element, newName, usages, listener)
return
}
val name = (element as KtNamedDeclaration).name!!
val oldGetterName = JvmAbi.getterName(name)
val oldSetterName = JvmAbi.setterName(name)
if (isEnumCompanionPropertyWithEntryConflict(element, newNameUnquoted)) {
for ((i, usage) in usages.withIndex()) {
if (usage !is MoveRenameUsageInfo) continue
val ref = usage.reference ?: continue
// TODO: Enum value can't be accessed from Java in case of conflict with companion member
if (ref is KtReference) {
val newRef = (ref.bindToElement(element) as? KtSimpleNameExpression)?.mainReference ?: continue
usages[i] = MoveRenameUsageInfo(newRef, usage.referencedElement)
}
}
}
val adjustedUsages = if (element is KtParameter) usages.filterNot {
val refTarget = it.reference?.resolve()
refTarget is KtLightMethod && DataClassDescriptorResolver.isComponentLike(Name.guessByFirstCharacter(refTarget.name))
} else usages.toList()
val refKindUsages = adjustedUsages.groupBy { usage: UsageInfo ->
val refElement = usage.reference?.resolve()
if (refElement is PsiMethod) {
when (refElement.name) {
oldGetterName -> UsageKind.GETTER_USAGE
oldSetterName -> UsageKind.SETTER_USAGE
else -> UsageKind.SIMPLE_PROPERTY_USAGE
}
}
else {
UsageKind.SIMPLE_PROPERTY_USAGE
}
}
super.renameElement(element, JvmAbi.setterName(newNameUnquoted).quoteIfNeeded(),
refKindUsages[UsageKind.SETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null)
super.renameElement(element, JvmAbi.getterName(newNameUnquoted).quoteIfNeeded(),
refKindUsages[UsageKind.GETTER_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null)
super.renameElement(element, newName,
refKindUsages[UsageKind.SIMPLE_PROPERTY_USAGE]?.toTypedArray() ?: arrayOf<UsageInfo>(),
null)
usages.forEach { (it as? KtResolvableCollisionUsageInfo)?.apply() }
dropOverrideKeywordIfNecessary(element)
listener?.elementRenamed(element)
}
private fun addRenameElements(psiMethod: PsiMethod?,
oldName: String?,
newName: String?,
allRenames: MutableMap<PsiElement, String>,
scope: SearchScope) {
if (psiMethod == null) return
OverridingMethodsSearch.search(psiMethod, scope, true).forEach { overrider ->
val overriderElement = overrider.namedUnwrappedElement
if (overriderElement != null && overriderElement !is SyntheticElement) {
RenameProcessor.assertNonCompileElement(overriderElement)
val overriderName = overriderElement.name
if (overriderElement is PsiMethod) {
if (newName != null && Name.isValidIdentifier(newName)) {
val isGetter = overriderElement.parameterList.parametersCount == 0
allRenames[overriderElement] = if (isGetter) JvmAbi.getterName(newName) else JvmAbi.setterName(newName)
}
}
else {
val newOverriderName = RefactoringUtil.suggestNewOverriderName(overriderName, oldName, newName)
if (newOverriderName != null) {
allRenames[overriderElement] = newOverriderName
}
}
}
}
}
private fun findDeepestOverriddenDeclaration(declaration: KtCallableDeclaration): KtCallableDeclaration? {
if (declaration.modifierList?.hasModifier(KtTokens.OVERRIDE_KEYWORD) == true) {
val bindingContext = declaration.analyze()
var descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
if (descriptor is ValueParameterDescriptor) {
descriptor = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor]
?: return declaration
}
if (descriptor != null) {
assert(descriptor is PropertyDescriptor) { "Property descriptor is expected" }
val supers = (descriptor as PropertyDescriptor).getDeepestSuperDeclarations()
// Take one of supers for now - API doesn't support substitute to several elements (IDEA-48796)
val deepest = supers.first()
if (deepest != descriptor) {
val superPsiElement = DescriptorToSourceUtils.descriptorToDeclaration(deepest)
return superPsiElement as? KtCallableDeclaration
}
}
}
return null
}
}
@@ -0,0 +1,120 @@
/*
* Copyright 2010-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiUtilCore
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.resolve.ImportPath
abstract class RenameKotlinPsiProcessor : RenamePsiElementProcessor() {
override fun canProcessElement(element: PsiElement): Boolean = element is KtNamedDeclaration
override fun findReferences(element: PsiElement): Collection<PsiReference> {
val searchParameters = KotlinReferencesSearchParameters(
element,
element.project.projectScope(),
kotlinOptions = KotlinReferencesSearchOptions(searchForComponentConventions = false)
)
val references = ReferencesSearch.search(searchParameters).toMutableList()
if (element is KtNamedFunction
|| (element is KtProperty && !element.isLocal)
|| (element is KtParameter && element.hasValOrVar())) {
element.toLightMethods().flatMapTo(references) { MethodReferencesSearch.search(it) }
}
return references
}
override fun getQualifiedNameAfterRename(element: PsiElement, newName: String, nonJava: Boolean): String? {
if (!nonJava) return newName
val qualifiedName = when (element) {
is KtNamedDeclaration -> element.fqName?.asString() ?: element.name
is PsiClass -> element.qualifiedName ?: element.name
else -> return null
}
return PsiUtilCore.getQualifiedNameAfterRename(qualifiedName, newName)
}
override fun prepareRenaming(element: PsiElement, newName: String, allRenames: MutableMap<PsiElement, String>, scope: SearchScope) {
val safeNewName = newName.quoteIfNeeded()
if (!newName.isIdentifier()) {
allRenames[element] = safeNewName
}
val declaration = element.namedUnwrappedElement as? KtNamedDeclaration
if (declaration != null) {
declaration.liftToExpected()?.let { expectDeclaration ->
allRenames[expectDeclaration] = safeNewName
expectDeclaration.actualsForExpected().forEach { allRenames[it] = safeNewName }
}
}
}
protected var PsiElement.ambiguousImportUsages: List<UsageInfo>? by UserDataProperty(Key.create("AMBIGUOUS_IMPORT_USAGES"))
protected fun UsageInfo.isAmbiguousImportUsage(): Boolean {
val ref = reference as? PsiPolyVariantReference ?: return false
val refElement = ref.element
return refElement.parents.any { (it is KtImportDirective && !it.isAllUnder) || (it is PsiImportStaticStatement && !it.isOnDemand) }
&& ref.multiResolve(false).mapNotNullTo(HashSet()) { it.element?.unwrapped }.size > 1
}
override fun getPostRenameCallback(element: PsiElement, newName: String, elementListener: RefactoringElementListener): Runnable? {
return Runnable {
element.ambiguousImportUsages?.forEach {
val ref = it.reference as? PsiPolyVariantReference ?: return@forEach
if (ref.multiResolve(false).isEmpty()) {
ref.handleElementRename(newName)
}
else {
ref.element?.getStrictParentOfType<KtImportDirective>()?.let { importDirective ->
val fqName = importDirective.importedFqName!!
val newFqName = fqName.parent().child(Name.identifier(newName))
val importList = importDirective.parent as KtImportList
if (importList.imports.none { it.importedFqName == newFqName }) {
val newImportDirective = KtPsiFactory(element).createImportDirective(ImportPath(newFqName, false))
importDirective.parent.addAfter(newImportDirective, importDirective)
}
}
}
}
element.ambiguousImportUsages = null
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.rename
import com.intellij.psi.PsiElement
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.psi.KtTypeParameter
class RenameKotlinTypeParameterProcessor : RenameKotlinPsiProcessor() {
override fun canProcessElement(element: PsiElement) = element is KtTypeParameter
override fun findCollisions(
element: PsiElement,
newName: String,
allRenames: MutableMap<out PsiElement, String>,
result: MutableList<UsageInfo>
) {
val declaration = element as? KtTypeParameter ?: return
val descriptor = declaration.unsafeResolveToDescriptor()
checkRedeclarations(descriptor, newName, result)
}
}