New J2K: consider in-context declarations while performing conversion on copy-paste plain text conversion
#KT-32602 fixed
This commit is contained in:
@@ -47,10 +47,7 @@ import org.jetbrains.kotlin.idea.util.module
|
||||
import org.jetbrains.kotlin.j2k.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
||||
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import java.awt.datatransfer.Transferable
|
||||
|
||||
+6
-3
@@ -51,6 +51,7 @@ import kotlin.system.measureTimeMillis
|
||||
|
||||
class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
|
||||
private val LOG = Logger.getInstance(ConvertTextJavaCopyPasteProcessor::class.java)
|
||||
private val javaContextDeclarationRenderer = JavaContextDeclarationRenderer()
|
||||
|
||||
private class MyTransferableData(val text: String) : TextBlockTransferableData {
|
||||
|
||||
@@ -247,9 +248,10 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
}
|
||||
|
||||
private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode {
|
||||
|
||||
val targetFile = target.containingFile as KtFile
|
||||
|
||||
val (localDeclarations, memberDeclarations) = javaContextDeclarationRenderer.render(target)
|
||||
|
||||
val prefix = buildString {
|
||||
targetFile.packageDirective?.let {
|
||||
if (it.text.isNotEmpty()) {
|
||||
@@ -279,9 +281,10 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
return when (context) {
|
||||
JavaContext.TOP_LEVEL -> createCopiedJavaCode(prefix, "$", text)
|
||||
|
||||
JavaContext.CLASS_BODY -> createCopiedJavaCode(prefix, "$classDef {\n$\n}", text)
|
||||
JavaContext.CLASS_BODY -> createCopiedJavaCode(prefix, "$classDef {\n$memberDeclarations $\n}", text)
|
||||
|
||||
JavaContext.IN_BLOCK -> createCopiedJavaCode(prefix, "$classDef {\nvoid foo() {\n$\n}\n}", text)
|
||||
JavaContext.IN_BLOCK ->
|
||||
createCopiedJavaCode(prefix, "$classDef {\n$memberDeclarations void foo() {\n$localDeclarations $\n}\n}", text)
|
||||
|
||||
JavaContext.EXPRESSION -> createCopiedJavaCode(prefix, "$classDef {\nObject field = $\n}", text)
|
||||
}
|
||||
|
||||
@@ -29,15 +29,19 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import org.jetbrains.kotlin.idea.core.util.start
|
||||
import org.jetbrains.kotlin.idea.core.util.end
|
||||
import org.jetbrains.kotlin.idea.core.util.range
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
||||
data class DataForConversion private constructor(
|
||||
val elementsAndTexts: ElementAndTextList /* list consisting of PsiElement's to convert and plain String's */,
|
||||
val importsAndPackage: String,
|
||||
val file: PsiJavaFile
|
||||
) {
|
||||
companion object {
|
||||
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
||||
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
||||
val startOffsets = copiedCode.startOffsets.clone()
|
||||
val endOffsets = copiedCode.endOffsets.clone()
|
||||
assert(startOffsets.size == endOffsets.size) { "Must have the same size" }
|
||||
@@ -61,6 +65,7 @@ data class DataForConversion private constructor(
|
||||
return DataForConversion(elementsAndTexts, importsAndPackage, file)
|
||||
}
|
||||
|
||||
|
||||
private fun clipTextIfNeeded(file: PsiJavaFile, fileText: String, startOffsets: IntArray, endOffsets: IntArray): String? {
|
||||
val ranges = startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }.sortedBy { it.start }
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.conversion.copy
|
||||
|
||||
import com.intellij.psi.util.parents
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptorWithAccessors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
data class ContextDeclarations(
|
||||
val localDeclarationsJavaStubs: String,
|
||||
val memberDeclarationsJavaStubs: String
|
||||
)
|
||||
|
||||
|
||||
class JavaContextDeclarationRenderer {
|
||||
private val KtElement.memberDeclarations
|
||||
get() = parents()
|
||||
.flatMap { declaration ->
|
||||
when (declaration) {
|
||||
is KtClass -> declaration.resolveToDescriptorIfAny()
|
||||
?.unsubstitutedMemberScope
|
||||
?.getContributedDescriptors()
|
||||
?.asSequence()
|
||||
is KtDeclarationContainer ->
|
||||
declaration.declarations.mapNotNull { it.resolveToDescriptorIfAny() }.asSequence()
|
||||
else -> null
|
||||
} ?: emptySequence()
|
||||
}.filter { member ->
|
||||
member !is DeserializedMemberDescriptor
|
||||
&& !member.name.isSpecial
|
||||
&& member.name.asString() != "dummy"
|
||||
}
|
||||
|
||||
private val KtElement.localDeclarations
|
||||
get() = getParentOfType<KtDeclaration>(strict = false)
|
||||
?.safeAs<KtFunction>()
|
||||
?.bodyExpression
|
||||
?.blockExpressionsOrSingle()
|
||||
?.filterIsInstance<KtDeclaration>()
|
||||
?.mapNotNull { it.resolveToDescriptorIfAny() }
|
||||
.orEmpty()
|
||||
|
||||
fun render(contextElement: KtElement): ContextDeclarations =
|
||||
ContextDeclarations(
|
||||
contextElement.localDeclarations.render(),
|
||||
contextElement.memberDeclarations.render()
|
||||
)
|
||||
|
||||
private fun Sequence<DeclarationDescriptor>.render() =
|
||||
buildString {
|
||||
for (member in this@render) {
|
||||
renderJavaDeclaration(member)
|
||||
appendln()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun StringBuilder.renderJavaDeclaration(declaration: DeclarationDescriptor) {
|
||||
when (declaration) {
|
||||
is VariableDescriptorWithAccessors -> {
|
||||
renderType(declaration.type)
|
||||
append(' ')
|
||||
append(declaration.name.asString())
|
||||
append(" = null;")
|
||||
}
|
||||
is FunctionDescriptor -> {
|
||||
renderType(declaration.returnType)
|
||||
append(' ')
|
||||
append(declaration.name.asString())
|
||||
append('(')
|
||||
for ((i, parameter) in declaration.valueParameters.withIndex()) {
|
||||
renderType(parameter.type)
|
||||
append(' ')
|
||||
append(parameter.name.asString())
|
||||
if (i != declaration.valueParameters.lastIndex) {
|
||||
append(", ")
|
||||
}
|
||||
}
|
||||
append(") {}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun StringBuilder.renderType(type: KotlinType?) {
|
||||
val fqName = type?.constructor?.declarationDescriptor?.fqNameUnsafe
|
||||
|
||||
if (fqName != null) {
|
||||
renderFqName(fqName)
|
||||
} else {
|
||||
append("Object")
|
||||
}
|
||||
if (!type?.arguments.isNullOrEmpty()) {
|
||||
append("<")
|
||||
for (typeArgument in type!!.arguments) {
|
||||
renderType(typeArgument.type)
|
||||
}
|
||||
append(">")
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.renderFqName(fqName: FqNameUnsafe) {
|
||||
val stringFqName = when (fqName) {
|
||||
KotlinBuiltIns.FQ_NAMES.unit -> "void"
|
||||
else -> JavaToKotlinClassMap.mapKotlinToJava(fqName)?.asSingleFqName() ?: fqName.asString()
|
||||
}
|
||||
append(stringFqName)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class A {
|
||||
val str: String = TODO()
|
||||
internal fun f() {
|
||||
str.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
class A {
|
||||
val str: String = TODO()
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
void f() {
|
||||
str.length();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Test {
|
||||
val str: Int = 10
|
||||
fun test() {
|
||||
val str: String = ""
|
||||
val len = str.length
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Test {
|
||||
val str: Int = 10
|
||||
fun test() {
|
||||
val str: String = ""
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
int len = str.length();
|
||||
@@ -0,0 +1,4 @@
|
||||
fun test() {
|
||||
val str: String = ""
|
||||
val len = str.length
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun test() {
|
||||
val str: String = ""
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
int len = str.length();
|
||||
@@ -0,0 +1,4 @@
|
||||
val str: String = TODO()
|
||||
internal fun f() {
|
||||
str.length
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
val str: String = TODO()
|
||||
<caret>
|
||||
@@ -0,0 +1,3 @@
|
||||
void f() {
|
||||
str.length();
|
||||
}
|
||||
Generated
+20
@@ -49,6 +49,11 @@ public class TextNewJavaToKotlinCopyPasteConversionTestGenerated extends Abstrac
|
||||
runTest("nj2k/testData/copyPastePlainText/ImportResolve.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("InClassContextProperty.txt")
|
||||
public void testInClassContextProperty() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/InClassContextProperty.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("InsideIdentifier.txt")
|
||||
public void testInsideIdentifier() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/InsideIdentifier.txt");
|
||||
@@ -89,6 +94,16 @@ public class TextNewJavaToKotlinCopyPasteConversionTestGenerated extends Abstrac
|
||||
runTest("nj2k/testData/copyPastePlainText/KT32604.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("LocalAndMemberConflict.txt")
|
||||
public void testLocalAndMemberConflict() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/LocalAndMemberConflict.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("LocalContextProperty.txt")
|
||||
public void testLocalContextProperty() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/LocalContextProperty.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("MembersIntoClass.txt")
|
||||
public void testMembersIntoClass() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/MembersIntoClass.txt");
|
||||
@@ -119,6 +134,11 @@ public class TextNewJavaToKotlinCopyPasteConversionTestGenerated extends Abstrac
|
||||
runTest("nj2k/testData/copyPastePlainText/StatementsIntoFunction.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("TopLevelContextProperty.txt")
|
||||
public void testTopLevelContextProperty() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/TopLevelContextProperty.txt");
|
||||
}
|
||||
|
||||
@TestMetadata("WholeFile.txt")
|
||||
public void testWholeFile() throws Exception {
|
||||
runTest("nj2k/testData/copyPastePlainText/WholeFile.txt");
|
||||
|
||||
Reference in New Issue
Block a user