Deprecated symbol usages fix for the whole project
This commit is contained in:
@@ -16,371 +16,47 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.HighPriorityAction
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.asExpression
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
|
||||
import org.jetbrains.kotlin.idea.intentions.setType
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectElementsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
|
||||
import org.jetbrains.kotlin.types.ErrorUtils
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import java.util.ArrayList
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
|
||||
//TODO: replacement of class usages
|
||||
//TODO: different replacements for property accessors
|
||||
//TODO: replace all in project quickfixes on usage and on deprecated annotation
|
||||
public class DeprecatedSymbolUsageFix(
|
||||
element: JetSimpleNameExpression/*TODO?*/,
|
||||
val replaceWith: ReplaceWith
|
||||
) : JetIntentionAction<JetSimpleNameExpression>(element) {
|
||||
replaceWith: ReplaceWith
|
||||
) : DeprecatedSymbolUsageFixBase(element, replaceWith), HighPriorityAction {
|
||||
|
||||
override fun getFamilyName() = "Replace deprecated symbol usage"
|
||||
|
||||
override fun getText() = "Replace with '${replaceWith.expression}'" //TODO: substitute?
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
|
||||
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false
|
||||
if (!resolvedCall.getStatus().isSuccess()) return false
|
||||
val descriptor = resolvedCall.getResultingDescriptor()
|
||||
if (replaceWithPattern(descriptor) != replaceWith) return false
|
||||
|
||||
try {
|
||||
JetPsiFactory(project).createExpression(replaceWith.expression)
|
||||
return true
|
||||
}
|
||||
catch(e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
val bindingContext = element.analyze()
|
||||
val resolvedCall = element.getResolvedCall(bindingContext)!!
|
||||
val descriptor = resolvedCall.getResultingDescriptor()
|
||||
val callExpression = resolvedCall.getCall().getCallElement() as JetExpression
|
||||
val qualifiedExpression = callExpression.getParent() as? JetQualifiedExpression
|
||||
val expressionToReplace = qualifiedExpression ?: callExpression
|
||||
|
||||
var receiver = element.getReceiverExpression()
|
||||
receiver?.putCopyableUserData(USER_CODE_KEY, Unit)
|
||||
|
||||
if (receiver == null) {
|
||||
val receiverValue = if (descriptor.isExtension) resolvedCall.getExtensionReceiver() else resolvedCall.getDispatchReceiver()
|
||||
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expressionToReplace]
|
||||
if (receiverValue is ThisReceiver && resolutionScope != null) {
|
||||
receiver = receiverValue.asExpression(resolutionScope, psiFactory)
|
||||
}
|
||||
}
|
||||
|
||||
receiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
|
||||
|
||||
val originalDescriptor = (if (descriptor is CallableMemberDescriptor)
|
||||
DescriptorUtils.unwrapFakeOverride(descriptor)
|
||||
else
|
||||
descriptor).getOriginal()
|
||||
|
||||
var (expression, imports, parameterUsages) = ReplaceWithAnnotationAnalyzer.analyze(
|
||||
replaceWith, originalDescriptor, element.getResolutionFacade(), file, project)
|
||||
|
||||
//TODO: this@
|
||||
for (thisExpression in expression.collectElementsOfType<JetThisExpression>()) {
|
||||
if (receiver != null) {
|
||||
thisExpression.replace(receiver)
|
||||
}
|
||||
else {
|
||||
thisExpression.putCopyableUserData(FROM_THIS_KEY, Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun argumentForParameter(parameter: ValueParameterDescriptor): JetExpression? {
|
||||
//TODO: optional parameters
|
||||
val arguments = resolvedCall.getValueArguments()[parameter] ?: return null //TODO: what if not? vararg?
|
||||
return arguments.getArguments().firstOrNull()?.getArgumentExpression() //TODO: what if multiple?
|
||||
}
|
||||
|
||||
val introduceValuesForParameters = ArrayList<Pair<ValueParameterDescriptor, JetExpression>>()
|
||||
|
||||
for (parameter in descriptor.getValueParameters()) {
|
||||
val argument = argumentForParameter(parameter) ?: continue
|
||||
argument.putCopyableUserData(FROM_PARAMETER_KEY, parameter)
|
||||
argument.putCopyableUserData(USER_CODE_KEY, Unit)
|
||||
|
||||
val usages = parameterUsages[parameter.getOriginal()]!!
|
||||
usages.forEach { it.replace(argument) }
|
||||
|
||||
if (argument.shouldKeepValue(usages.size())) {
|
||||
introduceValuesForParameters.add(parameter to argument)
|
||||
}
|
||||
}
|
||||
|
||||
if (qualifiedExpression is JetSafeQualifiedExpression) {
|
||||
expression = expression.wrapExpressionForSafeCall(expressionToReplace, receiver!!, bindingContext)
|
||||
}
|
||||
else if (callExpression is JetBinaryExpression && callExpression.getOperationToken() == JetTokens.IDENTIFIER) {
|
||||
expression = expression.keepInfixFormIfPossible()
|
||||
}
|
||||
|
||||
if (receiver != null) {
|
||||
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
|
||||
if (receiver.shouldKeepValue(thisReplaced.size())) {
|
||||
expression = expression.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced)
|
||||
}
|
||||
}
|
||||
|
||||
for ((parameter, value) in introduceValuesForParameters) {
|
||||
val usagesReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_PARAMETER_KEY) == parameter }
|
||||
expression = expression.introduceValue(value, expressionToReplace, bindingContext, usagesReplaced, nameSuggestion = parameter.getName().asString())
|
||||
}
|
||||
|
||||
var result = expressionToReplace.replaced(expression)
|
||||
|
||||
//TODO: drop import of old function (if not needed anymore)?
|
||||
|
||||
for (importFqName in imports) {
|
||||
val descriptors = file.getResolutionFacade().resolveImportReference(file, importFqName)
|
||||
val descriptorToImport = descriptors.firstOrNull() ?: continue
|
||||
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptorToImport)
|
||||
}
|
||||
|
||||
val shortenFilter = { element: PsiElement ->
|
||||
if (element.getCopyableUserData(USER_CODE_KEY) != null) {
|
||||
ShortenReferences.FilterResult.SKIP
|
||||
}
|
||||
else {
|
||||
val thisReceiver = (element as? JetQualifiedExpression)?.getReceiverExpression() as? JetThisExpression
|
||||
if (thisReceiver != null && thisReceiver.getCopyableUserData(USER_CODE_KEY) != null) // don't remove explicit 'this' coming from user's code
|
||||
ShortenReferences.FilterResult.GO_INSIDE
|
||||
else
|
||||
ShortenReferences.FilterResult.PROCESS
|
||||
}
|
||||
}
|
||||
result = ShortenReferences({ ShortenReferences.Options(removeThis = true) }).process(result, shortenFilter) as JetExpression
|
||||
|
||||
// clean up user data
|
||||
result.accept(object : PsiRecursiveElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
element.putCopyableUserData(USER_CODE_KEY, null)
|
||||
element.putCopyableUserData(FROM_PARAMETER_KEY, null)
|
||||
element.putCopyableUserData(FROM_THIS_KEY, null)
|
||||
}
|
||||
})
|
||||
override fun invoke(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
bindingContext: BindingContext,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||
project: Project,
|
||||
editor: Editor?
|
||||
) {
|
||||
val result = DeprecatedSymbolUsageFixBase.performReplacement(element, bindingContext, resolvedCall, replacement)
|
||||
|
||||
val offset = (result.getCalleeExpressionIfAny() ?: result).getTextOffset()
|
||||
editor?.moveCaret(offset)
|
||||
}
|
||||
|
||||
private fun JetExpression.wrapExpressionForSafeCall(
|
||||
expressionToReplace: JetExpression,
|
||||
receiver: JetExpression,
|
||||
bindingContext: BindingContext
|
||||
): JetExpression {
|
||||
val psiFactory = JetPsiFactory(this)
|
||||
val qualified = this as? JetQualifiedExpression
|
||||
if (qualified != null) {
|
||||
if (qualified.getReceiverExpression().getCopyableUserData(FROM_THIS_KEY) != null) {
|
||||
if (qualified is JetSafeQualifiedExpression) return this // already safe
|
||||
val selector = qualified.getSelectorExpression()
|
||||
if (selector != null) {
|
||||
return psiFactory.createExpressionByPattern("$0?.$1", receiver, selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
|
||||
val thisReplaced = this.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
|
||||
return this.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
|
||||
}
|
||||
else {
|
||||
return psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", receiver, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun JetExpression.keepInfixFormIfPossible(): JetExpression {
|
||||
if (this !is JetDotQualifiedExpression) return this
|
||||
val receiver = getReceiverExpression()
|
||||
if (receiver.getCopyableUserData(FROM_THIS_KEY) == null) return this
|
||||
val call = getSelectorExpression() as? JetCallExpression ?: return this
|
||||
val nameExpression = call.getCalleeExpression() as? JetSimpleNameExpression ?: return this
|
||||
val argument = call.getValueArguments().singleOrNull() ?: return this
|
||||
if (argument.getArgumentName() != null) return this
|
||||
val argumentExpression = argument.getArgumentExpression() ?: return this
|
||||
return JetPsiFactory(this).createExpressionByPattern("$0 ${nameExpression.getText()} $1", receiver, argumentExpression)
|
||||
}
|
||||
|
||||
private fun JetExpression.introduceValue(
|
||||
value: JetExpression,
|
||||
insertDeclarationsBefore: JetExpression,
|
||||
bindingContext: BindingContext,
|
||||
usages: Collection<JetExpression>,
|
||||
nameSuggestion: String? = null,
|
||||
safeCall: Boolean = false
|
||||
): JetExpression {
|
||||
assert(usages.all { isAncestor(it, strict = true) })
|
||||
|
||||
val psiFactory = JetPsiFactory(this)
|
||||
|
||||
fun nameInCode(name: String) = IdeDescriptorRenderers.SOURCE_CODE.renderName(Name.identifier(name))
|
||||
|
||||
fun replaceUsages(name: String) {
|
||||
val nameInCode = psiFactory.createExpression(nameInCode(name))
|
||||
for (usage in usages) {
|
||||
usage.replace(nameInCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun suggestName(validator: JetNameValidator): String {
|
||||
return if (nameSuggestion != null)
|
||||
validator.validateName(nameSuggestion)
|
||||
else
|
||||
JetNameSuggester.suggestNamesForExpression(value, validator, "t").first()
|
||||
}
|
||||
|
||||
// checks that name is used (without receiver) inside this expression but not inside usages that will be replaced
|
||||
fun isNameUsed(name: String) = collectNameUsages(this, name).any { nameUsage -> usages.none { it.isAncestor(nameUsage) } }
|
||||
|
||||
if (!safeCall) {
|
||||
val block = insertDeclarationsBefore.getParent() as? JetBlockExpression
|
||||
if (block != null) {
|
||||
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, insertDeclarationsBefore]
|
||||
|
||||
if (usages.isNotEmpty()) {
|
||||
val valueType = bindingContext.getType(value)
|
||||
var explicitType: JetType? = null
|
||||
if (valueType != null && !ErrorUtils.containsErrorType(valueType)) {
|
||||
val valueTypeWithoutExpectedType = value.analyzeInContext(
|
||||
resolutionScope,
|
||||
dataFlowInfo = bindingContext.getDataFlowInfo(insertDeclarationsBefore)
|
||||
).getType(value)
|
||||
if (valueTypeWithoutExpectedType == null || ErrorUtils.containsErrorType(valueTypeWithoutExpectedType)) {
|
||||
explicitType = valueType
|
||||
}
|
||||
}
|
||||
|
||||
val name = suggestName(object : JetNameValidator() {
|
||||
override fun validateInner(name: String): Boolean {
|
||||
return resolutionScope.getLocalVariable(Name.identifier(name)) == null && !isNameUsed(name)
|
||||
}
|
||||
})
|
||||
|
||||
var declaration = psiFactory.createDeclaration<JetVariableDeclaration>("val ${nameInCode(name)} = " + value.getText())
|
||||
declaration = block.addBefore(declaration, insertDeclarationsBefore) as JetVariableDeclaration
|
||||
block.addBefore(psiFactory.createNewLine(), insertDeclarationsBefore)
|
||||
|
||||
if (explicitType != null) {
|
||||
declaration.setType(explicitType)
|
||||
}
|
||||
|
||||
replaceUsages(name)
|
||||
}
|
||||
else {
|
||||
block.addBefore(value, insertDeclarationsBefore)
|
||||
block.addBefore(psiFactory.createNewLine(), insertDeclarationsBefore)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
val dot = if (safeCall) "?." else "."
|
||||
|
||||
if (!isNameUsed("it")) {
|
||||
replaceUsages("it")
|
||||
return psiFactory.createExpressionByPattern("$0${dot}let { $1 }", value, this)
|
||||
}
|
||||
else {
|
||||
val name = suggestName(object : JetNameValidator() {
|
||||
override fun validateInner(name: String) = !isNameUsed(name)
|
||||
})
|
||||
replaceUsages(name)
|
||||
return psiFactory.createExpressionByPattern("$0${dot}let { ${nameInCode(name)} -> $1 }", value, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectNameUsages(scope: JetExpression, name: String)
|
||||
= scope.collectElementsOfType<JetSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
|
||||
|
||||
private fun JetExpression?.shouldKeepValue(usageCount: Int): Boolean {
|
||||
if (usageCount == 1) return false
|
||||
val sideEffectOnly = usageCount == 0
|
||||
|
||||
return when (this) {
|
||||
is JetSimpleNameExpression -> false
|
||||
is JetQualifiedExpression -> getReceiverExpression().shouldKeepValue(usageCount) || getSelectorExpression().shouldKeepValue(usageCount)
|
||||
is JetUnaryExpression -> getOperationToken() in setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) || getBaseExpression().shouldKeepValue(usageCount)
|
||||
is JetStringTemplateExpression -> getEntries().any { if (sideEffectOnly) it.getExpression().shouldKeepValue(usageCount) else it is JetStringTemplateEntryWithExpression }
|
||||
is JetThisExpression, is JetSuperExpression, is JetConstantExpression -> false
|
||||
is JetParenthesizedExpression -> getExpression().shouldKeepValue(usageCount)
|
||||
|
||||
// TODO: discuss it
|
||||
is JetBinaryExpression -> if (sideEffectOnly) getLeft().shouldKeepValue(usageCount) || getRight().shouldKeepValue(usageCount) else true
|
||||
is JetIfExpression -> if (sideEffectOnly) getCondition().shouldKeepValue(usageCount) || getThen().shouldKeepValue(usageCount) || getElse().shouldKeepValue(usageCount) else true
|
||||
is JetBinaryExpressionWithTypeRHS -> true
|
||||
|
||||
else -> true // what else it can be?
|
||||
}
|
||||
}
|
||||
|
||||
companion object : JetSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null
|
||||
val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(diagnostic).getA()
|
||||
val replacement = replaceWithPattern(descriptor) ?: return null
|
||||
val replacement = DeprecatedSymbolUsageFixBase.replaceWithPattern(descriptor) ?: return null
|
||||
return DeprecatedSymbolUsageFix(nameExpression, replacement)
|
||||
}
|
||||
|
||||
private fun replaceWithPattern(descriptor: DeclarationDescriptor): ReplaceWith? {
|
||||
val annotationClass = descriptor.builtIns.getDeprecatedAnnotation()
|
||||
val annotation = descriptor.getAnnotations().findAnnotation(DescriptorUtils.getFqNameSafe(annotationClass))!!
|
||||
val replaceWithValue = annotation.getAllValueArguments().entrySet()
|
||||
.singleOrNull { it.key.getName().asString() == "replaceWith"/*TODO*/ }
|
||||
?.value?.getValue() as? AnnotationDescriptor ?: return null
|
||||
val pattern = replaceWithValue.getAllValueArguments().entrySet()
|
||||
.singleOrNull { it.key.getName().asString() == "expression"/*TODO*/ }
|
||||
?.value?.getValue() as? String ?: return null
|
||||
if (pattern.isEmpty()) return null
|
||||
val argument = replaceWithValue.getAllValueArguments().entrySet().singleOrNull { it.key.getName().asString() == "imports"/*TODO*/ }?.value
|
||||
val imports = (argument?.getValue() as? List<CompileTimeConstant<String>>)?.map { it.getValue() } ?: emptyList()
|
||||
return ReplaceWith(pattern, *imports.toTypedArray())
|
||||
}
|
||||
|
||||
private val USER_CODE_KEY = Key<Unit>("USER_CODE")
|
||||
private val FROM_PARAMETER_KEY = Key<ValueParameterDescriptor>("FROM_PARAMETER")
|
||||
private val FROM_THIS_KEY = Key<Unit>("FROM_THIS")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* 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.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.HighPriorityAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.asExpression
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
|
||||
import org.jetbrains.kotlin.idea.intentions.setType
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectElementsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
|
||||
import org.jetbrains.kotlin.types.ErrorUtils
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import java.util.ArrayList
|
||||
|
||||
//TODO: replacement of class usages
|
||||
//TODO: different replacements for property accessors
|
||||
|
||||
public abstract class DeprecatedSymbolUsageFixBase(
|
||||
element: JetSimpleNameExpression/*TODO?*/,
|
||||
val replaceWith: ReplaceWith
|
||||
) : JetIntentionAction<JetSimpleNameExpression>(element) {
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
|
||||
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false
|
||||
if (!resolvedCall.getStatus().isSuccess()) return false
|
||||
val descriptor = resolvedCall.getResultingDescriptor()
|
||||
if (replaceWithPattern(descriptor) != replaceWith) return false
|
||||
|
||||
try {
|
||||
JetPsiFactory(project).createExpression(replaceWith.expression)
|
||||
return true
|
||||
}
|
||||
catch(e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
final override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
val bindingContext = element.analyze()
|
||||
val resolvedCall = element.getResolvedCall(bindingContext)!!
|
||||
val descriptor = resolvedCall.getResultingDescriptor()
|
||||
|
||||
val replacement = ReplaceWithAnnotationAnalyzer.analyze(replaceWith, descriptor, element.getResolutionFacade(), file, project)
|
||||
|
||||
invoke(resolvedCall, bindingContext, replacement, project, editor)
|
||||
}
|
||||
|
||||
protected abstract fun invoke(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
bindingContext: BindingContext,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||
project: Project,
|
||||
editor: Editor?)
|
||||
|
||||
companion object {
|
||||
public fun replaceWithPattern(descriptor: DeclarationDescriptor): ReplaceWith? {
|
||||
val annotationClass = descriptor.builtIns.getDeprecatedAnnotation()
|
||||
val annotation = descriptor.getAnnotations().findAnnotation(DescriptorUtils.getFqNameSafe(annotationClass))!!
|
||||
val replaceWithValue = annotation.getAllValueArguments().entrySet()
|
||||
.singleOrNull { it.key.getName().asString() == "replaceWith"/*TODO*/ }
|
||||
?.value?.getValue() as? AnnotationDescriptor ?: return null
|
||||
val pattern = replaceWithValue.getAllValueArguments().entrySet()
|
||||
.singleOrNull { it.key.getName().asString() == "expression"/*TODO*/ }
|
||||
?.value?.getValue() as? String ?: return null
|
||||
if (pattern.isEmpty()) return null
|
||||
val argument = replaceWithValue.getAllValueArguments().entrySet().singleOrNull { it.key.getName().asString() == "imports"/*TODO*/ }?.value
|
||||
val imports = (argument?.getValue() as? List<CompileTimeConstant<String>>)?.map { it.getValue() } ?: emptyList()
|
||||
return ReplaceWith(pattern, *imports.toTypedArray())
|
||||
}
|
||||
|
||||
public fun performReplacement(
|
||||
element: JetSimpleNameExpression,
|
||||
bindingContext: BindingContext,
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression
|
||||
): JetExpression {
|
||||
var (expression, imports, parameterUsages) = replacement
|
||||
val project = element.getProject()
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
val descriptor = resolvedCall.getResultingDescriptor()
|
||||
|
||||
val callExpression = resolvedCall.getCall().getCallElement() as JetExpression
|
||||
val qualifiedExpression = callExpression.getParent() as? JetQualifiedExpression
|
||||
val expressionToReplace = qualifiedExpression ?: callExpression
|
||||
|
||||
var receiver = element.getReceiverExpression()
|
||||
receiver?.putCopyableUserData(USER_CODE_KEY, Unit)
|
||||
|
||||
if (receiver == null) {
|
||||
val receiverValue = if (descriptor.isExtension) resolvedCall.getExtensionReceiver() else resolvedCall.getDispatchReceiver()
|
||||
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expressionToReplace]
|
||||
if (receiverValue is ThisReceiver && resolutionScope != null) {
|
||||
receiver = receiverValue.asExpression(resolutionScope, psiFactory)
|
||||
}
|
||||
}
|
||||
|
||||
receiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
|
||||
|
||||
//TODO: this@
|
||||
for (thisExpression in expression.collectElementsOfType<JetThisExpression>()) {
|
||||
if (receiver != null) {
|
||||
thisExpression.replace(receiver)
|
||||
}
|
||||
else {
|
||||
thisExpression.putCopyableUserData(FROM_THIS_KEY, Unit)
|
||||
}
|
||||
}
|
||||
|
||||
fun argumentForParameter(parameter: ValueParameterDescriptor): JetExpression? {
|
||||
//TODO: optional parameters
|
||||
val arguments = resolvedCall.getValueArguments()[parameter] ?: return null //TODO: what if not? vararg?
|
||||
return arguments.getArguments().firstOrNull()?.getArgumentExpression() //TODO: what if multiple?
|
||||
}
|
||||
|
||||
val introduceValuesForParameters = ArrayList<Pair<ValueParameterDescriptor, JetExpression>>()
|
||||
|
||||
for (parameter in descriptor.getValueParameters()) {
|
||||
val argument = argumentForParameter(parameter) ?: continue
|
||||
argument.putCopyableUserData(FROM_PARAMETER_KEY, parameter)
|
||||
argument.putCopyableUserData(USER_CODE_KEY, Unit)
|
||||
|
||||
val usages = parameterUsages[parameter.getOriginal()]!!
|
||||
usages.forEach { it.replace(argument) }
|
||||
|
||||
if (argument.shouldKeepValue(usages.size())) {
|
||||
introduceValuesForParameters.add(parameter to argument)
|
||||
}
|
||||
}
|
||||
|
||||
if (qualifiedExpression is JetSafeQualifiedExpression) {
|
||||
expression = expression.wrapExpressionForSafeCall(expressionToReplace, receiver!!, bindingContext)
|
||||
}
|
||||
else if (callExpression is JetBinaryExpression && callExpression.getOperationToken() == JetTokens.IDENTIFIER) {
|
||||
expression = expression.keepInfixFormIfPossible()
|
||||
}
|
||||
|
||||
if (receiver != null) {
|
||||
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
|
||||
if (receiver.shouldKeepValue(thisReplaced.size())) {
|
||||
expression = expression.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced)
|
||||
}
|
||||
}
|
||||
|
||||
for ((parameter, value) in introduceValuesForParameters) {
|
||||
val usagesReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_PARAMETER_KEY) == parameter }
|
||||
expression = expression.introduceValue(value, expressionToReplace, bindingContext, usagesReplaced, nameSuggestion = parameter.getName().asString())
|
||||
}
|
||||
|
||||
var result = expressionToReplace.replaced(expression)
|
||||
|
||||
//TODO: drop import of old function (if not needed anymore)?
|
||||
|
||||
val file = result.getContainingJetFile()
|
||||
for (importFqName in imports) {
|
||||
val descriptors = file.getResolutionFacade().resolveImportReference(file, importFqName)
|
||||
val descriptorToImport = descriptors.firstOrNull() ?: continue
|
||||
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptorToImport)
|
||||
}
|
||||
|
||||
val shortenFilter = { element: PsiElement ->
|
||||
if (element.getCopyableUserData(USER_CODE_KEY) != null) {
|
||||
ShortenReferences.FilterResult.SKIP
|
||||
}
|
||||
else {
|
||||
val thisReceiver = (element as? JetQualifiedExpression)?.getReceiverExpression() as? JetThisExpression
|
||||
if (thisReceiver != null && thisReceiver.getCopyableUserData(USER_CODE_KEY) != null) // don't remove explicit 'this' coming from user's code
|
||||
ShortenReferences.FilterResult.GO_INSIDE
|
||||
else
|
||||
ShortenReferences.FilterResult.PROCESS
|
||||
}
|
||||
}
|
||||
result = ShortenReferences({ ShortenReferences.Options(removeThis = true) }).process(result, shortenFilter) as JetExpression
|
||||
|
||||
// clean up user data
|
||||
result.accept(object : PsiRecursiveElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
element.putCopyableUserData(USER_CODE_KEY, null)
|
||||
element.putCopyableUserData(FROM_PARAMETER_KEY, null)
|
||||
element.putCopyableUserData(FROM_THIS_KEY, null)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun JetExpression.wrapExpressionForSafeCall(
|
||||
expressionToReplace: JetExpression,
|
||||
receiver: JetExpression,
|
||||
bindingContext: BindingContext
|
||||
): JetExpression {
|
||||
val psiFactory = JetPsiFactory(this)
|
||||
val qualified = this as? JetQualifiedExpression
|
||||
if (qualified != null) {
|
||||
if (qualified.getReceiverExpression().getCopyableUserData(FROM_THIS_KEY) != null) {
|
||||
if (qualified is JetSafeQualifiedExpression) return this // already safe
|
||||
val selector = qualified.getSelectorExpression()
|
||||
if (selector != null) {
|
||||
return psiFactory.createExpressionByPattern("$0?.$1", receiver, selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
|
||||
val thisReplaced = this.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
|
||||
return this.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
|
||||
}
|
||||
else {
|
||||
return psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", receiver, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun JetExpression.keepInfixFormIfPossible(): JetExpression {
|
||||
if (this !is JetDotQualifiedExpression) return this
|
||||
val receiver = getReceiverExpression()
|
||||
if (receiver.getCopyableUserData(FROM_THIS_KEY) == null) return this
|
||||
val call = getSelectorExpression() as? JetCallExpression ?: return this
|
||||
val nameExpression = call.getCalleeExpression() as? JetSimpleNameExpression ?: return this
|
||||
val argument = call.getValueArguments().singleOrNull() ?: return this
|
||||
if (argument.getArgumentName() != null) return this
|
||||
val argumentExpression = argument.getArgumentExpression() ?: return this
|
||||
return JetPsiFactory(this).createExpressionByPattern("$0 ${nameExpression.getText()} $1", receiver, argumentExpression)
|
||||
}
|
||||
|
||||
private fun JetExpression.introduceValue(
|
||||
value: JetExpression,
|
||||
insertDeclarationsBefore: JetExpression,
|
||||
bindingContext: BindingContext,
|
||||
usages: Collection<JetExpression>,
|
||||
nameSuggestion: String? = null,
|
||||
safeCall: Boolean = false
|
||||
): JetExpression {
|
||||
assert(usages.all { isAncestor(it, strict = true) })
|
||||
|
||||
val psiFactory = JetPsiFactory(this)
|
||||
|
||||
fun nameInCode(name: String) = IdeDescriptorRenderers.SOURCE_CODE.renderName(Name.identifier(name))
|
||||
|
||||
fun replaceUsages(name: String) {
|
||||
val nameInCode = psiFactory.createExpression(nameInCode(name))
|
||||
for (usage in usages) {
|
||||
usage.replace(nameInCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun suggestName(validator: JetNameValidator): String {
|
||||
return if (nameSuggestion != null)
|
||||
validator.validateName(nameSuggestion)
|
||||
else
|
||||
JetNameSuggester.suggestNamesForExpression(value, validator, "t").first()
|
||||
}
|
||||
|
||||
// checks that name is used (without receiver) inside this expression but not inside usages that will be replaced
|
||||
fun isNameUsed(name: String) = collectNameUsages(this, name).any { nameUsage -> usages.none { it.isAncestor(nameUsage) } }
|
||||
|
||||
if (!safeCall) {
|
||||
val block = insertDeclarationsBefore.getParent() as? JetBlockExpression
|
||||
if (block != null) {
|
||||
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, insertDeclarationsBefore]
|
||||
|
||||
if (usages.isNotEmpty()) {
|
||||
val valueType = bindingContext.getType(value)
|
||||
var explicitType: JetType? = null
|
||||
if (valueType != null && !ErrorUtils.containsErrorType(valueType)) {
|
||||
val valueTypeWithoutExpectedType = value.analyzeInContext(
|
||||
resolutionScope,
|
||||
dataFlowInfo = bindingContext.getDataFlowInfo(insertDeclarationsBefore)
|
||||
).getType(value)
|
||||
if (valueTypeWithoutExpectedType == null || ErrorUtils.containsErrorType(valueTypeWithoutExpectedType)) {
|
||||
explicitType = valueType
|
||||
}
|
||||
}
|
||||
|
||||
val name = suggestName(object : JetNameValidator() {
|
||||
override fun validateInner(name: String): Boolean {
|
||||
return resolutionScope.getLocalVariable(Name.identifier(name)) == null && !isNameUsed(name)
|
||||
}
|
||||
})
|
||||
|
||||
var declaration = psiFactory.createDeclaration<JetVariableDeclaration>("val ${nameInCode(name)} = " + value.getText())
|
||||
declaration = block.addBefore(declaration, insertDeclarationsBefore) as JetVariableDeclaration
|
||||
block.addBefore(psiFactory.createNewLine(), insertDeclarationsBefore)
|
||||
|
||||
if (explicitType != null) {
|
||||
declaration.setType(explicitType)
|
||||
}
|
||||
|
||||
replaceUsages(name)
|
||||
}
|
||||
else {
|
||||
block.addBefore(value, insertDeclarationsBefore)
|
||||
block.addBefore(psiFactory.createNewLine(), insertDeclarationsBefore)
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
val dot = if (safeCall) "?." else "."
|
||||
|
||||
if (!isNameUsed("it")) {
|
||||
replaceUsages("it")
|
||||
return psiFactory.createExpressionByPattern("$0${dot}let { $1 }", value, this)
|
||||
}
|
||||
else {
|
||||
val name = suggestName(object : JetNameValidator() {
|
||||
override fun validateInner(name: String) = !isNameUsed(name)
|
||||
})
|
||||
replaceUsages(name)
|
||||
return psiFactory.createExpressionByPattern("$0${dot}let { ${nameInCode(name)} -> $1 }", value, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectNameUsages(scope: JetExpression, name: String)
|
||||
= scope.collectElementsOfType<JetSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
|
||||
|
||||
private fun JetExpression?.shouldKeepValue(usageCount: Int): Boolean {
|
||||
if (usageCount == 1) return false
|
||||
val sideEffectOnly = usageCount == 0
|
||||
|
||||
return when (this) {
|
||||
is JetSimpleNameExpression -> false
|
||||
is JetQualifiedExpression -> getReceiverExpression().shouldKeepValue(usageCount) || getSelectorExpression().shouldKeepValue(usageCount)
|
||||
is JetUnaryExpression -> getOperationToken() in setOf(JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) || getBaseExpression().shouldKeepValue(usageCount)
|
||||
is JetStringTemplateExpression -> getEntries().any { if (sideEffectOnly) it.getExpression().shouldKeepValue(usageCount) else it is JetStringTemplateEntryWithExpression }
|
||||
is JetThisExpression, is JetSuperExpression, is JetConstantExpression -> false
|
||||
is JetParenthesizedExpression -> getExpression().shouldKeepValue(usageCount)
|
||||
|
||||
// TODO: discuss it
|
||||
is JetBinaryExpression -> if (sideEffectOnly) getLeft().shouldKeepValue(usageCount) || getRight().shouldKeepValue(usageCount) else true
|
||||
is JetIfExpression -> if (sideEffectOnly) getCondition().shouldKeepValue(usageCount) || getThen().shouldKeepValue(usageCount) || getElse().shouldKeepValue(usageCount) else true
|
||||
is JetBinaryExpressionWithTypeRHS -> true
|
||||
|
||||
else -> true // what else it can be?
|
||||
}
|
||||
}
|
||||
|
||||
private val USER_CODE_KEY = Key<Unit>("USER_CODE")
|
||||
private val FROM_PARAMETER_KEY = Key<ValueParameterDescriptor>("FROM_PARAMETER")
|
||||
private val FROM_THIS_KEY = Key<Unit>("FROM_THIS")
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.find.findUsages.FindUsagesOptions
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ex.MessagesEx
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.CommonProcessors
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.JetNamedFunction
|
||||
import org.jetbrains.kotlin.psi.JetProperty
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRendererBuilder
|
||||
import org.jetbrains.kotlin.renderer.NameShortness
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
public class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
element: JetSimpleNameExpression,
|
||||
replaceWith: ReplaceWith,
|
||||
private val text: String
|
||||
) : DeprecatedSymbolUsageFixBase(element, replaceWith) {
|
||||
|
||||
private val LOG = Logger.getInstance(javaClass<DeprecatedSymbolUsageInWholeProjectFix>());
|
||||
|
||||
override fun getFamilyName() = "Replace deprecated symbol usage in whole project"
|
||||
|
||||
override fun getText() = text
|
||||
|
||||
override fun startInWriteAction() = false
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
val targetPsiElement = element.getReference()?.resolve()
|
||||
return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty
|
||||
}
|
||||
|
||||
override fun invoke(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
bindingContext: BindingContext,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||
project: Project,
|
||||
editor: Editor?
|
||||
) {
|
||||
val psiElement = element.getReference()!!.resolve()!!
|
||||
|
||||
ProgressManager.getInstance().run(
|
||||
object : Task.Modal(project, "Applying '$text'", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val usages = runReadAction {
|
||||
val searchScope = JetSourceFilterScope.kotlinSources(GlobalSearchScope.projectScope(project), project)
|
||||
val findUsagesHandler = KotlinFindUsagesHandlerFactory(project).createFindUsagesHandler(psiElement, false)!!
|
||||
val processor = CommonProcessors.CollectProcessor<UsageInfo>()
|
||||
val options = createFindUsagesOptions(psiElement, searchScope, project)
|
||||
findUsagesHandler.processElementUsages(psiElement, processor, options)
|
||||
processor.getResults().map { it.getElement() }.filterIsInstance<JetSimpleNameExpression>()
|
||||
}
|
||||
replaceUsages(project, usages, replacement)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun createFindUsagesOptions(element: PsiElement, searchScope: GlobalSearchScope, project: Project): FindUsagesOptions {
|
||||
val options: FindUsagesOptions = when (element) {
|
||||
is JetNamedFunction -> {
|
||||
with(KotlinFunctionFindUsagesOptions(project)) {
|
||||
isSkipImportStatements = true
|
||||
isOverridingMethods = false
|
||||
isImplementingMethods = false
|
||||
isIncludeInherited = false
|
||||
isIncludeOverloadUsages = false
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
is JetProperty -> {
|
||||
with(KotlinPropertyFindUsagesOptions(project)) {
|
||||
isSkipImportStatements = true
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException(element.toString()) //TODO?
|
||||
}
|
||||
|
||||
options.searchScope = searchScope
|
||||
options.isSearchForTextOccurrences = false
|
||||
return options
|
||||
}
|
||||
|
||||
private fun replaceUsages(project: Project, usages: Collection<JetSimpleNameExpression>, replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression) {
|
||||
UIUtil.invokeLaterIfNeeded {
|
||||
var replacedCount = 0
|
||||
project.executeCommand(getText()) {
|
||||
runWriteAction {
|
||||
for (usage in usages) {
|
||||
try {
|
||||
if (!usage.isValid()) continue // TODO: nested calls
|
||||
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL)
|
||||
val resolvedCall = element.getResolvedCall(bindingContext) ?: continue
|
||||
if (!resolvedCall.getStatus().isSuccess()) continue
|
||||
DeprecatedSymbolUsageFixBase.performReplacement(usage, bindingContext, resolvedCall, replacement)
|
||||
replacedCount++
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
MessagesEx.showInfoMessage(null, "$replacedCount usages found and replaced", getText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : JetSingleIntentionActionFactory() {
|
||||
//TODO: better rendering needed
|
||||
private val RENDERER = DescriptorRendererBuilder()
|
||||
.setModifiers()
|
||||
.setNameShortness(NameShortness.SHORT)
|
||||
// .setWithoutTypeParameters(true)
|
||||
.setParameterNameRenderingPolicy(DescriptorRenderer.ParameterNameRenderingPolicy.NONE)
|
||||
.setReceiverAfterName(true)
|
||||
.setRenderCompanionObjectName(true)
|
||||
.setWithoutSuperTypes(true)
|
||||
.setStartFromName(true)
|
||||
.setWithDefinedIn(false)
|
||||
.build()
|
||||
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null
|
||||
val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(diagnostic).getA()
|
||||
val replacement = DeprecatedSymbolUsageFixBase.replaceWithPattern(descriptor) ?: return null
|
||||
val descriptorName = RENDERER.render(descriptor)
|
||||
return DeprecatedSymbolUsageInWholeProjectFix(nameExpression, replacement, "Replace usages of '$descriptorName' in whole project")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -345,5 +345,6 @@ public class QuickFixRegistrar {
|
||||
QuickFixes.factories.put(UNRESOLVED_REFERENCE, ReplaceObsoleteLabelSyntaxFix.Companion.createWholeProjectFixFactory());
|
||||
|
||||
QuickFixes.factories.put(DEPRECATED_SYMBOL_WITH_MESSAGE, DeprecatedSymbolUsageFix.Companion);
|
||||
QuickFixes.factories.put(DEPRECATED_SYMBOL_WITH_MESSAGE, DeprecatedSymbolUsageInWholeProjectFix.Companion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,12 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementVisitor
|
||||
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.asExpression
|
||||
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -35,6 +36,8 @@ import org.jetbrains.kotlin.psi.psiUtil.collectElementsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
@@ -43,7 +46,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
//TODO: use ReplaceWith from package kotlin
|
||||
data class ReplaceWith(val expression: String, vararg val imports: String)
|
||||
@@ -61,6 +65,20 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
resolutionFacade: ResolutionFacade,
|
||||
file: JetFile/*TODO: drop it*/,
|
||||
project: Project
|
||||
): ReplacementExpression {
|
||||
val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor)
|
||||
DescriptorUtils.unwrapFakeOverride(symbolDescriptor)
|
||||
else
|
||||
symbolDescriptor).getOriginal()
|
||||
return analyzeOriginal(annotation, originalDescriptor, resolutionFacade, file, project)
|
||||
}
|
||||
|
||||
private fun analyzeOriginal(
|
||||
annotation: ReplaceWith,
|
||||
symbolDescriptor: CallableDescriptor,
|
||||
resolutionFacade: ResolutionFacade,
|
||||
file: JetFile/*TODO: drop it*/,
|
||||
project: Project
|
||||
): ReplacementExpression {
|
||||
val psiFactory = JetPsiFactory(project)
|
||||
var expression = psiFactory.createExpression(annotation.expression)
|
||||
@@ -159,7 +177,12 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
|
||||
getResolutionScope(descriptor.getContainingDeclaration()!!),
|
||||
RedeclarationHandler.DO_NOTHING)
|
||||
is LocalVariableDescriptor -> {
|
||||
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as JetDeclaration
|
||||
declaration.analyze()[BindingContext.RESOLUTION_SCOPE, declaration]
|
||||
}
|
||||
|
||||
//TODO?
|
||||
else -> throw IllegalArgumentException("Cannot find resolution scope for $descriptor")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with '1'" "true"
|
||||
|
||||
fun foo(): Int {
|
||||
@deprecated("", ReplaceWith("1"))
|
||||
fun localFun() = 1
|
||||
|
||||
return <caret>localFun()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with '1'" "true"
|
||||
|
||||
fun foo(): Int {
|
||||
@deprecated("", ReplaceWith("1"))
|
||||
fun localFun() = 1
|
||||
|
||||
return <caret>1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with '1'" "true"
|
||||
|
||||
fun foo(): Int {
|
||||
@deprecated("", ReplaceWith("1"))
|
||||
val localVar = 1
|
||||
|
||||
return <caret>localVar
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace with '1'" "true"
|
||||
|
||||
fun foo(): Int {
|
||||
@deprecated("", ReplaceWith("1"))
|
||||
val localVar = 1
|
||||
|
||||
return <caret>1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import newPack.newFun
|
||||
import pack.oldFun
|
||||
|
||||
fun x() {
|
||||
newFun(1 + 1)
|
||||
newFun(2 + 1)
|
||||
}
|
||||
|
||||
fun y() {
|
||||
newFun(3 + 1)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import pack.PackPackage;
|
||||
|
||||
class C {
|
||||
void foo() {
|
||||
PackPackage.oldFun(-1);
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package newPack
|
||||
|
||||
fun newFun(p: Int){}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package pack
|
||||
|
||||
@deprecated("", ReplaceWith("newFun(p + 1)", "newPack.newFun"))
|
||||
fun oldFun(p: Int) {
|
||||
newFun(p + 1)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Replace usages of 'oldFun(Int): Unit' in whole project" "true"
|
||||
|
||||
import newPack.newFun
|
||||
import pack.oldFun
|
||||
|
||||
fun foo() {
|
||||
<caret>newFun(0 + 1)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import pack.oldFun
|
||||
|
||||
fun x() {
|
||||
oldFun(1)
|
||||
oldFun(2)
|
||||
}
|
||||
|
||||
fun y() {
|
||||
oldFun(3)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import pack.PackPackage;
|
||||
|
||||
class C {
|
||||
void foo() {
|
||||
PackPackage.oldFun(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace usages of 'oldFun(Int): Unit' in whole project" "true"
|
||||
|
||||
import pack.oldFun
|
||||
|
||||
fun foo() {
|
||||
<caret>oldFun(0)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package newPack
|
||||
|
||||
fun newFun(p: Int){}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package pack
|
||||
|
||||
@deprecated("", ReplaceWith("newFun(p + 1)", "newPack.newFun"))
|
||||
fun oldFun(p: Int) {
|
||||
newFun(p + 1)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun x() {
|
||||
pack.bar(pack.newProp)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package pack
|
||||
|
||||
@deprecated("", ReplaceWith("newProp"))
|
||||
val oldProp: String = ""
|
||||
|
||||
fun foo(s: String){}
|
||||
fun bar(s: String){}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace usages of 'oldProp: String' in whole project" "true"
|
||||
|
||||
import pack.*
|
||||
|
||||
fun foo() {
|
||||
foo(<caret>newProp)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun x() {
|
||||
pack.bar(pack.oldProp)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package pack
|
||||
|
||||
@deprecated("", ReplaceWith("newProp"))
|
||||
val oldProp: String = ""
|
||||
|
||||
fun foo(s: String){}
|
||||
fun bar(s: String){}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace usages of 'oldProp: String' in whole project" "true"
|
||||
|
||||
import pack.*
|
||||
|
||||
fun foo() {
|
||||
foo(<caret>oldProp)
|
||||
}
|
||||
@@ -881,6 +881,27 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/toMethodFromCompanionObject.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/wholeProject")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class WholeProject extends AbstractQuickFixMultiFileTest {
|
||||
public void testAllFilesPresentInWholeProject() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/wholeProject"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("function.before.Main.kt")
|
||||
public void testFunction() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("property.before.Main.kt")
|
||||
public void testProperty() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration")
|
||||
|
||||
@@ -3070,6 +3070,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localFun.kt")
|
||||
public void testLocalFun() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localVar.kt")
|
||||
public void testLocalVar() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("memberFunction.kt")
|
||||
public void testMemberFunction() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/memberFunction.kt");
|
||||
@@ -3195,6 +3207,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/usageInDerivedClassGeneric.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/expressions")
|
||||
@@ -3624,7 +3637,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class MissingConstructorKeyword extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInMissingConstructorKeyword() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/missingConstructorKeyword"), Pattern.compile("^(\\w+)\\.kt$"), true);
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/missingConstructorKeyword"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("basic.kt")
|
||||
@@ -3639,7 +3652,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ObsoleteLabelSyntax extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInObsoleteLabelSyntax() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/obsoleteLabelSyntax"), Pattern.compile("^(\\w+)\\.kt$"), true);
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/obsoleteLabelSyntax"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("lambda.kt")
|
||||
@@ -3660,7 +3673,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class RemoveNameFromFunctionExpression extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInRemoveNameFromFunctionExpression() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/removeNameFromFunctionExpression"), Pattern.compile("^(\\w+)\\.kt$"), true);
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/removeNameFromFunctionExpression"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("basic.kt")
|
||||
|
||||
Reference in New Issue
Block a user