KT-5959 Imports should be inserted on paste from java with conversion to Kotlin
#KT-5959 Fixed
This commit is contained in:
@@ -54,6 +54,7 @@ public class JavaToKotlinAction : AnAction() {
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
|
||||
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
|
||||
convertedTexts = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
|
||||
.map { it!!.text /*conversion of a file always succeeds*/ }
|
||||
}
|
||||
|
||||
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
|
||||
+34
-23
@@ -85,11 +85,7 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
if (file !is JetFile || DumbService.getInstance(file.getProject()).isDumb()) return listOf()
|
||||
|
||||
val collectedData = try {
|
||||
toTextRanges(startOffsets, endOffsets).flatMap {
|
||||
CollectHighlightsUtil.getElementsInRange(file, it.start, it.end).flatMap { element ->
|
||||
collectReferenceDataFromElement(element, file, it.start, startOffsets, endOffsets)
|
||||
}
|
||||
}
|
||||
collectReferenceData(file, startOffsets, endOffsets)
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
// supposedly analysis can only be canceled from another thread
|
||||
@@ -107,23 +103,36 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
return listOf(KotlinReferenceTransferableData(collectedData.copyToArray()))
|
||||
}
|
||||
|
||||
private fun collectReferenceDataFromElement(
|
||||
public fun collectReferenceData(
|
||||
file: JetFile,
|
||||
startOffsets: IntArray,
|
||||
endOffsets: IntArray
|
||||
): List<KotlinReferenceData> {
|
||||
val result = ArrayList<KotlinReferenceData>()
|
||||
for (range in toTextRanges(startOffsets, endOffsets)) {
|
||||
for (element in CollectHighlightsUtil.getElementsInRange(file, range.start, range.end)) {
|
||||
result.addReferenceDataFromElement(element, file, range.start, startOffsets, endOffsets)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun MutableCollection<KotlinReferenceData>.addReferenceDataFromElement(
|
||||
element: PsiElement,
|
||||
file: JetFile,
|
||||
startOffset: Int,
|
||||
startOffsets: IntArray,
|
||||
endOffsets: IntArray
|
||||
): Collection<KotlinReferenceData> {
|
||||
) {
|
||||
|
||||
if (PsiTreeUtil.getParentOfType(element, *IGNORE_REFERENCES_INSIDE) != null) return listOf()
|
||||
if (PsiTreeUtil.getParentOfType(element, *IGNORE_REFERENCES_INSIDE) != null) return
|
||||
|
||||
val reference = element.getReference() as? JetReference ?: return listOf()
|
||||
val reference = element.getReference() as? JetReference ?: return
|
||||
|
||||
val descriptors = reference.resolveToDescriptors((element as JetElement).analyze()) //TODO: we could use partial body resolve for all references together
|
||||
//check whether this reference is unambiguous
|
||||
if (reference !is JetMultiReference<*> && descriptors.size() > 1) return listOf()
|
||||
if (reference !is JetMultiReference<*> && descriptors.size() > 1) return
|
||||
|
||||
val collectedData = ArrayList<KotlinReferenceData>()
|
||||
for (descriptor in descriptors) {
|
||||
val declarations = DescriptorToSourceUtilsIde.getAllDeclarations(file.getProject(), descriptor)
|
||||
val declaration = declarations.singleOrNull()
|
||||
@@ -139,9 +148,8 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
if (!descriptor.canBeReferencedViaImport()) continue
|
||||
|
||||
val kind = referenceDataKind(descriptor) ?: continue
|
||||
collectedData.add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind))
|
||||
add(KotlinReferenceData(element.range.start - startOffset, element.range.end - startOffset, fqName.asString(), kind))
|
||||
}
|
||||
return collectedData
|
||||
}
|
||||
|
||||
private fun referenceDataKind(descriptor: DeclarationDescriptor): KotlinReferenceData.Kind? {
|
||||
@@ -179,12 +187,16 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
|
||||
if (file !is JetFile) return
|
||||
|
||||
assert(values.size() == 1)
|
||||
val referenceData = values.single().data
|
||||
|
||||
processReferenceData(project, file, bounds.getStartOffset(), referenceData)
|
||||
}
|
||||
|
||||
public fun processReferenceData(project: Project, file: JetFile, boundStart: Int, referenceData: Array<KotlinReferenceData>) {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
assert(values.size() == 1)
|
||||
|
||||
val referenceData = values.single().data
|
||||
val referencesPossibleToRestore = findReferencesToRestore(file, bounds, referenceData)
|
||||
val referencesPossibleToRestore = findReferencesToRestore(file, boundStart, referenceData)
|
||||
|
||||
val selectedReferencesToRestore = showRestoreReferencesDialog(project, referencesPossibleToRestore)
|
||||
if (selectedReferencesToRestore.isEmpty()) return
|
||||
@@ -192,14 +204,13 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
ApplicationManager.getApplication()!!.runWriteAction(Runnable {
|
||||
restoreReferences(selectedReferencesToRestore, file)
|
||||
})
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
}
|
||||
|
||||
private fun findReferencesToRestore(file: PsiFile, bounds: RangeMarker, referenceData: Array<out KotlinReferenceData>): List<ReferenceToRestoreData> {
|
||||
private fun findReferencesToRestore(file: PsiFile, boundStart: Int, referenceData: Array<out KotlinReferenceData>): List<ReferenceToRestoreData> {
|
||||
if (file !is JetFile) return listOf()
|
||||
|
||||
return referenceData.map {
|
||||
val referenceElement = findReference(it, file, bounds)
|
||||
val referenceElement = findReference(it, file, boundStart)
|
||||
if (referenceElement != null)
|
||||
createReferenceToRestoreData(referenceElement, it)
|
||||
else
|
||||
@@ -207,9 +218,9 @@ public class KotlinCopyPasteReferenceProcessor() : CopyPastePostProcessor<Kotlin
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
private fun findReference(data: KotlinReferenceData, file: JetFile, bounds: RangeMarker): JetElement? {
|
||||
val startOffset = data.startOffset + bounds.getStartOffset()
|
||||
val endOffset = data.endOffset + bounds.getStartOffset()
|
||||
private fun findReference(data: KotlinReferenceData, file: JetFile, boundStart: Int): JetElement? {
|
||||
val startOffset = data.startOffset + boundStart
|
||||
val endOffset = data.endOffset + boundStart
|
||||
val element = file.findElementAt(startOffset)
|
||||
val desiredRange = TextRange(startOffset, endOffset)
|
||||
var expression = element
|
||||
|
||||
+131
-23
@@ -28,17 +28,18 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import org.jetbrains.kotlin.idea.codeInsight.KotlinCopyPasteReferenceProcessor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.KotlinReferenceData
|
||||
import org.jetbrains.kotlin.idea.editor.JetEditorOptions
|
||||
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
|
||||
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
|
||||
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||
import org.jetbrains.kotlin.j2k.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import java.awt.datatransfer.Transferable
|
||||
import java.util.ArrayList
|
||||
|
||||
@@ -69,43 +70,80 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
val data = values.single()
|
||||
if (data !is CopiedCode) return
|
||||
|
||||
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()) as? JetFile ?: return
|
||||
val document = editor.getDocument()
|
||||
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(document) as? JetFile ?: return
|
||||
|
||||
fun doConversion(): String? {
|
||||
fun doConversion(): Pair<String?, Collection<KotlinReferenceData>> {
|
||||
//TODO: create it in correct context!
|
||||
val sourceFile = PsiFileFactory.getInstance(project).
|
||||
createFileFromText(data.fileName, JavaLanguage.INSTANCE, data.fileText) as PsiJavaFile
|
||||
return convertCopiedCodeToKotlin(data, sourceFile)
|
||||
val result = convertCopiedCodeToKotlin(data, sourceFile)
|
||||
val referenceData = buildReferenceData(result.text, result.parseContext, sourceFile, targetFile)
|
||||
return (if (result.textChanged) result.text else null) to referenceData
|
||||
}
|
||||
|
||||
var conversionResult: String? = null
|
||||
fun insertImports(bounds: TextRange, referenceData: Collection<KotlinReferenceData>): TextRange? {
|
||||
if (referenceData.isEmpty()) return bounds
|
||||
|
||||
val rangeMarker = document.createRangeMarker(bounds)
|
||||
rangeMarker.setGreedyToLeft(true)
|
||||
rangeMarker.setGreedyToRight(true)
|
||||
|
||||
KotlinCopyPasteReferenceProcessor().processReferenceData(project, targetFile, bounds.start, referenceData.copyToArray())
|
||||
|
||||
return rangeMarker.range
|
||||
}
|
||||
|
||||
var conversionResult: Pair<String?, Collection<KotlinReferenceData>>? = null
|
||||
|
||||
fun doConversionAndInsertImportsIfNoConversion(): Boolean {
|
||||
conversionResult = doConversion()
|
||||
|
||||
val text = conversionResult!!.first
|
||||
if (text != null) return false
|
||||
|
||||
insertImports(bounds.range ?: return true, conversionResult!!.second)
|
||||
return true
|
||||
}
|
||||
|
||||
val textLength = data.startOffsets.indices.sumBy { data.endOffsets[it] - data.startOffsets[it] }
|
||||
if (textLength < 1000) { // if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted
|
||||
conversionResult = doConversion() ?: return
|
||||
if (doConversionAndInsertImportsIfNoConversion()) return
|
||||
}
|
||||
|
||||
val jetEditorOptions = JetEditorOptions.getInstance()!!
|
||||
val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project))
|
||||
if (needConvert) {
|
||||
if (conversionResult == null) {
|
||||
conversionResult = doConversion() ?: return
|
||||
if (doConversionAndInsertImportsIfNoConversion()) return
|
||||
}
|
||||
val (text, referenceData) = conversionResult!!
|
||||
text!! // otherwise we should get true from doConversionAndInsertImportsIfNoConversion and return above
|
||||
|
||||
ApplicationManager.getApplication()!!.runWriteAction {
|
||||
val startOffset = bounds.getStartOffset()
|
||||
editor.getDocument().replaceString(startOffset, bounds.getEndOffset(), conversionResult!!)
|
||||
document.replaceString(startOffset, bounds.getEndOffset(), text)
|
||||
|
||||
val endOffsetAfterCopy = startOffset + conversionResult!!.length()
|
||||
val endOffsetAfterCopy = startOffset + text.length()
|
||||
editor.getCaretModel().moveToOffset(endOffsetAfterCopy)
|
||||
|
||||
var newBounds = insertImports(TextRange(startOffset, endOffsetAfterCopy), referenceData)
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, TextRange(startOffset, endOffsetAfterCopy))
|
||||
AfterConversionPass(project, J2kPostProcessor(formatCode = true)).run(targetFile, newBounds)
|
||||
|
||||
conversionPerformed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile): String? {
|
||||
private data class ConversionResult(
|
||||
val text: String,
|
||||
val parseContext: ParseContext,
|
||||
val textChanged: Boolean
|
||||
)
|
||||
|
||||
private fun convertCopiedCodeToKotlin(code: CopiedCode, sourceFile: PsiJavaFile): ConversionResult {
|
||||
assert(code.startOffsets.size() == code.endOffsets.size(), "Must have the same size")
|
||||
val sourceFileText = code.fileText
|
||||
|
||||
@@ -114,8 +152,6 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
list.collectElementsToConvert(sourceFile, sourceFileText, TextRange(code.startOffsets[i], code.endOffsets[i]))
|
||||
}
|
||||
|
||||
if (list.all { it is String }) return null // nothing to convert
|
||||
|
||||
val converter = JavaToKotlinConverter(
|
||||
sourceFile.getProject(),
|
||||
ConverterSettings.defaultSettings,
|
||||
@@ -130,12 +166,16 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
var resultIndex = 0
|
||||
val convertedCodeBuilder = StringBuilder()
|
||||
val originalCodeBuilder = StringBuilder()
|
||||
var parseContext: ParseContext? = null
|
||||
for (o in list) {
|
||||
if (o is PsiElement) {
|
||||
val originalText = o.getText()
|
||||
val result = results[resultIndex++]
|
||||
if (!result.isEmpty()) {
|
||||
convertedCodeBuilder.append(result)
|
||||
if (result != null) {
|
||||
convertedCodeBuilder.append(result.text)
|
||||
if (parseContext == null) { // use parse context of the first converted element as parse context for the whole text
|
||||
parseContext = result.parseContext
|
||||
}
|
||||
}
|
||||
else { // failed to convert element to Kotlin, insert "as is"
|
||||
convertedCodeBuilder.append(originalText)
|
||||
@@ -150,9 +190,7 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
|
||||
val convertedCode = convertedCodeBuilder.toString()
|
||||
val originalCode = originalCodeBuilder.toString()
|
||||
if (convertedCode == originalCode) return null
|
||||
|
||||
return convertedCode
|
||||
return ConversionResult(convertedCode, parseContext ?: ParseContext.TOP_LEVEL, convertedCode != originalCode)
|
||||
}
|
||||
|
||||
// builds list consisting of PsiElement's to convert and plain String's
|
||||
@@ -172,6 +210,76 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildReferenceData(text: String, parseContext: ParseContext, sourceFile: PsiJavaFile, targetFile: JetFile): Collection<KotlinReferenceData> {
|
||||
var blockStart: Int? = null
|
||||
var blockEnd: Int? = null
|
||||
val fileText = StringBuilder {
|
||||
val packageName = sourceFile.getPackageName()
|
||||
if (!packageName.isEmpty()) {
|
||||
append("package ").append(packageName).append("\n")
|
||||
}
|
||||
|
||||
val importList = sourceFile.getImportList()
|
||||
if (importList != null) {
|
||||
for (import in importList.getImportStatements()) {
|
||||
val qualifiedName = import.getQualifiedName() ?: continue
|
||||
if (import.isOnDemand()) {
|
||||
append("import ").append(qualifiedName).append(".*\n")
|
||||
}
|
||||
else {
|
||||
val fqName = FqNameUnsafe(qualifiedName)
|
||||
// skip explicit imports of platform classes mapped into Kotlin classes
|
||||
if (fqName.isSafe() && JavaToKotlinClassMap.INSTANCE.mapPlatformClass(fqName.toSafe()).isNotEmpty()) continue
|
||||
append("import ").append(qualifiedName).append("\n")
|
||||
}
|
||||
}
|
||||
//TODO: static imports
|
||||
}
|
||||
|
||||
|
||||
val contextPrefix: String
|
||||
val contextSuffix: String
|
||||
when (parseContext) {
|
||||
ParseContext.CODE_BLOCK -> {
|
||||
contextPrefix = "fun ${generateDummyFunctionName(text)}() {\n"
|
||||
contextSuffix = "\n}"
|
||||
}
|
||||
|
||||
ParseContext.TOP_LEVEL -> {
|
||||
contextPrefix = ""
|
||||
contextSuffix = ""
|
||||
}
|
||||
|
||||
//TODO: see KT-7264
|
||||
else -> {
|
||||
contextPrefix = ""
|
||||
contextSuffix = ""
|
||||
}
|
||||
}
|
||||
|
||||
append(contextPrefix)
|
||||
|
||||
blockStart = length()
|
||||
append(text)
|
||||
blockEnd = length()
|
||||
|
||||
append(contextSuffix)
|
||||
}.toString()
|
||||
|
||||
val dummyFile = JetPsiFactory(targetFile.getProject()).createAnalyzableFile("dummy.kt", fileText, targetFile)
|
||||
|
||||
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArray(blockStart!!), intArray(blockEnd!!))
|
||||
}
|
||||
|
||||
private fun generateDummyFunctionName(convertedCode: String): String {
|
||||
var i = 0
|
||||
while (true) {
|
||||
val name = "dummy$i"
|
||||
if (convertedCode.indexOf(name) < 0) return name
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
private fun okFromDialog(project: Project): Boolean {
|
||||
val dialog = KotlinPasteFromJavaDialog(project)
|
||||
dialog.show()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.conversion.copy
|
||||
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
|
||||
@@ -29,3 +30,6 @@ public val TextRange.end: Int
|
||||
|
||||
public val PsiElement.range: TextRange
|
||||
get() = getTextRange()!!
|
||||
|
||||
public val RangeMarker.range: TextRange?
|
||||
get() = if (isValid()) TextRange(getStartOffset(), getEndOffset()) else null
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.io.File
|
||||
|
||||
fun foo(file: File): List<String> {
|
||||
return emptyList()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class C {
|
||||
<selection> List<String> foo(File file) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
</selection>}
|
||||
@@ -0,0 +1 @@
|
||||
<caret>
|
||||
@@ -0,0 +1,3 @@
|
||||
import java.io.File
|
||||
|
||||
fun foo(): List<File><caret>
|
||||
@@ -0,0 +1,10 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class C {
|
||||
<selection>List<File></selection> foo() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
fun foo(): <caret>
|
||||
@@ -0,0 +1,5 @@
|
||||
import java.io.File
|
||||
|
||||
fun foo() {
|
||||
list.add(File.separatorChar)<caret>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
class C {
|
||||
void foo(List<Character> list) {
|
||||
<selection>list.add(File.separatorChar)</selection>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ppp
|
||||
|
||||
import kotlin.platform.platformStatic
|
||||
|
||||
public object Dependency {
|
||||
[platformStatic]
|
||||
public val FIELD: Int = 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import ppp.Dependency
|
||||
|
||||
fun f() {
|
||||
return Dependency.FIELD<caret>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ppp
|
||||
|
||||
class C {
|
||||
char foo() {
|
||||
<selection>return Dependency.FIELD;</selection>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun f() {
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package ppp
|
||||
|
||||
class dummy0
|
||||
@@ -0,0 +1,5 @@
|
||||
import ppp.dummy0
|
||||
|
||||
fun f() {
|
||||
return dummy0()<caret>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import ppp.*
|
||||
|
||||
class C {
|
||||
dummy0 foo() {
|
||||
<selection>return dummy0();</selection>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun f() {
|
||||
<caret>
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
No conversion performed
|
||||
val v = b - c + d
|
||||
@@ -1,3 +1,4 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
public static void main(String[]args){
|
||||
a + <selection>b - c + d</selection> - e
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
No conversion performed
|
||||
fun main(args: Array<String>) {
|
||||
reference(arg)
|
||||
somethingElse()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
class A {
|
||||
public static void main(String[] args) {
|
||||
qualifier.<selection>reference(arg)</selection>;
|
||||
|
||||
@@ -1 +1 @@
|
||||
No conversion performed
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
class A {
|
||||
public static void main(String[] args) {
|
||||
//sdasd
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
No conversion performed
|
||||
fun main(args: Array<String>) {
|
||||
qualifier
|
||||
somethingElse()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
class A {
|
||||
public static void main(String[] args) {
|
||||
<selection>qualifier</selection>.reference(arg);
|
||||
|
||||
@@ -1 +1 @@
|
||||
No conversion performed
|
||||
class Some
|
||||
@@ -1,3 +1,4 @@
|
||||
// NO_CONVERSION_EXPECTED
|
||||
<selection>class Some</selection>InterestingClass {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
|
||||
public abstract class AbstractCopyPasteTest : JetLightCodeInsightFixtureTestCase() {
|
||||
private var savedImportsOnPasteSetting: Int = 0
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
savedImportsOnPasteSetting = CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE
|
||||
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = CodeInsightSettings.YES
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = savedImportsOnPasteSetting
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
+2
-18
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightSettings;
|
||||
import com.intellij.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
@@ -26,7 +25,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
|
||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
|
||||
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest;
|
||||
import org.jetbrains.kotlin.idea.PluginTestCaseBase;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
|
||||
import org.jetbrains.kotlin.psi.JetFile;
|
||||
@@ -38,26 +37,11 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractInsertImportOnPasteTest extends JetLightCodeInsightFixtureTestCase {
|
||||
public abstract class AbstractInsertImportOnPasteTest extends AbstractCopyPasteTest {
|
||||
private static final String BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/imports";
|
||||
private static final String DEFAULT_TO_FILE_TEXT = "package to\n\n<caret>";
|
||||
private static final String ALLOW_UNRESOLVED_DIRECTIVE = "// ALLOW_UNRESOLVED";
|
||||
|
||||
private int savedState;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
savedState = CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE;
|
||||
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = CodeInsightSettings.YES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
CodeInsightSettings.getInstance().ADD_IMPORTS_ON_PASTE = savedState;
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void doTestCut(String path) throws Exception {
|
||||
doTestAction(IdeActions.ACTION_CUT, path);
|
||||
}
|
||||
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor;
|
||||
import org.jetbrains.kotlin.idea.PluginTestCaseBase;
|
||||
import org.jetbrains.kotlin.idea.editor.JetEditorOptions;
|
||||
|
||||
public abstract class AbstractJavaToKotlinCopyPasteConversionTest extends JetLightCodeInsightFixtureTestCase {
|
||||
|
||||
private static final String BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/conversion";
|
||||
private JetEditorOptions oldState = null;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
oldState = JetEditorOptions.getInstance().getState();
|
||||
JetEditorOptions.getInstance().setEnableJavaToKotlinConversion(true);
|
||||
JetEditorOptions.getInstance().setDonTShowConversionDialog(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
JetEditorOptions.getInstance().loadState(oldState);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void doTest(@SuppressWarnings("UnusedParameters") String path) throws Exception {
|
||||
myFixture.setTestDataPath(BASE_PATH);
|
||||
String testName = getTestName(false);
|
||||
myFixture.configureByFile(testName + ".java");
|
||||
myFixture.performEditorAction(IdeActions.ACTION_COPY);
|
||||
|
||||
//noinspection AssignmentToStaticFieldFromInstanceMethod
|
||||
ConvertJavaCopyPastePostProcessor.conversionPerformed = false;
|
||||
|
||||
myFixture.configureByFile(testName + ".to.kt");
|
||||
myFixture.performEditorAction(IdeActions.ACTION_PASTE);
|
||||
|
||||
if (!ConvertJavaCopyPastePostProcessor.conversionPerformed) {
|
||||
CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Document document = myFixture.getEditor().getDocument();
|
||||
document.replaceString(0, document.getTextLength(), "No conversion performed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "", null);
|
||||
}
|
||||
|
||||
myFixture.checkResultByFile(testName + ".expected.kt");
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.openapi.actionSystem.IdeActions
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.AbstractCopyPasteTest
|
||||
import org.jetbrains.kotlin.idea.JetWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.editor.JetEditorOptions
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
public abstract class AbstractJavaToKotlinCopyPasteConversionTest : AbstractCopyPasteTest() {
|
||||
private val BASE_PATH = PluginTestCaseBase.getTestDataPathBase() + "/copyPaste/conversion"
|
||||
|
||||
private var oldEditorOptions: JetEditorOptions? = null
|
||||
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
return JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
oldEditorOptions = JetEditorOptions.getInstance().getState()
|
||||
JetEditorOptions.getInstance().setEnableJavaToKotlinConversion(true)
|
||||
JetEditorOptions.getInstance().setDonTShowConversionDialog(true)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
JetEditorOptions.getInstance().loadState(oldEditorOptions)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
public fun doTest(path: String) {
|
||||
myFixture.setTestDataPath(BASE_PATH)
|
||||
val testName = getTestName(false)
|
||||
myFixture.configureByFiles(testName + ".java")
|
||||
|
||||
val fileText = myFixture.getEditor().getDocument().getText()
|
||||
val noConversionExpected = InTextDirectivesUtils.findListWithPrefixes(fileText, "// NO_CONVERSION_EXPECTED").isNotEmpty()
|
||||
|
||||
myFixture.performEditorAction(IdeActions.ACTION_COPY)
|
||||
|
||||
val toFileName = testName + ".to.kt"
|
||||
val dependencyFileName = testName + ".dependency.kt"
|
||||
|
||||
if (File(BASE_PATH + File.separator + dependencyFileName).exists()) {
|
||||
myFixture.configureByFiles(toFileName, dependencyFileName)
|
||||
}
|
||||
else {
|
||||
myFixture.configureByFile(toFileName)
|
||||
}
|
||||
|
||||
ConvertJavaCopyPastePostProcessor.conversionPerformed = false
|
||||
|
||||
myFixture.performEditorAction(IdeActions.ACTION_PASTE)
|
||||
|
||||
assertEquals(noConversionExpected, !ConvertJavaCopyPastePostProcessor.conversionPerformed,
|
||||
if (noConversionExpected) "Conversion to Kotlin should not be suggested" else "No conversion to Kotlin suggested")
|
||||
|
||||
myFixture.checkResultByFile(testName + ".expected.kt")
|
||||
}
|
||||
}
|
||||
+30
@@ -32,6 +32,36 @@ import java.util.regex.Pattern;
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class JavaToKotlinCopyPasteConversionTestGenerated extends AbstractJavaToKotlinCopyPasteConversionTest {
|
||||
@TestMetadata("AddImports.java")
|
||||
public void testAddImports() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImports.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AddImportsButNoConversion.java")
|
||||
public void testAddImportsButNoConversion() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsButNoConversion.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AddImportsButNoConversion2.java")
|
||||
public void testAddImportsButNoConversion2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsButNoConversion2.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AddImportsClassInSamePackage.java")
|
||||
public void testAddImportsClassInSamePackage() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsClassInSamePackage.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("AddImportsDummyConflict.java")
|
||||
public void testAddImportsDummyConflict() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/copyPaste/conversion/AddImportsDummyConflict.java");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInConversion() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/copyPaste/conversion"), Pattern.compile("^(.+)\\.java$"), true);
|
||||
}
|
||||
|
||||
@@ -72,15 +72,26 @@ class Converter private(
|
||||
|
||||
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
|
||||
|
||||
public fun convert(): ((Map<PsiElement, UsageProcessing>) -> String)? {
|
||||
val element = convertTopElement(elementToConvert) ?: return null
|
||||
return { usageProcessings ->
|
||||
unfoldDeferredElements(usageProcessings)
|
||||
public data class IntermediateResult(
|
||||
val codeGenerator: (Map<PsiElement, UsageProcessing>) -> String,
|
||||
val parseContext: ParseContext
|
||||
)
|
||||
|
||||
val builder = CodeBuilder(elementToConvert)
|
||||
builder.append(element)
|
||||
builder.result
|
||||
public fun convert(): IntermediateResult? {
|
||||
val element = convertTopElement(elementToConvert) ?: return null
|
||||
val parseContext = when (elementToConvert) {
|
||||
is PsiStatement, is PsiExpression -> ParseContext.CODE_BLOCK
|
||||
else -> ParseContext.TOP_LEVEL
|
||||
}
|
||||
return IntermediateResult(
|
||||
{ usageProcessings ->
|
||||
unfoldDeferredElements(usageProcessings)
|
||||
|
||||
val builder = CodeBuilder(elementToConvert)
|
||||
builder.append(element)
|
||||
builder.result
|
||||
},
|
||||
parseContext)
|
||||
}
|
||||
|
||||
private fun convertTopElement(element: PsiElement): Element? = when (element) {
|
||||
|
||||
@@ -63,6 +63,11 @@ public trait PostProcessor {
|
||||
public fun doAdditionalProcessing(file: JetFile, rangeMarker: RangeMarker?)
|
||||
}
|
||||
|
||||
public enum class ParseContext {
|
||||
TOP_LEVEL
|
||||
CODE_BLOCK
|
||||
}
|
||||
|
||||
public class JavaToKotlinConverter(private val project: Project,
|
||||
private val settings: ConverterSettings,
|
||||
private val referenceSearcher: ReferenceSearcher,
|
||||
@@ -75,13 +80,15 @@ public class JavaToKotlinConverter(private val project: Project,
|
||||
val postProcessingContext: PsiElement?
|
||||
)
|
||||
|
||||
public data class Result(val text: String, val parseContext: ParseContext)
|
||||
|
||||
public fun elementsToKotlin(
|
||||
inputElements: List<InputElement>,
|
||||
progress: ProgressIndicator = EmptyProgressIndicator()
|
||||
): List<String> {
|
||||
): List<Result?> {
|
||||
try {
|
||||
val elementCount = inputElements.size()
|
||||
val intermediateResults = ArrayList<((Map<PsiElement, UsageProcessing>) -> String)?>(elementCount)
|
||||
val intermediateResults = ArrayList<(Converter.IntermediateResult)?>(elementCount)
|
||||
val usageProcessings = HashMap<PsiElement, UsageProcessing>()
|
||||
val usageProcessingCollector: (UsageProcessing) -> Unit = { usageProcessing ->
|
||||
assert(!usageProcessings.containsKey(usageProcessing.targetElement))
|
||||
@@ -130,21 +137,27 @@ public class JavaToKotlinConverter(private val project: Project,
|
||||
intermediateResults.add(result)
|
||||
}
|
||||
|
||||
val results = ArrayList<String>(elementCount)
|
||||
val results = ArrayList<Result?>(elementCount)
|
||||
processFilesWithProgress(0.25) { i ->
|
||||
val result = intermediateResults[i]
|
||||
results.add(if (result != null) result(usageProcessings) else "")
|
||||
results.add(if (result != null)
|
||||
Result(result.codeGenerator(usageProcessings), result.parseContext)
|
||||
else
|
||||
null)
|
||||
intermediateResults[i] = null // to not hold unused objects in the heap
|
||||
}
|
||||
|
||||
val finalResults = ArrayList<String>(elementCount)
|
||||
if (postProcessor == null) return results
|
||||
|
||||
val finalResults = ArrayList<Result?>(elementCount)
|
||||
processFilesWithProgress(0.5) { i ->
|
||||
val result = results[i]
|
||||
if (postProcessor != null) {
|
||||
if (result != null) {
|
||||
try {
|
||||
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result, inputElements[i].postProcessingContext!!)
|
||||
//TODO: post processing does not work correctly for ParseContext different from TOP_LEVEL
|
||||
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result.text, inputElements[i].postProcessingContext!!)
|
||||
AfterConversionPass(project, postProcessor).run(kotlinFile, null)
|
||||
finalResults.add(kotlinFile.getText())
|
||||
finalResults.add(Result(kotlinFile.getText(), result.parseContext))
|
||||
}
|
||||
catch(e: ProcessCanceledException) {
|
||||
throw e
|
||||
@@ -155,7 +168,7 @@ public class JavaToKotlinConverter(private val project: Project,
|
||||
}
|
||||
}
|
||||
else {
|
||||
finalResults.add(result)
|
||||
finalResults.add(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ public object JavaToKotlinTranslator {
|
||||
val file = createFile(javaCode)
|
||||
if (file is PsiJavaFile) {
|
||||
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null)
|
||||
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single())
|
||||
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).single()!!.text)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
|
||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
|
||||
val inputElements = psiFiles.map { JavaToKotlinConverter.InputElement(it, it) }
|
||||
val results: List<String> = converter.elementsToKotlin(inputElements)
|
||||
val results: List<String> = converter.elementsToKotlin(inputElements).map { it!!.text }
|
||||
|
||||
fun expectedFile(i: Int) = File(javaFiles[i].getPath().replace(".java", ".kt"))
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
|
||||
val converter = JavaToKotlinConverter(project, settings,
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
|
||||
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file))
|
||||
return converter.elementsToKotlin(inputElements).single()
|
||||
return converter.elementsToKotlin(inputElements).single()!!.text
|
||||
}
|
||||
|
||||
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
|
||||
|
||||
Reference in New Issue
Block a user