Unify usage of ProgressManager.progressIndicator
This commit is contained in:
+1
@@ -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
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
-164
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
-356
@@ -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() }
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
-222
@@ -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
|
||||
|
||||
|
||||
-201
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user