181: compilation fix when access progressIndicator in ProgressManager by adding !!
This commit is contained in:
committed by
Nikolay Krasko
parent
395a817ada
commit
f291873cf8
+434
@@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* 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.debugger.evaluate
|
||||||
|
|
||||||
|
import com.intellij.debugger.DebuggerManagerEx
|
||||||
|
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
|
||||||
|
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||||
|
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||||
|
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||||
|
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||||
|
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||||
|
import com.intellij.ide.highlighter.JavaFileType
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.psi.util.PsiTypesUtil
|
||||||
|
import com.intellij.util.IncorrectOperationException
|
||||||
|
import com.intellij.util.concurrency.Semaphore
|
||||||
|
import com.intellij.xdebugger.XDebuggerManager
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import org.jetbrains.eval4j.jdi.asValue
|
||||||
|
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
||||||
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.j2k
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.j2kText
|
||||||
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
|
||||||
|
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||||
|
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
|
val contextElement = getWrappedContextElement(project, context)
|
||||||
|
if (contextElement == null) {
|
||||||
|
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}")
|
||||||
|
}
|
||||||
|
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) {
|
||||||
|
KtExpressionCodeFragment(
|
||||||
|
project,
|
||||||
|
"fragment.kt",
|
||||||
|
item.text,
|
||||||
|
initImports(item.imports),
|
||||||
|
contextElement
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KtBlockCodeFragment(
|
||||||
|
project,
|
||||||
|
"fragment.kt",
|
||||||
|
item.text,
|
||||||
|
initImports(item.imports),
|
||||||
|
contextElement
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||||
|
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||||
|
val debuggerSession = debuggerContext.debuggerSession
|
||||||
|
if (debuggerSession == null || debuggerContext.suspendContext == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val semaphore = Semaphore()
|
||||||
|
semaphore.down()
|
||||||
|
val nameRef = AtomicReference<KotlinType>()
|
||||||
|
val worker = object : KotlinRuntimeTypeEvaluator(
|
||||||
|
null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!!
|
||||||
|
) {
|
||||||
|
override fun typeCalculationFinished(type: KotlinType?) {
|
||||||
|
nameRef.set(type)
|
||||||
|
semaphore.up()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||||
|
|
||||||
|
for (i in 0..50) {
|
||||||
|
ProgressManager.checkCanceled()
|
||||||
|
if (semaphore.waitFor(20)) break
|
||||||
|
}
|
||||||
|
|
||||||
|
nameRef.get()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (contextElement != null && contextElement !is KtElement) {
|
||||||
|
codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, {
|
||||||
|
val emptyFile = createFakeFileWithJavaContextElement("", contextElement)
|
||||||
|
|
||||||
|
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||||
|
val debuggerSession = debuggerContext.debuggerSession
|
||||||
|
if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint")
|
||||||
|
return@putCopyableUserData emptyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
|
||||||
|
if (frameDescriptor == null) {
|
||||||
|
LOG.warn("Couldn't get info about 'this' and local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}")
|
||||||
|
return@putCopyableUserData emptyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
val receiverTypeReference =
|
||||||
|
frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference
|
||||||
|
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||||
|
|
||||||
|
val kotlinVariablesText =
|
||||||
|
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
||||||
|
|
||||||
|
val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}"
|
||||||
|
|
||||||
|
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||||
|
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||||
|
val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull()
|
||||||
|
|
||||||
|
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
|
||||||
|
val semaphore = Semaphore()
|
||||||
|
semaphore.down()
|
||||||
|
|
||||||
|
var frameInfo: FrameInfo? = null
|
||||||
|
|
||||||
|
val worker = object : DebuggerCommandImpl() {
|
||||||
|
override fun action() {
|
||||||
|
try {
|
||||||
|
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
|
||||||
|
contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame
|
||||||
|
else
|
||||||
|
debuggerContext.frameProxy?.stackFrame
|
||||||
|
|
||||||
|
val visibleVariables = frame?.let {
|
||||||
|
val values = it.getValues(it.visibleVariables())
|
||||||
|
values.filterValues { it != null }
|
||||||
|
} ?: emptyMap()
|
||||||
|
|
||||||
|
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
|
||||||
|
} catch (ignored: AbsentInformationException) {
|
||||||
|
// Debug info unavailable
|
||||||
|
} finally {
|
||||||
|
semaphore.up()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||||
|
|
||||||
|
for (i in 0..50) {
|
||||||
|
if (semaphore.waitFor(20)) break
|
||||||
|
}
|
||||||
|
|
||||||
|
return frameInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
|
||||||
|
|
||||||
|
private fun initImports(imports: String?): String? {
|
||||||
|
if (imports != null && !imports.isEmpty()) {
|
||||||
|
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
||||||
|
.mapNotNull { fixImportIfNeeded(it) }
|
||||||
|
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fixImportIfNeeded(import: String): String? {
|
||||||
|
// skip arrays
|
||||||
|
if (import.endsWith("[]")) {
|
||||||
|
return fixImportIfNeeded(import.removeSuffix("[]").trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip primitive types
|
||||||
|
if (PsiTypesUtil.boxIfPossible(import) != import) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return import
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? {
|
||||||
|
val newContext = getContextElement(context)
|
||||||
|
if (newContext !is KtElement) return newContext
|
||||||
|
return wrapContextIfNeeded(project, context, newContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
|
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
||||||
|
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
||||||
|
val javaExpression = try {
|
||||||
|
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
|
||||||
|
} catch (e: IncorrectOperationException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val importList = try {
|
||||||
|
kotlinCodeFragment.importsAsImportList()?.let {
|
||||||
|
(PsiFileFactory.getInstance(project).createFileFromText(
|
||||||
|
"dummy.java", JavaFileType.INSTANCE, it.text
|
||||||
|
) as? PsiJavaFile)?.importList
|
||||||
|
}
|
||||||
|
} catch (e: IncorrectOperationException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) {
|
||||||
|
var convertedFragment: KtExpressionCodeFragment? = null
|
||||||
|
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
|
||||||
|
try {
|
||||||
|
val newText = javaExpression.j2kText()
|
||||||
|
val newImports = importList?.j2kText()
|
||||||
|
if (newText != null) {
|
||||||
|
convertedFragment = KtExpressionCodeFragment(
|
||||||
|
project,
|
||||||
|
kotlinCodeFragment.name,
|
||||||
|
newText,
|
||||||
|
newImports,
|
||||||
|
kotlinCodeFragment.context
|
||||||
|
)
|
||||||
|
|
||||||
|
AfterConversionPass(project, J2kPostProcessor(formatCode = false)).run(convertedFragment!!, range = null)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// ignored because text can be invalid
|
||||||
|
LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return convertedFragment ?: kotlinCodeFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kotlinCodeFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isContextAccepted(contextElement: PsiElement?): Boolean {
|
||||||
|
return when {
|
||||||
|
// PsiCodeBlock -> DummyHolder -> originalElement
|
||||||
|
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
|
||||||
|
contextElement == null -> false
|
||||||
|
contextElement.language == KotlinFileType.INSTANCE.language -> true
|
||||||
|
contextElement.language == JavaFileType.INSTANCE.language -> {
|
||||||
|
getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
|
||||||
|
|
||||||
|
override fun getEvaluatorBuilder() = KotlinEvaluationBuilder
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = Logger.getInstance(this::class.java)
|
||||||
|
|
||||||
|
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
|
||||||
|
|
||||||
|
private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
||||||
|
|
||||||
|
fun getContextElement(elementAt: PsiElement?): PsiElement? {
|
||||||
|
if (elementAt == null) return null
|
||||||
|
|
||||||
|
if (elementAt is PsiCodeBlock) {
|
||||||
|
return getContextElement(elementAt.context?.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementAt is KtLightClass) {
|
||||||
|
return getContextElement(elementAt.kotlinOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
val containingFile = elementAt.containingFile
|
||||||
|
if (containingFile is PsiJavaFile) return elementAt
|
||||||
|
if (containingFile !is KtFile) return null
|
||||||
|
|
||||||
|
// elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window)
|
||||||
|
val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) {
|
||||||
|
PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset
|
||||||
|
?: elementAt.textOffset
|
||||||
|
} else {
|
||||||
|
elementAt.textOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KtElement.takeIfAcceptedAsCodeFragmentContext() = takeIf { KotlinEditorTextProvider.isAcceptedAsCodeFragmentContext(it) }
|
||||||
|
|
||||||
|
PsiTreeUtil.findElementOfClassAtOffset(containingFile, lineStartOffset, KtExpression::class.java, false)
|
||||||
|
?.takeIfAcceptedAsCodeFragmentContext()
|
||||||
|
?.let { return CodeInsightUtils.getTopmostElementAtOffset(it, lineStartOffset, KtExpression::class.java) }
|
||||||
|
|
||||||
|
KotlinEditorTextProvider.findExpressionInner(elementAt, true)
|
||||||
|
?.takeIfAcceptedAsCodeFragmentContext()
|
||||||
|
?.let { return it }
|
||||||
|
|
||||||
|
return containingFile
|
||||||
|
}
|
||||||
|
|
||||||
|
//internal for tests
|
||||||
|
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val variables = markupMap.entries.associate {
|
||||||
|
val (value, markup) = it
|
||||||
|
"${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value
|
||||||
|
}.filterValues { it != null } as Map<String, Value>
|
||||||
|
|
||||||
|
return variables.kotlinVariablesAsText(project) to variables
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
|
||||||
|
val psiNameHelper = PsiNameHelper.getInstance(project)
|
||||||
|
for ((variableName, variableValue) in entries) {
|
||||||
|
if (!psiNameHelper.isIdentifier(variableName)) continue
|
||||||
|
|
||||||
|
val variableTypeName = variableValue.type()?.name() ?: continue
|
||||||
|
|
||||||
|
val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue
|
||||||
|
|
||||||
|
sb.append("${kotlinProperty.text}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("val _debug_context_val = 1\n")
|
||||||
|
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? {
|
||||||
|
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
|
||||||
|
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
|
||||||
|
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
|
||||||
|
return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
|
||||||
|
|
||||||
|
val className = variableTypeName.replace("$", ".").substringBefore("[]")
|
||||||
|
val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project))
|
||||||
|
val type = (if (value !is PrimitiveValue && classType.resolve() == null)
|
||||||
|
CommonClassNames.JAVA_LANG_OBJECT
|
||||||
|
else
|
||||||
|
className).addArraySuffix()
|
||||||
|
|
||||||
|
val field = PsiElementFactory.SERVICE.getInstance(project)
|
||||||
|
.createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
|
||||||
|
val ktField = field.j2k() as? KtProperty
|
||||||
|
ktField?.modifierList?.delete()
|
||||||
|
return ktField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? {
|
||||||
|
val markupMap: Map<*, ValueMarkup>? =
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestMode)
|
||||||
|
NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess)
|
||||||
|
else
|
||||||
|
(XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers
|
||||||
|
|
||||||
|
if (markupMap == null || markupMap.isEmpty()) return newContext
|
||||||
|
|
||||||
|
val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
|
||||||
|
if (text.isEmpty()) return newContext
|
||||||
|
|
||||||
|
return createWrappingContext(text, labels, newContext, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||||
|
val javaFile = javaContext.containingFile as? PsiJavaFile
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
|
||||||
|
javaFile?.packageName?.takeUnless { it.isBlank() }?.let {
|
||||||
|
sb.append("package ").append(it.quoteIfNeeded()).append("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
javaFile?.importList?.let { sb.append(it.text).append("\n") }
|
||||||
|
|
||||||
|
sb.append(funWithLocalVariables)
|
||||||
|
|
||||||
|
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal for test
|
||||||
|
private fun createWrappingContext(
|
||||||
|
newFragmentText: String,
|
||||||
|
labels: Map<String, Value>,
|
||||||
|
originalContext: KtElement?,
|
||||||
|
project: Project
|
||||||
|
): KtElement? {
|
||||||
|
val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext)
|
||||||
|
|
||||||
|
codeFragment.accept(object : KtTreeVisitorVoid() {
|
||||||
|
override fun visitProperty(property: KtProperty) {
|
||||||
|
val reference = labels[property.name]
|
||||||
|
if (reference != null) {
|
||||||
|
property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return codeFragment.getContentElement().statements.lastOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* 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.actions
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.navigation.NavigationUtil
|
||||||
|
import com.intellij.ide.scratch.ScratchFileService
|
||||||
|
import com.intellij.ide.scratch.ScratchRootType
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.Messages
|
||||||
|
import com.intellij.openapi.ui.ex.MessagesEx
|
||||||
|
import com.intellij.openapi.vfs.VfsUtilCore
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.openapi.vfs.VirtualFileVisitor
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiErrorElement
|
||||||
|
import com.intellij.psi.PsiJavaFile
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
|
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
||||||
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||||
|
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class JavaToKotlinAction : AnAction() {
|
||||||
|
companion object {
|
||||||
|
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
|
||||||
|
val ioFile = File(javaFile.path.replace('/', File.separatorChar))
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
while (true) {
|
||||||
|
val fileName = javaFile.nameWithoutExtension + (if (i > 0) i else "") + ".kt"
|
||||||
|
if (!ioFile.resolveSibling(fileName).exists()) return fileName
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val title = "Convert Java to Kotlin"
|
||||||
|
|
||||||
|
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
|
||||||
|
val result = ArrayList<VirtualFile>()
|
||||||
|
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
|
||||||
|
try {
|
||||||
|
val document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile)
|
||||||
|
if (document == null) {
|
||||||
|
MessagesEx.error(psiFile.project, "Failed to save conversion result: couldn't find document for " + psiFile.name).showLater()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
document.replaceString(0, document.textLength, text)
|
||||||
|
FileDocumentManager.getInstance().saveDocument(document)
|
||||||
|
|
||||||
|
val virtualFile = psiFile.virtualFile
|
||||||
|
if (ScratchRootType.getInstance().containsFile(virtualFile)) {
|
||||||
|
val mapping = ScratchFileService.getInstance().scratchesMapping
|
||||||
|
mapping.setMapping(virtualFile, KotlinFileType.INSTANCE.language)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val fileName = uniqueKotlinFileName(virtualFile)
|
||||||
|
virtualFile.rename(this, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: IOException) {
|
||||||
|
MessagesEx.error(psiFile.project, e.message ?: "").showLater()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, enableExternalCodeProcessing: Boolean = true): List<KtFile> {
|
||||||
|
var converterResult: JavaToKotlinConverter.FilesResult? = null
|
||||||
|
fun convert() {
|
||||||
|
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
|
||||||
|
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().progressIndicator!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
|
::convert,
|
||||||
|
title,
|
||||||
|
true,
|
||||||
|
project)) return emptyList()
|
||||||
|
|
||||||
|
|
||||||
|
var externalCodeUpdate: (() -> Unit)? = null
|
||||||
|
|
||||||
|
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
|
||||||
|
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
|
||||||
|
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
|
||||||
|
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||||
|
{
|
||||||
|
runReadAction {
|
||||||
|
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().progressIndicator!!)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
true,
|
||||||
|
project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return project.executeWriteCommand("Convert files from Java to Kotlin", null) {
|
||||||
|
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
|
||||||
|
|
||||||
|
val newFiles = saveResults(javaFiles, converterResult!!.results)
|
||||||
|
|
||||||
|
externalCodeUpdate?.invoke()
|
||||||
|
|
||||||
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
|
||||||
|
newFiles.singleOrNull()?.let {
|
||||||
|
FileEditorManager.getInstance(project).openFile(it, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
newFiles.map { it.toPsiFile(project) as KtFile }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val javaFiles = selectedJavaFiles(e).filter { it.isWritable }.toList()
|
||||||
|
val project = CommonDataKeys.PROJECT.getData(e.dataContext)!!
|
||||||
|
|
||||||
|
val firstSyntaxError = javaFiles.asSequence().map { PsiTreeUtil.findChildOfType(it, PsiErrorElement::class.java) }.firstOrNull()
|
||||||
|
|
||||||
|
if (firstSyntaxError != null) {
|
||||||
|
val count = javaFiles.filter { PsiTreeUtil.hasErrorElements(it) }.count()
|
||||||
|
val question = firstSyntaxError.containingFile.name +
|
||||||
|
(if (count > 1) " and ${count - 1} more Java files" else " file") +
|
||||||
|
" contain syntax errors, the conversion result may be incorrect"
|
||||||
|
|
||||||
|
val okText = "Investigate Errors"
|
||||||
|
val cancelText = "Proceed with Conversion"
|
||||||
|
if (Messages.showOkCancelDialog(
|
||||||
|
project,
|
||||||
|
question,
|
||||||
|
title,
|
||||||
|
okText,
|
||||||
|
cancelText,
|
||||||
|
Messages.getWarningIcon()
|
||||||
|
) == Messages.OK) {
|
||||||
|
NavigationUtil.activateFileWithPsiElement(firstSyntaxError.navigationElement)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertFiles(javaFiles, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return
|
||||||
|
val project = e.project ?: return
|
||||||
|
|
||||||
|
e.presentation.isEnabled = isAnyJavaFileSelected(project, virtualFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAnyJavaFileSelected(project: Project, files: Array<VirtualFile>): Boolean {
|
||||||
|
val manager = PsiManager.getInstance(project)
|
||||||
|
|
||||||
|
if (files.any { manager.findFile(it) is PsiJavaFile && it.isWritable }) return true
|
||||||
|
return files.any { it.isDirectory && isAnyJavaFileSelected(project, it.children) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectedJavaFiles(e: AnActionEvent): Sequence<PsiJavaFile> {
|
||||||
|
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf()
|
||||||
|
val project = e.project ?: return sequenceOf()
|
||||||
|
return allJavaFiles(virtualFiles, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<PsiJavaFile> {
|
||||||
|
val manager = PsiManager.getInstance(project)
|
||||||
|
return allFiles(filesOrDirs)
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { manager.findFile(it) as? PsiJavaFile }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allFiles(filesOrDirs: Array<VirtualFile>): Collection<VirtualFile> {
|
||||||
|
val result = ArrayList<VirtualFile>()
|
||||||
|
for (file in filesOrDirs) {
|
||||||
|
VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor<Unit>() {
|
||||||
|
override fun visitFile(file: VirtualFile): Boolean {
|
||||||
|
result.add(file)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+356
@@ -0,0 +1,356 @@
|
|||||||
|
/*
|
||||||
|
* 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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
+222
@@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* 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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.quickfix
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.intention.IntentionAction
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiNamedElement
|
||||||
|
import org.jetbrains.kotlin.asJava.unwrapped
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
|
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
||||||
|
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||||
|
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.OverridingUtil
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||||
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
|
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
|
||||||
|
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||||
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
|
import org.jetbrains.kotlin.utils.ifEmpty
|
||||||
|
import java.util.ArrayList
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.LinkedHashSet
|
||||||
|
import kotlin.collections.filter
|
||||||
|
|
||||||
|
class ChangeSuspendInHierarchyFix(
|
||||||
|
element: KtNamedFunction,
|
||||||
|
private val addModifier: Boolean
|
||||||
|
) : KotlinQuickFixAction<KtNamedFunction>(element) {
|
||||||
|
override fun getFamilyName(): String {
|
||||||
|
return if (addModifier) {
|
||||||
|
"Add 'suspend' modifier to all functions in hierarchy"
|
||||||
|
} else {
|
||||||
|
"Remove 'suspend' modifier from all functions in hierarchy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getText() = familyName
|
||||||
|
|
||||||
|
override fun startInWriteAction() = false
|
||||||
|
|
||||||
|
private fun findAllFunctionToProcess(project: Project): Set<KtNamedFunction> {
|
||||||
|
val result = LinkedHashSet<KtNamedFunction>()
|
||||||
|
|
||||||
|
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
|
||||||
|
|
||||||
|
val function = element ?: return emptySet()
|
||||||
|
val functionDescriptor = function.unsafeResolveToDescriptor() as FunctionDescriptor
|
||||||
|
|
||||||
|
val baseFunctionDescriptors = functionDescriptor.findTopMostOverriddables()
|
||||||
|
baseFunctionDescriptors.forEach { baseFunctionDescriptor ->
|
||||||
|
val baseClassDescriptor = baseFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return@forEach
|
||||||
|
val baseClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, baseClassDescriptor) ?: return@forEach
|
||||||
|
|
||||||
|
val name = (baseClass as? PsiNamedElement)?.name ?: return@forEach
|
||||||
|
progressIndicator.text = "Looking for class $name inheritors..."
|
||||||
|
val classes = listOf(baseClass) + HierarchySearchRequest(baseClass, baseClass.useScope).searchInheritors()
|
||||||
|
classes.mapNotNullTo(result) {
|
||||||
|
val subClass = it.unwrapped as? KtClassOrObject ?: return@mapNotNullTo null
|
||||||
|
val classDescriptor = subClass.unsafeResolveToDescriptor() as ClassDescriptor
|
||||||
|
val substitutor = getTypeSubstitutor(baseClassDescriptor.defaultType, classDescriptor.defaultType)
|
||||||
|
?: return@mapNotNullTo null
|
||||||
|
val signatureInSubClass = baseFunctionDescriptor.substitute(substitutor) as FunctionDescriptor
|
||||||
|
val subFunctionDescriptor = classDescriptor.findCallableMemberBySignature(signatureInSubClass, true)
|
||||||
|
?: return@mapNotNullTo null
|
||||||
|
subFunctionDescriptor.source.getPsi() as? KtNamedFunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
|
val functions = project.runSynchronouslyWithProgress("Analyzing class hierarchy...", true) {
|
||||||
|
runReadAction { findAllFunctionToProcess(project) }
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
runWriteAction {
|
||||||
|
functions.forEach {
|
||||||
|
if (addModifier) {
|
||||||
|
it.addModifier(KtTokens.SUSPEND_KEYWORD)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it.removeModifier(KtTokens.SUSPEND_KEYWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : KotlinIntentionActionsFactory() {
|
||||||
|
fun FunctionDescriptor.findTopMostOverriddables(): List<FunctionDescriptor> {
|
||||||
|
val overridablesCache = HashMap<FunctionDescriptor, List<FunctionDescriptor>>()
|
||||||
|
|
||||||
|
fun FunctionDescriptor.getOverridables(): List<FunctionDescriptor> {
|
||||||
|
return overridablesCache.getOrPut(this) {
|
||||||
|
val classDescriptor = containingDeclaration as? ClassDescriptorWithResolutionScopes ?: return emptyList()
|
||||||
|
DescriptorUtils.getSuperclassDescriptors(classDescriptor).flatMap { superClassDescriptor ->
|
||||||
|
if (superClassDescriptor !is ClassDescriptorWithResolutionScopes) return@flatMap emptyList<FunctionDescriptor>()
|
||||||
|
val candidates = superClassDescriptor.unsubstitutedMemberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE)
|
||||||
|
val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, classDescriptor.defaultType)
|
||||||
|
?: return@flatMap emptyList<FunctionDescriptor>()
|
||||||
|
candidates.filter {
|
||||||
|
val signature = it.substitute(substitutor) as FunctionDescriptor
|
||||||
|
classDescriptor.findCallableMemberBySignature(signature, true) == this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DFS.dfs(
|
||||||
|
listOf(this),
|
||||||
|
{ it?.getOverridables() ?: emptyList() },
|
||||||
|
object : DFS.CollectingNodeHandler<FunctionDescriptor, FunctionDescriptor, ArrayList<FunctionDescriptor>>(ArrayList()) {
|
||||||
|
override fun afterChildren(current: FunctionDescriptor) {
|
||||||
|
if (current.getOverridables().isEmpty()) {
|
||||||
|
result.add(current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Collection<DeclarationDescriptor>.getOverridables(
|
||||||
|
currentDescriptor: FunctionDescriptor
|
||||||
|
): List<DeclarationDescriptor> {
|
||||||
|
val currentClassDescriptor = currentDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
|
||||||
|
return filter {
|
||||||
|
if (it !is FunctionDescriptor || it == currentDescriptor) return@filter false
|
||||||
|
if (it.isSuspend == currentDescriptor.isSuspend) return@filter false
|
||||||
|
val containingClassDescriptor = it.containingDeclaration as? ClassDescriptor ?: return@filter false
|
||||||
|
if (!currentClassDescriptor.isSubclassOf(containingClassDescriptor)) return@filter false
|
||||||
|
val substitutor = getTypeSubstitutor(
|
||||||
|
containingClassDescriptor.defaultType,
|
||||||
|
currentClassDescriptor.defaultType
|
||||||
|
) ?: return@filter false
|
||||||
|
val signatureInCurrentClass = it.substitute(substitutor) ?: return@filter false
|
||||||
|
OverridingUtil.DEFAULT.isOverridableBy(signatureInCurrentClass, currentDescriptor, null).result ==
|
||||||
|
OverridingUtil.OverrideCompatibilityInfo.Result.CONFLICT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||||
|
val currentFunction = diagnostic.psiElement as? KtNamedFunction ?: return emptyList()
|
||||||
|
val currentDescriptor = currentFunction.unsafeResolveToDescriptor() as FunctionDescriptor
|
||||||
|
Errors.CONFLICTING_OVERLOADS.cast(diagnostic).a.getOverridables(currentDescriptor).ifEmpty { return emptyList() }
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
ChangeSuspendInHierarchyFix(currentFunction, true),
|
||||||
|
ChangeSuspendInHierarchyFix(currentFunction, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 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.refactoring.move.moveClassesOrPackages
|
||||||
|
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.psi.PsiDirectory
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiPackage
|
||||||
|
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import com.intellij.refactoring.MoveDestination
|
||||||
|
import com.intellij.usageView.UsageInfo
|
||||||
|
import com.intellij.util.containers.MultiMap
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.createMoveUsageInfoIfPossible
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
|
||||||
|
import org.jetbrains.kotlin.idea.search.projectScope
|
||||||
|
import org.jetbrains.kotlin.idea.stubindex.KotlinExactPackagesIndex
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||||
|
|
||||||
|
class KotlinAwareDelegatingMoveDestination(
|
||||||
|
private val delegate: MoveDestination,
|
||||||
|
private val targetPackage: PsiPackage?,
|
||||||
|
private val targetDirectory: PsiDirectory?
|
||||||
|
) : MoveDestination by delegate {
|
||||||
|
override fun analyzeModuleConflicts(
|
||||||
|
elements: MutableCollection<PsiElement>,
|
||||||
|
conflicts: MultiMap<PsiElement, String>,
|
||||||
|
usages: Array<out UsageInfo>
|
||||||
|
) {
|
||||||
|
delegate.analyzeModuleConflicts(elements, conflicts, usages)
|
||||||
|
|
||||||
|
if (targetPackage == null || targetDirectory == null) return
|
||||||
|
|
||||||
|
val project = targetDirectory.project
|
||||||
|
val moveTarget = KotlinDirectoryMoveTarget(FqName(targetPackage.qualifiedName), targetDirectory)
|
||||||
|
val packagesIndex = KotlinExactPackagesIndex.getInstance()
|
||||||
|
val directoriesToMove = elements.flatMapTo(LinkedHashSet<PsiDirectory>()) {
|
||||||
|
(it as? PsiPackage)?.directories?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
val projectScope = project.projectScope()
|
||||||
|
val filesToProcess = elements.flatMapTo(LinkedHashSet<KtFile>()) {
|
||||||
|
if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val extraElementsForReferenceSearch = LinkedHashSet<PsiElement>()
|
||||||
|
val extraElementCollector = object : PsiRecursiveElementWalkingVisitor() {
|
||||||
|
override fun visitElement(element: PsiElement) {
|
||||||
|
if (element is KtNamedDeclaration && element.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
|
||||||
|
element.parentsWithSelf.lastOrNull { it is KtNamedDeclaration }?.let { extraElementsForReferenceSearch += it }
|
||||||
|
stopWalking()
|
||||||
|
}
|
||||||
|
super.visitElement(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesToProcess.flatMap {it.declarations}.forEach { it.accept(extraElementCollector) }
|
||||||
|
|
||||||
|
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
|
||||||
|
progressIndicator.pushState()
|
||||||
|
|
||||||
|
val extraUsages = ArrayList<UsageInfo>()
|
||||||
|
try {
|
||||||
|
progressIndicator.text = "Looking for Usages"
|
||||||
|
for ((index, element) in extraElementsForReferenceSearch.withIndex()) {
|
||||||
|
progressIndicator.fraction = (index + 1)/extraElementsForReferenceSearch.size.toDouble()
|
||||||
|
ReferencesSearch.search(element, projectScope).mapNotNullTo(extraUsages) { ref ->
|
||||||
|
createMoveUsageInfoIfPossible(ref, element, true, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
progressIndicator.popState()
|
||||||
|
}
|
||||||
|
|
||||||
|
filesToProcess.forEach {
|
||||||
|
analyzeConflictsInFile(it, extraUsages, moveTarget, directoriesToMove, conflicts) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user