Workaround KT-14531, Partial fix for KT-13600, KT-11620 J2K: Invalid conversion context on plain text pasting
For detailed info see KT-14531
This commit is contained in:
+11
-10
@@ -86,7 +86,7 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
|||||||
|
|
||||||
fun doConversion(): Result {
|
fun doConversion(): Result {
|
||||||
val dataForConversion = DataForConversion.prepare(data, project)
|
val dataForConversion = DataForConversion.prepare(data, project)
|
||||||
val result = convertCopiedCodeToKotlin(dataForConversion.elementsAndTexts, project)
|
val result = dataForConversion.elementsAndTexts.convertCodeToKotlin(project)
|
||||||
val referenceData = buildReferenceData(result.text, result.parseContext, dataForConversion.importsAndPackage, targetFile)
|
val referenceData = buildReferenceData(result.text, result.parseContext, dataForConversion.importsAndPackage, targetFile)
|
||||||
val text = if (result.textChanged) result.text else null
|
val text = if (result.textChanged) result.text else null
|
||||||
return Result(text, referenceData, result.importsToAdd)
|
return Result(text, referenceData, result.importsToAdd)
|
||||||
@@ -198,14 +198,14 @@ internal class ConversionResult(
|
|||||||
val textChanged: Boolean
|
val textChanged: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun convertCopiedCodeToKotlin(elementsAndTexts: Collection<Any>, project: Project): ConversionResult {
|
internal fun ElementAndTextList.convertCodeToKotlin(project: Project): ConversionResult {
|
||||||
val converter = JavaToKotlinConverter(
|
val converter = JavaToKotlinConverter(
|
||||||
project,
|
project,
|
||||||
ConverterSettings.defaultSettings,
|
ConverterSettings.defaultSettings,
|
||||||
IdeaJavaToKotlinServices
|
IdeaJavaToKotlinServices
|
||||||
)
|
)
|
||||||
|
|
||||||
val inputElements = elementsAndTexts.filterIsInstance<PsiElement>()
|
val inputElements = this.toList().filterIsInstance<PsiElement>()
|
||||||
val results = converter.elementsToKotlin(inputElements).results
|
val results = converter.elementsToKotlin(inputElements).results
|
||||||
val importsToAdd = LinkedHashSet<FqName>()
|
val importsToAdd = LinkedHashSet<FqName>()
|
||||||
|
|
||||||
@@ -213,9 +213,9 @@ internal fun convertCopiedCodeToKotlin(elementsAndTexts: Collection<Any>, projec
|
|||||||
val convertedCodeBuilder = StringBuilder()
|
val convertedCodeBuilder = StringBuilder()
|
||||||
val originalCodeBuilder = StringBuilder()
|
val originalCodeBuilder = StringBuilder()
|
||||||
var parseContext: ParseContext? = null
|
var parseContext: ParseContext? = null
|
||||||
for (o in elementsAndTexts) {
|
this.process(object : ElementsAndTextsProcessor {
|
||||||
if (o is PsiElement) {
|
override fun processElement(element: PsiElement) {
|
||||||
val originalText = o.text
|
val originalText = element.text
|
||||||
originalCodeBuilder.append(originalText)
|
originalCodeBuilder.append(originalText)
|
||||||
|
|
||||||
val result = results[resultIndex++]
|
val result = results[resultIndex++]
|
||||||
@@ -230,11 +230,12 @@ internal fun convertCopiedCodeToKotlin(elementsAndTexts: Collection<Any>, projec
|
|||||||
convertedCodeBuilder.append(originalText)
|
convertedCodeBuilder.append(originalText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
originalCodeBuilder.append(o)
|
override fun processText(string: String) {
|
||||||
convertedCodeBuilder.append(o as String)
|
originalCodeBuilder.append(string)
|
||||||
|
convertedCodeBuilder.append(string)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
val convertedCode = convertedCodeBuilder.toString()
|
val convertedCode = convertedCodeBuilder.toString()
|
||||||
val originalCode = originalCodeBuilder.toString()
|
val originalCode = originalCodeBuilder.toString()
|
||||||
|
|||||||
+30
-58
@@ -27,22 +27,23 @@ import com.intellij.openapi.project.DumbService
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiErrorElement
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import com.intellij.psi.PsiFileFactory
|
||||||
import com.intellij.util.LocalTimeCounter
|
import com.intellij.util.LocalTimeCounter
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.referenceExpression
|
|
||||||
import org.jetbrains.kotlin.idea.editor.KotlinEditorOptions
|
import org.jetbrains.kotlin.idea.editor.KotlinEditorOptions
|
||||||
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.ImportPath
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
|
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
|
||||||
import java.awt.datatransfer.DataFlavor
|
import java.awt.datatransfer.DataFlavor
|
||||||
@@ -103,10 +104,21 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
if (!confirmConvertJavaOnPaste(project, isPlainText = true)) return
|
if (!confirmConvertJavaOnPaste(project, isPlainText = true)) return
|
||||||
|
|
||||||
val copiedJavaCode = prepareCopiedJavaCodeByContext(text, conversionContext, pasteTarget)
|
val copiedJavaCode = prepareCopiedJavaCodeByContext(text, conversionContext, pasteTarget)
|
||||||
|
val dataForConversion = DataForConversion.prepare(copiedJavaCode, project)
|
||||||
|
|
||||||
val convertedText = copiedJavaCode.convertCodeToKotlin(project).text
|
val additionalImports = dataForConversion.tryResolveImports(targetFile)
|
||||||
|
var convertedImportsText = additionalImports.convertCodeToKotlin(project).text
|
||||||
|
|
||||||
|
val convertedText = dataForConversion.convertCodeToKotlin(project).text
|
||||||
|
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
|
|
||||||
|
val importsInsertOffset = targetFile.importList?.endOffset ?: 0
|
||||||
|
if (targetFile.importDirectives.isEmpty() && importsInsertOffset > 0)
|
||||||
|
convertedImportsText = "\n" + convertedImportsText
|
||||||
|
if (convertedImportsText.isNotBlank())
|
||||||
|
editor.document.insertString(importsInsertOffset, convertedImportsText)
|
||||||
|
|
||||||
val startOffset = bounds.startOffset
|
val startOffset = bounds.startOffset
|
||||||
editor.document.replaceString(startOffset, bounds.endOffset, convertedText)
|
editor.document.replaceString(startOffset, bounds.endOffset, convertedText)
|
||||||
|
|
||||||
@@ -116,16 +128,15 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
val newBounds = TextRange(startOffset, startOffset + convertedText.length)
|
val newBounds = TextRange(startOffset, startOffset + convertedText.length)
|
||||||
|
|
||||||
psiDocumentManager.commitAllDocuments()
|
psiDocumentManager.commitAllDocuments()
|
||||||
|
|
||||||
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, newBounds)
|
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, newBounds)
|
||||||
|
|
||||||
conversionPerformed = true
|
conversionPerformed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CopiedJavaCode.convertCodeToKotlin(project: Project): ConversionResult {
|
private fun DataForConversion.convertCodeToKotlin(project: Project): ConversionResult {
|
||||||
val dataForConversion = DataForConversion.prepare(this, project)
|
return elementsAndTexts.convertCodeToKotlin(project)
|
||||||
val conversionResult = convertCopiedCodeToKotlin(dataForConversion.elementsAndTexts, project)
|
|
||||||
return conversionResult
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val KtElement.pasteContext: KotlinContext
|
private val KtElement.pasteContext: KotlinContext
|
||||||
@@ -211,7 +222,14 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseAsFile(text: String, fileType: LanguageFileType, project: Project): PsiFile {
|
private fun parseAsFile(text: String, fileType: LanguageFileType, project: Project): PsiFile {
|
||||||
return PsiFileFactory.getInstance(project).createFileFromText("Dummy", fileType, text, LocalTimeCounter.currentTime(), true)
|
return PsiFileFactory.getInstance(project).createFileFromText("Dummy.java", fileType, text, LocalTimeCounter.currentTime(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataForConversion.tryResolveImports(targetFile: KtFile): ElementAndTextList {
|
||||||
|
val importResolver = PlainTextPasteImportResolver(this, targetFile)
|
||||||
|
importResolver.addImportsFromTargetFile()
|
||||||
|
importResolver.tryResolveReferences()
|
||||||
|
return ElementAndTextList(importResolver.addedImports.flatMap { listOf("\n", it) } + "\n\n") //TODO Non-manual formatting for import list
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode {
|
private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode {
|
||||||
@@ -225,53 +243,6 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
append(";\n")
|
append(";\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appendImport(importPath: ImportPath) {
|
|
||||||
append("import ")
|
|
||||||
append(importPath)
|
|
||||||
append(";\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun appendStaticImport(importPath: ImportPath) {
|
|
||||||
append("import static ")
|
|
||||||
append(importPath)
|
|
||||||
append(";\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetFile.importDirectives.forEach {
|
|
||||||
val importPath = it.importPath
|
|
||||||
val importedReference = it.importedReference
|
|
||||||
if (importPath != null && !importPath.hasAlias() && importedReference is KtDotQualifiedExpression) {
|
|
||||||
|
|
||||||
val receiver = importedReference
|
|
||||||
.receiverExpression
|
|
||||||
.referenceExpression()
|
|
||||||
?.mainReference
|
|
||||||
?.resolve()
|
|
||||||
val selector = importedReference
|
|
||||||
.selectorExpression
|
|
||||||
?.referenceExpression()
|
|
||||||
?.mainReference
|
|
||||||
?.resolve()
|
|
||||||
|
|
||||||
val isPackageReceiver = receiver is PsiPackage
|
|
||||||
val isClassReceiver = receiver is PsiClass
|
|
||||||
val isClassSelector = selector is PsiClass
|
|
||||||
|
|
||||||
if (importPath.isAllUnder) {
|
|
||||||
if (isClassReceiver)
|
|
||||||
appendStaticImport(importPath)
|
|
||||||
else if (isPackageReceiver)
|
|
||||||
appendImport(importPath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (isPackageReceiver && isClassSelector)
|
|
||||||
appendImport(importPath)
|
|
||||||
else if (isClassReceiver)
|
|
||||||
appendStaticImport(importPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val classDef = when (context) {
|
val classDef = when (context) {
|
||||||
@@ -300,11 +271,12 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
|
|
||||||
JavaContext.EXPRESSION -> createCopiedJavaCode(prefix, "$classDef {\nObject field = $\n}", text)
|
JavaContext.EXPRESSION -> createCopiedJavaCode(prefix, "$classDef {\nObject field = $\n}", text)
|
||||||
}
|
}
|
||||||
|
|
||||||
return copiedJavaCode
|
return copiedJavaCode
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createCopiedJavaCode(prefix: String, templateWithoutPrefix: String, text: String): CopiedJavaCode {
|
private fun createCopiedJavaCode(prefix: String, templateWithoutPrefix: String, text: String): CopiedJavaCode {
|
||||||
val template = "$prefix\n$templateWithoutPrefix"
|
val template = "$prefix$templateWithoutPrefix"
|
||||||
val index = template.indexOf("$")
|
val index = template.indexOf("$")
|
||||||
assert(index >= 0)
|
assert(index >= 0)
|
||||||
val fileText = template.substring(0, index) + text + template.substring(index + 1)
|
val fileText = template.substring(0, index) + text + template.substring(index + 1)
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class DataForConversion private constructor(
|
data class DataForConversion private constructor(
|
||||||
val elementsAndTexts: Collection<Any> /* list consisting of PsiElement's to convert and plain String's */,
|
val elementsAndTexts: ElementAndTextList /* list consisting of PsiElement's to convert and plain String's */,
|
||||||
val importsAndPackage: String
|
val importsAndPackage: String,
|
||||||
|
val file: PsiJavaFile
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
||||||
@@ -49,12 +50,12 @@ data class DataForConversion private constructor(
|
|||||||
file = PsiFileFactory.getInstance(project).createFileFromText(JavaLanguage.INSTANCE, newFileText) as PsiJavaFile
|
file = PsiFileFactory.getInstance(project).createFileFromText(JavaLanguage.INSTANCE, newFileText) as PsiJavaFile
|
||||||
}
|
}
|
||||||
|
|
||||||
val elementsAndTexts = ArrayList<Any>()
|
val elementsAndTexts = ElementAndTextList()
|
||||||
for (i in startOffsets.indices) {
|
for (i in startOffsets.indices) {
|
||||||
elementsAndTexts.collectElementsToConvert(file, fileText, TextRange(startOffsets[i], endOffsets[i]))
|
elementsAndTexts.collectElementsToConvert(file, fileText, TextRange(startOffsets[i], endOffsets[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return DataForConversion(elementsAndTexts, importsAndPackage)
|
return DataForConversion(elementsAndTexts, importsAndPackage, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clipTextIfNeeded(file: PsiJavaFile, fileText: String, startOffsets: IntArray, endOffsets: IntArray): String? {
|
private fun clipTextIfNeeded(file: PsiJavaFile, fileText: String, startOffsets: IntArray, endOffsets: IntArray): String? {
|
||||||
@@ -207,7 +208,7 @@ data class DataForConversion private constructor(
|
|||||||
return clipTo
|
return clipTo
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableList<Any>.collectElementsToConvert(
|
private fun ElementAndTextList.collectElementsToConvert(
|
||||||
file: PsiJavaFile,
|
file: PsiJavaFile,
|
||||||
fileText: String,
|
fileText: String,
|
||||||
range: TextRange
|
range: TextRange
|
||||||
@@ -218,8 +219,11 @@ data class DataForConversion private constructor(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
add(fileText.substring(range.start, elements.first().range.start))
|
add(fileText.substring(range.start, elements.first().range.start))
|
||||||
elements.flatMapTo(this) {
|
elements.forEach {
|
||||||
if (shouldExpandToChildren(it)) it.allChildren.toList() else listOf(it)
|
if (shouldExpandToChildren(it))
|
||||||
|
this += it.allChildren.toList()
|
||||||
|
else
|
||||||
|
this += it
|
||||||
}
|
}
|
||||||
add(fileText.substring(elements.last().range.end, range.end))
|
add(fileText.substring(elements.last().range.end, range.end))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.conversion.copy
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class ElementAndTextList() {
|
||||||
|
private val elementsAndTexts = ArrayList<Any>()
|
||||||
|
|
||||||
|
constructor(elements: List<Any>) : this() {
|
||||||
|
elementsAndTexts.addAll(elements.filter { it is PsiElement || it is String })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(a: String) = elementsAndTexts.add(a)
|
||||||
|
|
||||||
|
fun add(a: PsiElement) = elementsAndTexts.add(a)
|
||||||
|
|
||||||
|
operator fun plusAssign(other: String) = plusAssign(other as Any)
|
||||||
|
|
||||||
|
operator fun plusAssign(other: PsiElement) = plusAssign(other as Any)
|
||||||
|
|
||||||
|
private fun plusAssign(a: Any) {
|
||||||
|
elementsAndTexts.add(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plusAssign(other: Collection<PsiElement>): Unit {
|
||||||
|
elementsAndTexts.addAll(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(other: ElementAndTextList): ElementAndTextList {
|
||||||
|
val newList = ElementAndTextList()
|
||||||
|
newList.elementsAndTexts.addAll(this.elementsAndTexts)
|
||||||
|
newList.elementsAndTexts.addAll(other.elementsAndTexts)
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toList(): List<Any> = elementsAndTexts.toList()
|
||||||
|
|
||||||
|
fun process(processor: ElementsAndTextsProcessor) {
|
||||||
|
elementsAndTexts.forEach {
|
||||||
|
when (it) {
|
||||||
|
is PsiElement -> processor.processElement(it)
|
||||||
|
is String -> processor.processText(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ElementsAndTextsProcessor {
|
||||||
|
fun processElement(element: PsiElement)
|
||||||
|
fun processText(string: String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 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.conversion.copy
|
||||||
|
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.search.PsiShortNamesCache
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.referenceExpression
|
||||||
|
import org.jetbrains.kotlin.idea.core.isVisible
|
||||||
|
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
||||||
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
|
||||||
|
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtImportDirective
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class PlainTextPasteImportResolver(dataForConversion: DataForConversion, val targetFile: KtFile) {
|
||||||
|
|
||||||
|
private val file = dataForConversion.file
|
||||||
|
private val project = targetFile.project
|
||||||
|
|
||||||
|
private val importList = file.importList!!
|
||||||
|
private val psiElementFactory = PsiElementFactory.SERVICE.getInstance(project)
|
||||||
|
|
||||||
|
private val bindingContext by lazy { targetFile.analyzeFully() }
|
||||||
|
private val resolutionFacade = targetFile.getResolutionFacade()
|
||||||
|
|
||||||
|
private val shortNameCache = PsiShortNamesCache.getInstance(project)
|
||||||
|
private val scope = file.resolveScope
|
||||||
|
|
||||||
|
val failedToResolveReferenceNames = HashSet<String>()
|
||||||
|
var ambiguityInResolution = false
|
||||||
|
var couldNotResolve = false
|
||||||
|
|
||||||
|
val addedImports = ArrayList<PsiImportStatementBase>()
|
||||||
|
|
||||||
|
private fun canBeImported(descriptor: DeclarationDescriptorWithVisibility?): Boolean {
|
||||||
|
return descriptor != null
|
||||||
|
&& descriptor.canBeReferencedViaImport()
|
||||||
|
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addImport(importStatement: PsiImportStatementBase, shouldAddToTarget: Boolean = false) {
|
||||||
|
importList.add(importStatement)
|
||||||
|
if (shouldAddToTarget)
|
||||||
|
addedImports.add(importStatement)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addImportsFromTargetFile() {
|
||||||
|
|
||||||
|
fun tryConvertKotlinImport(importDirective: KtImportDirective) {
|
||||||
|
val importPath = importDirective.importPath
|
||||||
|
val importedReference = importDirective.importedReference
|
||||||
|
if (importPath != null && !importPath.hasAlias() && importedReference is KtDotQualifiedExpression) {
|
||||||
|
val receiver = importedReference
|
||||||
|
.receiverExpression
|
||||||
|
.referenceExpression()
|
||||||
|
?.mainReference
|
||||||
|
?.resolve()
|
||||||
|
val selector = importedReference
|
||||||
|
.selectorExpression
|
||||||
|
?.referenceExpression()
|
||||||
|
?.mainReference
|
||||||
|
?.resolve()
|
||||||
|
|
||||||
|
val isPackageReceiver = receiver is PsiPackage
|
||||||
|
val isClassReceiver = receiver is PsiClass
|
||||||
|
val isClassSelector = selector is PsiClass
|
||||||
|
|
||||||
|
if (importPath.isAllUnder) {
|
||||||
|
if (isClassReceiver)
|
||||||
|
psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*")
|
||||||
|
else if (isPackageReceiver)
|
||||||
|
psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isPackageReceiver && isClassSelector)
|
||||||
|
psiElementFactory.createImportStatement(selector as PsiClass)
|
||||||
|
else if (isClassReceiver)
|
||||||
|
psiElementFactory.createImportStaticStatement(receiver as PsiClass, importPath.importedName!!.asString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runWriteAction {
|
||||||
|
targetFile.importDirectives.forEach(::tryConvertKotlinImport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tryResolveReferences() {
|
||||||
|
|
||||||
|
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) {
|
||||||
|
it.reference != null
|
||||||
|
&& it.reference is PsiQualifiedReference
|
||||||
|
&& it.reference?.resolve() == null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tryResolveReference(reference: PsiQualifiedReference): Boolean {
|
||||||
|
if (reference.resolve() != null) return true
|
||||||
|
val referenceName = reference.referenceName!!
|
||||||
|
if (referenceName in failedToResolveReferenceNames) return false
|
||||||
|
val classes = shortNameCache.getClassesByName(referenceName, scope)
|
||||||
|
.map { it to it.resolveToDescriptor(resolutionFacade) }
|
||||||
|
.filter { canBeImported(it.second) }
|
||||||
|
|
||||||
|
classes.find { (psiClass, descriptor) -> JavaToKotlinClassMap.INSTANCE.mapPlatformClass(descriptor!!).isNotEmpty() }
|
||||||
|
?.let { (psiClass, descriptor) -> addImport(psiElementFactory.createImportStatement(psiClass)) }
|
||||||
|
if (reference.resolve() != null) return true
|
||||||
|
|
||||||
|
classes.singleOrNull()?.let { (psiClass, descriptor) ->
|
||||||
|
addImport(psiElementFactory.createImportStatement(psiClass), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reference.resolve() != null) return true
|
||||||
|
else {
|
||||||
|
if (classes.isNotEmpty()) {
|
||||||
|
ambiguityInResolution = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val members = (shortNameCache.getMethodsByName(referenceName, scope).asList() +
|
||||||
|
shortNameCache.getFieldsByName(referenceName, scope).asList())
|
||||||
|
.map { it as PsiMember }
|
||||||
|
.map { it to it.getJavaMemberDescriptor(resolutionFacade) as? DeclarationDescriptorWithVisibility }
|
||||||
|
.filter { canBeImported(it.second) }
|
||||||
|
|
||||||
|
members.singleOrNull()?.let { (psiMember, descriptor) ->
|
||||||
|
addImport(psiElementFactory.createImportStaticStatement(psiMember.containingClass!!, psiMember.name!!), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reference.resolve() != null) return false
|
||||||
|
else {
|
||||||
|
if (members.isNotEmpty()) {
|
||||||
|
ambiguityInResolution = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
couldNotResolve = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
runWriteAction {
|
||||||
|
elementsWithUnresolvedRef.forEach {
|
||||||
|
val reference = it.reference as PsiQualifiedReference
|
||||||
|
if (!tryResolveReference(reference)) failedToResolveReferenceNames += reference.referenceName!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
bar(ArrayList<String>())
|
bar(ArrayList<String>())
|
||||||
}
|
}
|
||||||
@@ -1 +1,3 @@
|
|||||||
fun foo() = ArrayList<String>()
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
fun foo() = ArrayList<String>()
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
public class ToBeImportedJava {
|
||||||
|
public static final String TO_BE_IMPORTED_CONST = "!!!";
|
||||||
|
|
||||||
|
public static void staticMethod() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAmbiguousJava {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AmbiguousJava {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAmbiguous {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Ambiguous {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Z {
|
||||||
|
public interface IAmbiguousJava {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AmbiguousJava {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
class ToBeImportedKotlin {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAmbiguous {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ambiguous {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface IAmbiguousKotlin {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AmbiguousKotlin {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Z {
|
||||||
|
interface IAmbiguousKotlin {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AmbiguousKotlin {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import java.util.ArrayList
|
||||||
|
import test.ToBeImportedJava
|
||||||
|
import test.ToBeImportedKotlin
|
||||||
|
import java.util.HashMap
|
||||||
|
import test.ToBeImportedJava.TO_BE_IMPORTED_CONST
|
||||||
|
import test.ToBeImportedJava.staticMethod
|
||||||
|
|
||||||
|
class Target {
|
||||||
|
var listOfPlatformType: List<String> = ArrayList()
|
||||||
|
|
||||||
|
var unresolved: UnresolvedInterface<UnresolvedGeneric> = UnresolvedImplementation() // Should not add import
|
||||||
|
|
||||||
|
var hashMapOfNotImported: Map<ToBeImportedJava, ToBeImportedKotlin> = HashMap()
|
||||||
|
|
||||||
|
fun acceptKotlinClass(tbi: ToBeImportedKotlin) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun acceptJavaClass(tbi: ToBeImportedJava) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var ambiguousKotlin: IAmbiguousKotlin = AmbiguousKotlin() // Should not add import in case of 2 declarations in Kotlin
|
||||||
|
var ambiguous: IAmbiguous = Ambiguous() // Should not add import in case of ambiguous declarations in Kotlin and in Java
|
||||||
|
var ambiguousJava: IAmbiguousJava = AmbiguousJava() // Should not add import in case of 2 declarations in Java
|
||||||
|
|
||||||
|
fun workWithStatics() {
|
||||||
|
val a = TO_BE_IMPORTED_CONST
|
||||||
|
staticMethod()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
class Target {
|
||||||
|
<caret>
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
public List<String> listOfPlatformType = new ArrayList<String>();
|
||||||
|
|
||||||
|
UnresolvedInterface<UnresolvedGeneric> unresolved = new UnresolvedImplementation<>(); // Should not add import
|
||||||
|
|
||||||
|
Map<ToBeImportedJava, ToBeImportedKotlin> hashMapOfNotImported = new HashMap<>();
|
||||||
|
|
||||||
|
void acceptKotlinClass(ToBeImportedKotlin tbi) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void acceptJavaClass(ToBeImportedJava tbi) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
IAmbiguousKotlin ambiguousKotlin = new AmbiguousKotlin(); // Should not add import in case of 2 declarations in Kotlin
|
||||||
|
IAmbiguous ambiguous = new Ambiguous(); // Should not add import in case of ambiguous declarations in Kotlin and in Java
|
||||||
|
IAmbiguousJava ambiguousJava = new AmbiguousJava(); // Should not add import in case of 2 declarations in Java
|
||||||
|
|
||||||
|
void workWithStatics() {
|
||||||
|
String a = TO_BE_IMPORTED_CONST;
|
||||||
|
staticMethod();
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
bar(/*comment*/ArrayList<String>())
|
bar(/*comment*/ArrayList<String>())
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package to
|
package to
|
||||||
|
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
list.add(1)
|
list.add(1)
|
||||||
|
|||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val list = ArrayList<String>()
|
val list = ArrayList<String>()
|
||||||
list.add(1)
|
list.add(1)
|
||||||
|
|||||||
+6
@@ -47,6 +47,12 @@ public class TextJavaToKotlinCopyPasteConversionTestGenerated extends AbstractTe
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ImportResolve.txt")
|
||||||
|
public void testImportResolve() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/ImportResolve.txt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("InsideIdentifier.txt")
|
@TestMetadata("InsideIdentifier.txt")
|
||||||
public void testInsideIdentifier() throws Exception {
|
public void testInsideIdentifier() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/InsideIdentifier.txt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/copyPaste/plainTextConversion/InsideIdentifier.txt");
|
||||||
|
|||||||
Reference in New Issue
Block a user