Unify usage of ProgressManager.progressIndicator

This commit is contained in:
Nikolay Krasko
2018-04-12 14:29:08 +03:00
parent 1b1e4ab970
commit 2260f66e02
10 changed files with 19 additions and 948 deletions
+1
View File
@@ -280,6 +280,7 @@
<list>
<Problem reference="com.intellij.testFramework.PlatformTestCase#createModuleAt" reason="Not static anymore in 181 after 7dacf096c47d2125e17031c71a037b63ab00ec53" />
<Problem reference="com.intellij.testFramework.PlatformTestCase#doCreateRealModuleIn" reason="Not static anymore in 181 after 7dacf096c47d2125e17031c71a037b63ab00ec53" />
<Problem reference="com.intellij.openapi.progress.ProgressManager#getProgressIndicator" reason="Nullable in 181. Temporary use progressIndicatorNullable instead." />
</list>
</option>
</inspection_tool>
@@ -18,6 +18,9 @@ package org.jetbrains.kotlin.idea.util.application
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
fun <T> runReadAction(action: () -> T): T {
@@ -41,4 +44,10 @@ fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -
CommandProcessor.getInstance().executeCommand(this, { result = command() }, name, groupId)
@Suppress("USELESS_CAST")
return result as T
}
}
/**
* ProgressManager.getProgressIndicator() is nullable in 181 and dynamic again in 182
* BUNCH: 181
*/
val ProgressManager.progressIndicatorNullable: ProgressIndicator? get() = progressIndicator
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiModifier
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.name.FqName
@@ -54,7 +55,7 @@ class SearchNotPropertyCandidatesAction : AnAction() {
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
ProgressManager.getInstance().progressIndicator.isIndeterminate = true
ProgressManager.getInstance().progressIndicatorNullable!!.isIndeterminate = true
processAllDescriptors(packageDesc, project)
}
},
@@ -1,164 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.actions.internal
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.ProgressManager.progress
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
class SearchNotPropertyCandidatesAction : AnAction() {
override fun actionPerformed(e: AnActionEvent?) {
val project = e?.project!!
val psiFile = e.getData(CommonDataKeys.PSI_FILE) as? KtFile ?: return
val desc = psiFile.findModuleDescriptor()
val result = Messages.showInputDialog("Enter package FqName", "Search for Not Property candidates", Messages.getQuestionIcon())
val packageDesc = try {
val fqName = FqName.fromSegments(result!!.split('.'))
desc.getPackage(fqName)
}
catch (e: Exception) {
return
}
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
ProgressManager.getInstance().progressIndicator!!.isIndeterminate = true
processAllDescriptors(packageDesc, project)
}
},
"Searching for Not Property candidates",
true,
project)
}
private fun processAllDescriptors(desc: DeclarationDescriptor, project: Project) {
val processed = mutableSetOf<DeclarationDescriptor>()
var pFunctions = 0
val matchedDescriptors = mutableSetOf<FunctionDescriptor>()
fun recursive(desc: DeclarationDescriptor) {
if (desc in processed) return
progress("Step 1: Collecting ${processed.size}:$pFunctions:${matchedDescriptors.size}", "$desc")
when (desc) {
is ModuleDescriptor -> recursive(desc.getPackage(FqName("java")))
is ClassDescriptor -> desc.unsubstitutedMemberScope.getContributedDescriptors { true }.forEach(::recursive)
is PackageViewDescriptor -> desc.memberScope.getContributedDescriptors { true }.forEach(::recursive)
is FunctionDescriptor -> {
if (desc.isFromJava) {
val name = desc.fqNameUnsafe.shortName().asString()
if (name.length > 3 &&
((name.startsWith("get") && desc.valueParameters.isEmpty() && desc.returnType != null) ||
(name.startsWith("set") && desc.valueParameters.size == 1))) {
if (desc in matchedDescriptors) return
matchedDescriptors += desc
}
}
pFunctions++
return
}
}
processed += desc
}
recursive(desc)
val resultDescriptors = mutableSetOf<FunctionDescriptor>()
matchedDescriptors.flatMapTo(resultDescriptors) {
sequenceOf(it, *(it.overriddenDescriptors.toTypedArray())).asIterable()
}
println("Found ${resultDescriptors.size} accessors")
fun PsiMethod.isTrivial(): Boolean {
val t = this.text
val s = t.indexOf('{')
val e = t.lastIndexOf('}')
return if (s != e && s != -1) t.substring(s, e).lines().size <= 3 else true
}
val descriptorToPsiBinding = mutableMapOf<FunctionDescriptor, PsiMethod>()
var i = 0
resultDescriptors.forEach { desc ->
progress("Step 2: ${i++} of ${resultDescriptors.size}", "$desc")
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, desc)
.filterIsInstance<PsiMethod>()
.firstOrNull() ?: return@forEach
val abstract = source.modifierList.hasModifierProperty(PsiModifier.ABSTRACT)
if (!abstract) {
if (!source.isTrivial()) {
descriptorToPsiBinding[desc] = source
}
}
}
val nonTrivial = mutableSetOf<FunctionDescriptor>()
i = 0
descriptorToPsiBinding.forEach { t, u ->
progress("Step 3: ${i++} of ${descriptorToPsiBinding.size}", "$t")
val descriptors = t.overriddenDescriptors
var impl = false
descriptors.forEach {
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, it).filterIsInstance<PsiMethod>().firstOrNull()
if (source != null) {
if (source.body != null || source.hasModifierProperty(PsiModifier.ABSTRACT))
nonTrivial += it
impl = true
}
}
if (u.body != null)
if (!impl)
nonTrivial += t
}
nonTrivial.forEach(::println)
println("Non trivial count: ${nonTrivial.size}")
}
override fun update(e: AnActionEvent) {
if (!KotlinInternalMode.enabled) {
e.presentation.isVisible = false
e.presentation.isEnabled = false
}
else {
e.presentation.isVisible = true
e.presentation.isEnabled = e.getData(CommonDataKeys.PSI_FILE) is KtFile
}
}
}
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
@@ -186,7 +187,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator.fraction = (i + 1) * progressStep
ProgressManager.getInstance().progressIndicatorNullable!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
@@ -1,356 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.explicateReceiverOf
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type parameter to receiver"
) {
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val parameterToMove = functionTypeParameterList.parameters.getOrNull(data.typeParameterIndex) ?: return
val typeReferenceToMove = parameterToMove.typeReference ?: return
functionType.setReceiverTypeReference(typeReferenceToMove)
functionTypeParameterList.removeParameter(parameterToMove)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val argumentList = callExpression.valueArgumentList ?: return
val expressionToMove = argumentList.arguments.getOrNull(data.typeParameterIndex)?.getArgumentExpression() ?: return
val callWithReceiver = KtPsiFactory(callExpression).createExpressionByPattern("$0.$1", expressionToMove, callExpression) as KtQualifiedExpression
(callWithReceiver.selectorExpression as KtCallExpression).valueArgumentList!!.removeArgument(data.typeParameterIndex)
callExpression.replace(callWithReceiver)
}
}
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractProcessableUsageInfo<KtSimpleNameExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val lambdaType = data.lambdaType
val validator = CollectingNameValidator()
val parameterNames = lambdaType.arguments
.dropLast(1)
.map { KotlinNameSuggester.suggestNamesByType(it.type, validator, "p").first() }
val receiver = parameterNames.getOrNull(data.typeParameterIndex) ?: return
val arguments = parameterNames.filter { it != receiver }
val adapterLambda = KtPsiFactory(expression).createLambdaExpression(
parameterNames.joinToString(),
"$receiver.${expression.text}(${arguments.joinToString()})"
)
expression.replaced(adapterLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
}
class LambdaInfo(element: KtExpression) : AbstractProcessableUsageInfo<KtExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(expression)
if (expression is KtLambdaExpression || (expression !is KtSimpleNameExpression && expression !is KtCallableReferenceExpression)) {
expression.forEachDescendantOfType<KtThisExpression> {
if (it.getLabelName() != null) return@forEachDescendantOfType
val descriptor = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
it.replace(psiFactory.createExpression(explicateReceiverOf(descriptor)))
}
}
if (expression is KtLambdaExpression) {
expression.valueParameters.getOrNull(data.typeParameterIndex)?.let { parameterToConvert ->
val thisRefExpr = psiFactory.createThisExpression()
for (ref in ReferencesSearch.search(parameterToConvert, LocalSearchScope(expression))) {
(ref.element as? KtSimpleNameExpression)?.replace(thisRefExpr)
}
val lambda = expression.functionLiteral
lambda.valueParameterList!!.removeParameter(parameterToConvert)
if (lambda.valueParameters.isEmpty()) {
lambda.arrow?.delete()
}
}
return
}
val originalLambdaTypes = data.lambdaType
val originalParameterTypes = originalLambdaTypes.arguments.dropLast(1).map { it.type }
val calleeText = when (expression) {
is KtSimpleNameExpression -> expression.text
is KtCallableReferenceExpression -> "(${expression.text})"
else -> generateVariable(expression)
}
val parameterNameValidator = CollectingNameValidator(
if (expression !is KtCallableReferenceExpression) listOf(calleeText) else emptyList()
)
val parameterNamesWithReceiver = originalParameterTypes.mapIndexed { i, type ->
if (i != data.typeParameterIndex) KotlinNameSuggester.suggestNamesByType(type, parameterNameValidator, "p").first() else "this"
}
val parameterNames = parameterNamesWithReceiver.filter { it != "this" }
val body = psiFactory.createExpression(parameterNamesWithReceiver.joinToString(prefix = "$calleeText(", postfix = ")"))
val replacingLambda = psiFactory.buildExpression {
appendFixedText("{ ")
appendFixedText(parameterNames.joinToString())
appendFixedText(" -> ")
appendExpression(body)
appendFixedText(" }")
} as KtLambdaExpression
expression.replaced(replacingLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
private fun generateVariable(expression: KtExpression): String {
var baseCallee = ""
KotlinIntroduceVariableHandler.doRefactoring(project, null, expression, false, emptyList()) {
baseCallee = it.name!!
}
return baseCallee
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
usageLoop@ for (ref in callable.searchReferencesOrMethodReferences()) {
val refElement = ref.element ?: continue
when (ref) {
is KtSimpleReference<*> -> processExternalUsage(conflicts, refElement, usages)
is KtReference -> continue@usageLoop
else -> {
if (data.isFirstParameter) continue@usageLoop
conflicts.putValue(
refElement,
"Can't replace non-Kotlin reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
if (callable is KtFunction) {
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(
conflicts: MultiMap<PsiElement, String>,
refElement: PsiElement,
usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>
) {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (callElement != null) {
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = getArgumentExpressionToProcess(callElement, context) ?: return
if (!data.isFirstParameter
&& callElement is KtConstructorDelegationCall
&& expressionToProcess !is KtLambdaExpression
&& expressionToProcess !is KtSimpleNameExpression
&& expressionToProcess !is KtCallableReferenceExpression) {
conflicts.putValue(
expressionToProcess,
"Following expression won't be processed since refactoring can't preserve its semantics: ${expressionToProcess.text}"
)
return
}
if (!checkThisExpressionsAreExplicatable(conflicts, context, expressionToProcess)) return
if (data.isFirstParameter && expressionToProcess !is KtLambdaExpression) return
usages += LambdaInfo(expressionToProcess)
return
}
if (data.isFirstParameter) return
val callableReference = refElement.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }
if (callableReference != null) {
conflicts.putValue(
refElement,
"Callable reference transformation is not supported: " + StringUtil.htmlEmphasize(callableReference.text)
)
return
}
}
private fun getArgumentExpressionToProcess(callElement: KtCallElement, context: BindingContext): KtExpression? {
return callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
}
private fun checkThisExpressionsAreExplicatable(conflicts: MultiMap<PsiElement, String>, context: BindingContext, expressionToProcess: KtExpression): Boolean {
for (thisExpr in expressionToProcess.collectDescendantsOfType<KtThisExpression>()) {
if (thisExpr.getLabelName() != null) continue
val descriptor = context[BindingContext.REFERENCE_TARGET, thisExpr.instanceReference] ?: continue
if (explicateReceiverOf(descriptor) == "this") {
conflicts.putValue(
thisExpr,
"Following expression won't be processed since refactoring can't preserve its semantics: ${thisExpr.text}"
)
return false
}
}
return true
}
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression }
if (callExpression != null) {
usages += ParameterCallInfo(callExpression)
}
else if (!data.isFirstParameter) {
usages += InternalReferencePassInfo(element)
}
}
}
}
}
class ConversionData(
val typeParameterIndex: Int,
val functionParameterIndex: Int,
val lambdaType: KotlinType,
val function: KtFunction
) {
val isFirstParameter: Boolean get() = typeParameterIndex == 0
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val parameter = parent as? KtParameter ?: return null
val functionType = parameter.getParentOfTypeAndBranch<KtFunctionType> { parameterList } ?: return null
if (functionType.receiverTypeReference != null) return null
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val typeParameterIndex = functionType.parameters.indexOf(parameter)
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
setReceiverTypeReference(element)
parameterList!!.removeParameter(data.typeParameterIndex)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
@@ -125,7 +126,7 @@ class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntent
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator.fraction = (i + 1) * progressStep
ProgressManager.getInstance().progressIndicatorNullable!!.fraction = (i + 1) * progressStep
if (callable !is KtFunction) continue
@@ -1,222 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type receiver to parameter"
) {
class ConversionData(
val functionParameterIndex: Int,
val lambdaReceiverType: KotlinType,
val function: KtFunction
) {
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val functionTypeReceiver = functionType.receiverTypeReference ?: return
val parameterToAdd = KtPsiFactory(project).createFunctionTypeParameter(functionTypeReceiver)
functionTypeParameterList.addParameterBefore(parameterToAdd, functionTypeParameterList.parameters.firstOrNull())
functionType.setReceiverTypeReference(null)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() ?: return
val receiverExpression = qualifiedExpression.receiverExpression
val argumentList = callExpression.getOrCreateValueArgumentList()
argumentList.addArgumentBefore(KtPsiFactory(project).createArgument(receiverExpression), argumentList.arguments.firstOrNull())
qualifiedExpression.replace(callExpression)
}
}
class LambdaInfo(element: KtLambdaExpression) : AbstractProcessableUsageInfo<KtLambdaExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val lambda = element?.functionLiteral ?: return
val context = lambda.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(project)
val validator = CollectingNameValidator(
lambda.valueParameters.mapNotNull { it.name },
NewDeclarationNameValidator(lambda.bodyExpression!!, null, NewDeclarationNameValidator.Target.VARIABLES)
)
val newParameterName = KotlinNameSuggester.suggestNamesByType(data.lambdaReceiverType, validator, "p").first()
val newParameterRefExpression = psiFactory.createExpression(newParameterName)
lambda.forEachDescendantOfType<KtThisExpression> {
val thisTarget = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
if (DescriptorToSourceUtilsIde.getAnyDeclaration(project, thisTarget) == lambda) {
it.replace(newParameterRefExpression)
}
}
val lambdaParameterList = lambda.getOrCreateParameterList()
val parameterToAdd = psiFactory.createLambdaParameterList(newParameterName).parameters.first()
lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull())
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is KtFunction) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
for (ref in callable.searchReferencesOrMethodReferences()) {
if (ref !is KtSimpleReference<*>) continue
processExternalUsage(ref, usages)
}
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(ref: KtSimpleReference<*>, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val callElement = ref.element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
?: return
if (expressionToProcess is KtLambdaExpression) {
usages += LambdaInfo(expressionToProcess)
}
}
private fun processInternalUsages(callable: KtFunction, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression } ?: continue
usages += ParameterCallInfo(callExpression)
}
}
}
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val functionTypeReceiver = parent as? KtFunctionTypeReceiver ?: return null
val functionType = functionTypeReceiver.parent as? KtFunctionType ?: return null
val lambdaReceiverType = functionType
.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL))
?.getReceiverTypeFromFunctionType()
?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
parameterList!!.addParameterBefore(
KtPsiFactory(element).createFunctionTypeParameter(element),
parameterList!!.parameters.firstOrNull()
)
setReceiverTypeReference(null)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
@@ -97,7 +98,7 @@ class ConvertPropertyToFunctionIntention : SelfTargetingIntention<KtProperty>(Kt
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator.fraction = (i + 1)*progressStep
ProgressManager.getInstance().progressIndicatorNullable!!.fraction = (i + 1)*progressStep
if (callable !is PsiNamedElement) continue
@@ -1,201 +0,0 @@
/*
* 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.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
import java.util.*
class ConvertPropertyToFunctionIntention : SelfTargetingIntention<KtProperty>(KtProperty::class.java, "Convert property to function"), LowPriorityAction {
private inner class Converter(
project: Project,
descriptor: CallableDescriptor
): CallableRefactoring<CallableDescriptor>(project, descriptor, text) {
private val newName: String = JvmAbi.getterName(callableDescriptor.name.asString())
private fun convertProperty(originalProperty: KtProperty, psiFactory: KtPsiFactory) {
val property = originalProperty.copy() as KtProperty
val getter = property.getter
val sampleFunction = psiFactory.createFunction("fun foo() {\n\n}")
property.valOrVarKeyword.replace(sampleFunction.funKeyword!!)
property.addAfter(psiFactory.createParameterList("()"), property.nameIdentifier)
if (property.initializer == null) {
if (getter != null) {
val dropGetterTo = (getter.equalsToken ?: getter.bodyExpression)
?.siblings(forward = false, withItself = false)
?.firstOrNull { it !is PsiWhiteSpace }
getter.deleteChildRange(getter.firstChild, dropGetterTo)
val dropPropertyFrom = getter
.siblings(forward = false, withItself = false)
.first { it !is PsiWhiteSpace }
.nextSibling
property.deleteChildRange(dropPropertyFrom, getter.prevSibling)
}
}
property.setName(newName)
originalProperty.replace(psiFactory.createFunction(property.text))
}
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val propertyName = callableDescriptor.name.asString()
val nameChanged = propertyName != newName
val getterName = JvmAbi.getterName(callableDescriptor.name.asString())
val conflicts = MultiMap<PsiElement, String>()
val callables = getAffectedCallables(project, descriptorsForChange)
val kotlinRefsToReplaceWithCall = ArrayList<KtSimpleNameExpression>()
val refsToRename = ArrayList<PsiReference>()
val javaRefsToReplaceWithCall = ArrayList<PsiReferenceExpression>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
if (callable is KtProperty) {
callableDescriptor.getContainingScope()
?.findFunction(callableDescriptor.name, NoLookupLocation.FROM_IDE) { it.valueParameters.isEmpty() }
?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
else if (callable is PsiMethod) {
callable.containingClass
?.findMethodsByName(propertyName, true)
// as is necessary here: see KT-10386
?.firstOrNull { it.parameterList.parametersCount == 0 && !callables.contains(it.namedUnwrappedElement as PsiElement?) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
val usages = ReferencesSearch.search(callable)
for (usage in usages) {
if (usage is KtReference) {
if (usage is KtSimpleNameReference) {
val expression = usage.expression
if (expression.getCall(expression.analyze(BodyResolveMode.PARTIAL)) != null
&& expression.getStrictParentOfType<KtCallableReferenceExpression>() == null) {
kotlinRefsToReplaceWithCall.add(expression)
}
else if (nameChanged) {
refsToRename.add(usage)
}
}
else {
val refElement = usage.element
conflicts.putValue(
refElement,
"Unrecognized reference will be skipped: " + StringUtil.htmlEmphasize(refElement.text)
)
}
continue
}
val refElement = usage.element
if (refElement.text.endsWith(getterName)) continue
if (usage is PsiJavaReference) {
if (usage.resolve() is PsiField && usage is PsiReferenceExpression) {
javaRefsToReplaceWithCall.add(usage)
}
continue
}
conflicts.putValue(
refElement,
"Can't replace foreign reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val kotlinPsiFactory = KtPsiFactory(project)
val javaPsiFactory = PsiElementFactory.SERVICE.getInstance(project)
val newKotlinCallExpr = kotlinPsiFactory.createExpression("$newName()")
kotlinRefsToReplaceWithCall.forEach { it.replace(newKotlinCallExpr) }
refsToRename.forEach { it.handleElementRename(newName) }
javaRefsToReplaceWithCall.forEach {
val getterRef = it.handleElementRename(newName)
getterRef.replace(javaPsiFactory.createExpressionFromText("${getterRef.text}()", null))
}
callables.forEach {
when (it) {
is KtProperty -> convertProperty(it, kotlinPsiFactory)
is PsiMethod -> it.name = newName
}
}
}
}
}
}
override fun startInWriteAction(): Boolean = false
override fun isApplicableTo(element: KtProperty, caretOffset: Int): Boolean {
val identifier = element.nameIdentifier ?: return false
if (!identifier.textRange.containsOffset(caretOffset)) return false
return element.delegate == null && !element.isVar && !element.isLocal && (element.initializer == null || element.getter == null)
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val descriptor = element.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
Converter(element.project, descriptor).run()
}
}