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.j2k.*
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
|
||||||
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
|
|
||||||
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||||
import java.awt.datatransfer.Transferable
|
import java.awt.datatransfer.Transferable
|
||||||
|
|||||||
+6
-3
@@ -51,6 +51,7 @@ import kotlin.system.measureTimeMillis
|
|||||||
|
|
||||||
class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
|
class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
|
||||||
private val LOG = Logger.getInstance(ConvertTextJavaCopyPasteProcessor::class.java)
|
private val LOG = Logger.getInstance(ConvertTextJavaCopyPasteProcessor::class.java)
|
||||||
|
private val javaContextDeclarationRenderer = JavaContextDeclarationRenderer()
|
||||||
|
|
||||||
private class MyTransferableData(val text: String) : TextBlockTransferableData {
|
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 {
|
private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode {
|
||||||
|
|
||||||
val targetFile = target.containingFile as KtFile
|
val targetFile = target.containingFile as KtFile
|
||||||
|
|
||||||
|
val (localDeclarations, memberDeclarations) = javaContextDeclarationRenderer.render(target)
|
||||||
|
|
||||||
val prefix = buildString {
|
val prefix = buildString {
|
||||||
targetFile.packageDirective?.let {
|
targetFile.packageDirective?.let {
|
||||||
if (it.text.isNotEmpty()) {
|
if (it.text.isNotEmpty()) {
|
||||||
@@ -279,9 +281,10 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
|||||||
return when (context) {
|
return when (context) {
|
||||||
JavaContext.TOP_LEVEL -> createCopiedJavaCode(prefix, "$", text)
|
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)
|
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.start
|
||||||
import org.jetbrains.kotlin.idea.core.util.end
|
import org.jetbrains.kotlin.idea.core.util.end
|
||||||
import org.jetbrains.kotlin.idea.core.util.range
|
import org.jetbrains.kotlin.idea.core.util.range
|
||||||
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data class DataForConversion private constructor(
|
data class DataForConversion private constructor(
|
||||||
val elementsAndTexts: ElementAndTextList /* 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
|
val file: PsiJavaFile
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
||||||
val startOffsets = copiedCode.startOffsets.clone()
|
val startOffsets = copiedCode.startOffsets.clone()
|
||||||
val endOffsets = copiedCode.endOffsets.clone()
|
val endOffsets = copiedCode.endOffsets.clone()
|
||||||
assert(startOffsets.size == endOffsets.size) { "Must have the same size" }
|
assert(startOffsets.size == endOffsets.size) { "Must have the same size" }
|
||||||
@@ -61,6 +65,7 @@ data class DataForConversion private constructor(
|
|||||||
return DataForConversion(elementsAndTexts, importsAndPackage, file)
|
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? {
|
||||||
val ranges = startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }.sortedBy { it.start }
|
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");
|
runTest("nj2k/testData/copyPastePlainText/ImportResolve.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InClassContextProperty.txt")
|
||||||
|
public void testInClassContextProperty() throws Exception {
|
||||||
|
runTest("nj2k/testData/copyPastePlainText/InClassContextProperty.txt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("InsideIdentifier.txt")
|
@TestMetadata("InsideIdentifier.txt")
|
||||||
public void testInsideIdentifier() throws Exception {
|
public void testInsideIdentifier() throws Exception {
|
||||||
runTest("nj2k/testData/copyPastePlainText/InsideIdentifier.txt");
|
runTest("nj2k/testData/copyPastePlainText/InsideIdentifier.txt");
|
||||||
@@ -89,6 +94,16 @@ public class TextNewJavaToKotlinCopyPasteConversionTestGenerated extends Abstrac
|
|||||||
runTest("nj2k/testData/copyPastePlainText/KT32604.txt");
|
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")
|
@TestMetadata("MembersIntoClass.txt")
|
||||||
public void testMembersIntoClass() throws Exception {
|
public void testMembersIntoClass() throws Exception {
|
||||||
runTest("nj2k/testData/copyPastePlainText/MembersIntoClass.txt");
|
runTest("nj2k/testData/copyPastePlainText/MembersIntoClass.txt");
|
||||||
@@ -119,6 +134,11 @@ public class TextNewJavaToKotlinCopyPasteConversionTestGenerated extends Abstrac
|
|||||||
runTest("nj2k/testData/copyPastePlainText/StatementsIntoFunction.txt");
|
runTest("nj2k/testData/copyPastePlainText/StatementsIntoFunction.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("TopLevelContextProperty.txt")
|
||||||
|
public void testTopLevelContextProperty() throws Exception {
|
||||||
|
runTest("nj2k/testData/copyPastePlainText/TopLevelContextProperty.txt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("WholeFile.txt")
|
@TestMetadata("WholeFile.txt")
|
||||||
public void testWholeFile() throws Exception {
|
public void testWholeFile() throws Exception {
|
||||||
runTest("nj2k/testData/copyPastePlainText/WholeFile.txt");
|
runTest("nj2k/testData/copyPastePlainText/WholeFile.txt");
|
||||||
|
|||||||
Reference in New Issue
Block a user