Deprecated symbol usages fix for the whole project
This commit is contained in:
@@ -16,371 +16,47 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.quickfix
|
package org.jetbrains.kotlin.idea.quickfix
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.intention.HighPriorityAction
|
||||||
import com.intellij.codeInsight.intention.IntentionAction
|
import com.intellij.codeInsight.intention.IntentionAction
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Key
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
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.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||||
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.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.getCalleeExpressionIfAny
|
||||||
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
|
|
||||||
//TODO: replace all in project quickfixes on usage and on deprecated annotation
|
|
||||||
public class DeprecatedSymbolUsageFix(
|
public class DeprecatedSymbolUsageFix(
|
||||||
element: JetSimpleNameExpression/*TODO?*/,
|
element: JetSimpleNameExpression/*TODO?*/,
|
||||||
val replaceWith: ReplaceWith
|
replaceWith: ReplaceWith
|
||||||
) : JetIntentionAction<JetSimpleNameExpression>(element) {
|
) : DeprecatedSymbolUsageFixBase(element, replaceWith), HighPriorityAction {
|
||||||
|
|
||||||
override fun getFamilyName() = "Replace deprecated symbol usage"
|
override fun getFamilyName() = "Replace deprecated symbol usage"
|
||||||
|
|
||||||
override fun getText() = "Replace with '${replaceWith.expression}'" //TODO: substitute?
|
override fun getText() = "Replace with '${replaceWith.expression}'" //TODO: substitute?
|
||||||
|
|
||||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
override fun invoke(
|
||||||
if (!super.isAvailable(project, editor, file)) return false
|
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||||
|
bindingContext: BindingContext,
|
||||||
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false
|
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||||
if (!resolvedCall.getStatus().isSuccess()) return false
|
project: Project,
|
||||||
val descriptor = resolvedCall.getResultingDescriptor()
|
editor: Editor?
|
||||||
if (replaceWithPattern(descriptor) != replaceWith) return false
|
) {
|
||||||
|
val result = DeprecatedSymbolUsageFixBase.performReplacement(element, bindingContext, resolvedCall, replacement)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val offset = (result.getCalleeExpressionIfAny() ?: result).getTextOffset()
|
val offset = (result.getCalleeExpressionIfAny() ?: result).getTextOffset()
|
||||||
editor?.moveCaret(offset)
|
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() {
|
companion object : JetSingleIntentionActionFactory() {
|
||||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||||
val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null
|
val nameExpression = diagnostic.getPsiElement() as? JetSimpleNameExpression ?: return null
|
||||||
val descriptor = Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.cast(diagnostic).getA()
|
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)
|
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(UNRESOLVED_REFERENCE, ReplaceObsoleteLabelSyntaxFix.Companion.createWholeProjectFixFactory());
|
||||||
|
|
||||||
QuickFixes.factories.put(DEPRECATED_SYMBOL_WITH_MESSAGE, DeprecatedSymbolUsageFix.Companion);
|
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 com.intellij.psi.PsiRecursiveElementVisitor
|
||||||
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
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.ResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.core.asExpression
|
import org.jetbrains.kotlin.idea.core.asExpression
|
||||||
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
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.FqName
|
||||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||||
import org.jetbrains.kotlin.name.Name
|
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.getReceiverExpression
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
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.FunctionDescriptorUtil
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
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.Printer
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import java.util.*
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedHashSet
|
||||||
|
|
||||||
//TODO: use ReplaceWith from package kotlin
|
//TODO: use ReplaceWith from package kotlin
|
||||||
data class ReplaceWith(val expression: String, vararg val imports: String)
|
data class ReplaceWith(val expression: String, vararg val imports: String)
|
||||||
@@ -61,6 +65,20 @@ object ReplaceWithAnnotationAnalyzer {
|
|||||||
resolutionFacade: ResolutionFacade,
|
resolutionFacade: ResolutionFacade,
|
||||||
file: JetFile/*TODO: drop it*/,
|
file: JetFile/*TODO: drop it*/,
|
||||||
project: Project
|
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 {
|
): ReplacementExpression {
|
||||||
val psiFactory = JetPsiFactory(project)
|
val psiFactory = JetPsiFactory(project)
|
||||||
var expression = psiFactory.createExpression(annotation.expression)
|
var expression = psiFactory.createExpression(annotation.expression)
|
||||||
@@ -159,7 +177,12 @@ object ReplaceWithAnnotationAnalyzer {
|
|||||||
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
|
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
|
||||||
getResolutionScope(descriptor.getContainingDeclaration()!!),
|
getResolutionScope(descriptor.getContainingDeclaration()!!),
|
||||||
RedeclarationHandler.DO_NOTHING)
|
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")
|
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");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/toMethodFromCompanionObject.before.Main.kt");
|
||||||
doTestWithExtraFile(fileName);
|
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")
|
@TestMetadata("idea/testData/quickfix/migration")
|
||||||
|
|||||||
@@ -3070,6 +3070,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("memberFunction.kt")
|
||||||
public void testMemberFunction() throws Exception {
|
public void testMemberFunction() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/memberFunction.kt");
|
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");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/usageInDerivedClassGeneric.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/quickfix/expressions")
|
@TestMetadata("idea/testData/quickfix/expressions")
|
||||||
@@ -3624,7 +3637,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class MissingConstructorKeyword extends AbstractQuickFixTest {
|
public static class MissingConstructorKeyword extends AbstractQuickFixTest {
|
||||||
public void testAllFilesPresentInMissingConstructorKeyword() throws Exception {
|
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")
|
@TestMetadata("basic.kt")
|
||||||
@@ -3639,7 +3652,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class ObsoleteLabelSyntax extends AbstractQuickFixTest {
|
public static class ObsoleteLabelSyntax extends AbstractQuickFixTest {
|
||||||
public void testAllFilesPresentInObsoleteLabelSyntax() throws Exception {
|
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")
|
@TestMetadata("lambda.kt")
|
||||||
@@ -3660,7 +3673,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class RemoveNameFromFunctionExpression extends AbstractQuickFixTest {
|
public static class RemoveNameFromFunctionExpression extends AbstractQuickFixTest {
|
||||||
public void testAllFilesPresentInRemoveNameFromFunctionExpression() throws Exception {
|
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")
|
@TestMetadata("basic.kt")
|
||||||
|
|||||||
Reference in New Issue
Block a user