New J2K: consider in-context declarations while performing conversion on copy-paste plain text conversion

#KT-32602 fixed
This commit is contained in:
Ilya Kirillov
2019-07-16 18:16:26 +03:00
parent c865d74965
commit 96ca4712a0
17 changed files with 204 additions and 8 deletions
@@ -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
@@ -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();
}
@@ -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");