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