diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPasteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPasteProcessor.kt index e0d72049fa9..14634ff1774 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPasteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertJavaCopyPasteProcessor.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt index 58b168fa121..ee3a7714ad0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/ConvertTextJavaCopyPasteProcessor.kt @@ -51,6 +51,7 @@ import kotlin.system.measureTimeMillis class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor() { 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 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) } diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/DataForConversion.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/DataForConversion.kt index 4aa3196c0ce..80a16b52b48 100644 --- a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/DataForConversion.kt +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/DataForConversion.kt @@ -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 } diff --git a/idea/src/org/jetbrains/kotlin/idea/conversion/copy/JavaContextDeclarationRenderer.kt b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/JavaContextDeclarationRenderer.kt new file mode 100644 index 00000000000..84b1346f417 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/conversion/copy/JavaContextDeclarationRenderer.kt @@ -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(strict = false) + ?.safeAs() + ?.bodyExpression + ?.blockExpressionsOrSingle() + ?.filterIsInstance() + ?.mapNotNull { it.resolveToDescriptorIfAny() } + .orEmpty() + + fun render(contextElement: KtElement): ContextDeclarations = + ContextDeclarations( + contextElement.localDeclarations.render(), + contextElement.memberDeclarations.render() + ) + + private fun Sequence.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) + } + +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/InClassContextProperty.expected.kt b/nj2k/testData/copyPastePlainText/InClassContextProperty.expected.kt new file mode 100644 index 00000000000..d86ef50ec52 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/InClassContextProperty.expected.kt @@ -0,0 +1,6 @@ +class A { + val str: String = TODO() + internal fun f() { + str.length + } +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/InClassContextProperty.to.kt b/nj2k/testData/copyPastePlainText/InClassContextProperty.to.kt new file mode 100644 index 00000000000..96c131c7a90 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/InClassContextProperty.to.kt @@ -0,0 +1,4 @@ +class A { + val str: String = TODO() + +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/InClassContextProperty.txt b/nj2k/testData/copyPastePlainText/InClassContextProperty.txt new file mode 100644 index 00000000000..239e89048f8 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/InClassContextProperty.txt @@ -0,0 +1,3 @@ +void f() { + str.length(); +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.expected.kt b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.expected.kt new file mode 100644 index 00000000000..523af5823ba --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.expected.kt @@ -0,0 +1,7 @@ +class Test { + val str: Int = 10 + fun test() { + val str: String = "" + val len = str.length + } +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.to.kt b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.to.kt new file mode 100644 index 00000000000..e42362e9219 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.to.kt @@ -0,0 +1,7 @@ +class Test { + val str: Int = 10 + fun test() { + val str: String = "" + + } +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.txt b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.txt new file mode 100644 index 00000000000..36bc15b66ae --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalAndMemberConflict.txt @@ -0,0 +1 @@ +int len = str.length(); \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalContextProperty.expected.kt b/nj2k/testData/copyPastePlainText/LocalContextProperty.expected.kt new file mode 100644 index 00000000000..ae9c7c67391 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalContextProperty.expected.kt @@ -0,0 +1,4 @@ +fun test() { + val str: String = "" + val len = str.length +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalContextProperty.to.kt b/nj2k/testData/copyPastePlainText/LocalContextProperty.to.kt new file mode 100644 index 00000000000..ccee12727ca --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalContextProperty.to.kt @@ -0,0 +1,4 @@ +fun test() { + val str: String = "" + +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/LocalContextProperty.txt b/nj2k/testData/copyPastePlainText/LocalContextProperty.txt new file mode 100644 index 00000000000..36bc15b66ae --- /dev/null +++ b/nj2k/testData/copyPastePlainText/LocalContextProperty.txt @@ -0,0 +1 @@ +int len = str.length(); \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/TopLevelContextProperty.expected.kt b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.expected.kt new file mode 100644 index 00000000000..143b23d89bd --- /dev/null +++ b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.expected.kt @@ -0,0 +1,4 @@ +val str: String = TODO() +internal fun f() { + str.length +} \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/TopLevelContextProperty.to.kt b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.to.kt new file mode 100644 index 00000000000..c9efbca0823 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.to.kt @@ -0,0 +1,2 @@ +val str: String = TODO() + \ No newline at end of file diff --git a/nj2k/testData/copyPastePlainText/TopLevelContextProperty.txt b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.txt new file mode 100644 index 00000000000..239e89048f8 --- /dev/null +++ b/nj2k/testData/copyPastePlainText/TopLevelContextProperty.txt @@ -0,0 +1,3 @@ +void f() { + str.length(); +} \ No newline at end of file diff --git a/nj2k/tests/org/jetbrains/kotlin/nj2k/TextNewJavaToKotlinCopyPasteConversionTestGenerated.java b/nj2k/tests/org/jetbrains/kotlin/nj2k/TextNewJavaToKotlinCopyPasteConversionTestGenerated.java index a053a390c6f..706a0700edd 100644 --- a/nj2k/tests/org/jetbrains/kotlin/nj2k/TextNewJavaToKotlinCopyPasteConversionTestGenerated.java +++ b/nj2k/tests/org/jetbrains/kotlin/nj2k/TextNewJavaToKotlinCopyPasteConversionTestGenerated.java @@ -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");