diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt index 853c3ea584c..86d684eb60e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt @@ -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(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()) { - 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>() - - 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 { 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 { 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, + 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 { 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, - 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("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 { 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>)?.map { it.getValue() } ?: emptyList() - return ReplaceWith(pattern, *imports.toTypedArray()) - } - - private val USER_CODE_KEY = Key("USER_CODE") - private val FROM_PARAMETER_KEY = Key("FROM_PARAMETER") - private val FROM_THIS_KEY = Key("FROM_THIS") } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt new file mode 100644 index 00000000000..508eeb53e70 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFixBase.kt @@ -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(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, + 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>)?.map { it.getValue() } ?: emptyList() + return ReplaceWith(pattern, *imports.toTypedArray()) + } + + public fun performReplacement( + element: JetSimpleNameExpression, + bindingContext: BindingContext, + resolvedCall: ResolvedCall, + 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()) { + 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>() + + 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 { 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 { 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 { 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, + 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("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 { 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("USER_CODE") + private val FROM_PARAMETER_KEY = Key("FROM_PARAMETER") + private val FROM_THIS_KEY = Key("FROM_THIS") + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageInWholeProjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageInWholeProjectFix.kt new file mode 100644 index 00000000000..e2b2be78ea0 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageInWholeProjectFix.kt @@ -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()); + + 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, + 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() + val options = createFindUsagesOptions(psiElement, searchScope, project) + findUsagesHandler.processElementUsages(psiElement, processor, options) + processor.getResults().map { it.getElement() }.filterIsInstance() + } + 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, 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") + } + + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java index 5d3fdd3309f..050b2114fed 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.java @@ -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); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceWithAnnotationAnalyzer.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceWithAnnotationAnalyzer.kt index 767546c5181..a6110359fcb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceWithAnnotationAnalyzer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceWithAnnotationAnalyzer.kt @@ -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") } } diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt b/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt new file mode 100644 index 00000000000..6aa4f21c48b --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt @@ -0,0 +1,8 @@ +// "Replace with '1'" "true" + +fun foo(): Int { + @deprecated("", ReplaceWith("1")) + fun localFun() = 1 + + return localFun() +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt.after new file mode 100644 index 00000000000..2bee0f1646f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/localFun.kt.after @@ -0,0 +1,8 @@ +// "Replace with '1'" "true" + +fun foo(): Int { + @deprecated("", ReplaceWith("1")) + fun localFun() = 1 + + return 1 +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt b/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt new file mode 100644 index 00000000000..8af18d4c068 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt @@ -0,0 +1,8 @@ +// "Replace with '1'" "true" + +fun foo(): Int { + @deprecated("", ReplaceWith("1")) + val localVar = 1 + + return localVar +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt.after new file mode 100644 index 00000000000..2c865610483 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/localVar.kt.after @@ -0,0 +1,8 @@ +// "Replace with '1'" "true" + +fun foo(): Int { + @deprecated("", ReplaceWith("1")) + val localVar = 1 + + return 1 +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.1.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.1.kt new file mode 100644 index 00000000000..dac7a22446f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.1.kt @@ -0,0 +1,11 @@ +import newPack.newFun +import pack.oldFun + +fun x() { + newFun(1 + 1) + newFun(2 + 1) +} + +fun y() { + newFun(3 + 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.JavaClass.java b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.JavaClass.java new file mode 100644 index 00000000000..4e077591a89 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.JavaClass.java @@ -0,0 +1,7 @@ +import pack.PackPackage; + +class C { + void foo() { + PackPackage.oldFun(-1); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.NewDeclaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.NewDeclaration.kt new file mode 100644 index 00000000000..c204ec1d6c4 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.NewDeclaration.kt @@ -0,0 +1,3 @@ +package newPack + +fun newFun(p: Int){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.OldDeclaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.OldDeclaration.kt new file mode 100644 index 00000000000..778afce50a9 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.OldDeclaration.kt @@ -0,0 +1,6 @@ +package pack + +@deprecated("", ReplaceWith("newFun(p + 1)", "newPack.newFun")) +fun oldFun(p: Int) { + newFun(p + 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.kt new file mode 100644 index 00000000000..4d1539edeb6 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.after.kt @@ -0,0 +1,8 @@ +// "Replace usages of 'oldFun(Int): Unit' in whole project" "true" + +import newPack.newFun +import pack.oldFun + +fun foo() { + newFun(0 + 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.1.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.1.kt new file mode 100644 index 00000000000..bc4354c30ad --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.1.kt @@ -0,0 +1,10 @@ +import pack.oldFun + +fun x() { + oldFun(1) + oldFun(2) +} + +fun y() { + oldFun(3) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.JavaClass.java b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.JavaClass.java new file mode 100644 index 00000000000..4e077591a89 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.JavaClass.java @@ -0,0 +1,7 @@ +import pack.PackPackage; + +class C { + void foo() { + PackPackage.oldFun(-1); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.Main.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.Main.kt new file mode 100644 index 00000000000..0e23a9bd7d4 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.Main.kt @@ -0,0 +1,7 @@ +// "Replace usages of 'oldFun(Int): Unit' in whole project" "true" + +import pack.oldFun + +fun foo() { + oldFun(0) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.NewDeclaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.NewDeclaration.kt new file mode 100644 index 00000000000..c204ec1d6c4 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.NewDeclaration.kt @@ -0,0 +1,3 @@ +package newPack + +fun newFun(p: Int){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.OldDeclaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.OldDeclaration.kt new file mode 100644 index 00000000000..778afce50a9 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/function.before.OldDeclaration.kt @@ -0,0 +1,6 @@ +package pack + +@deprecated("", ReplaceWith("newFun(p + 1)", "newPack.newFun")) +fun oldFun(p: Int) { + newFun(p + 1) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.1.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.1.kt new file mode 100644 index 00000000000..6cea475e1c2 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.1.kt @@ -0,0 +1,3 @@ +fun x() { + pack.bar(pack.newProp) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.Declarations.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.Declarations.kt new file mode 100644 index 00000000000..7be0078f90f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.Declarations.kt @@ -0,0 +1,7 @@ +package pack + +@deprecated("", ReplaceWith("newProp")) +val oldProp: String = "" + +fun foo(s: String){} +fun bar(s: String){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.kt new file mode 100644 index 00000000000..0b4550b47e6 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.after.kt @@ -0,0 +1,7 @@ +// "Replace usages of 'oldProp: String' in whole project" "true" + +import pack.* + +fun foo() { + foo(newProp) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.1.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.1.kt new file mode 100644 index 00000000000..eec52b0c439 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.1.kt @@ -0,0 +1,3 @@ +fun x() { + pack.bar(pack.oldProp) +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Declarations.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Declarations.kt new file mode 100644 index 00000000000..7be0078f90f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Declarations.kt @@ -0,0 +1,7 @@ +package pack + +@deprecated("", ReplaceWith("newProp")) +val oldProp: String = "" + +fun foo(s: String){} +fun bar(s: String){} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Main.kt b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Main.kt new file mode 100644 index 00000000000..129f7ecd2af --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/wholeProject/property.before.Main.kt @@ -0,0 +1,7 @@ +// "Replace usages of 'oldProp: String' in whole project" "true" + +import pack.* + +fun foo() { + foo(oldProp) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 1b65a63221f..62a7ec8fdb6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -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") diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 56a6c7d62dc..bc9b4992c7c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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")