J2K, minor: clean copy-paste conversion code

This commit is contained in:
Ilya Kirillov
2019-08-02 20:30:34 +03:00
parent f1482177b4
commit 2dabb91a1f
7 changed files with 202 additions and 164 deletions
@@ -54,6 +54,7 @@ import java.util.*
import kotlin.system.measureTimeMillis
class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
@Suppress("PrivatePropertyName")
private val LOG = Logger.getInstance(ConvertJavaCopyPasteProcessor::class.java)
override fun extractTransferableData(content: Transferable): List<TextBlockTransferableData> {
@@ -61,20 +62,30 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
if (content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)) {
return listOf(content.getTransferData(CopiedJavaCode.DATA_FLAVOR) as TextBlockTransferableData)
}
}
catch (e: Throwable) {
} catch (e: Throwable) {
LOG.error(e)
}
return listOf()
}
override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): List<TextBlockTransferableData> {
override fun collectTransferableData(
file: PsiFile,
editor: Editor,
startOffsets: IntArray,
endOffsets: IntArray
): List<TextBlockTransferableData> {
if (file !is PsiJavaFile) return listOf()
return listOf(CopiedJavaCode(file.getText()!!, startOffsets, endOffsets))
}
override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, values: List<TextBlockTransferableData>) {
override fun processTransferableData(
project: Project,
editor: Editor,
bounds: RangeMarker,
caretOffset: Int,
indented: Ref<Boolean>,
values: List<TextBlockTransferableData>
) {
if (DumbService.getInstance(project).isDumb) return
if (!KotlinEditorOptions.getInstance().isEnableJavaToKotlinConversion) return
@@ -104,7 +115,11 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
return Result(text, referenceData, result.importsToAdd, result.converterContext)
}
fun insertImports(bounds: TextRange, referenceData: Collection<KotlinReferenceData>, explicitImports: Collection<FqName>): TextRange? {
fun insertImports(
bounds: TextRange,
referenceData: Collection<KotlinReferenceData>,
explicitImports: Collection<FqName>
): TextRange? {
if (referenceData.isEmpty() && explicitImports.isEmpty()) return bounds
PsiDocumentManager.getInstance(project).commitAllDocuments()
@@ -133,31 +148,31 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
if (conversionResult!!.text != null) return false
insertImports(bounds.range ?: return true, conversionResult!!.referenceData, conversionResult!!.explicitImports)
insertImports(
bounds.range ?: return true,
conversionResult!!.referenceData,
conversionResult!!.explicitImports
)
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
if (doConversionAndInsertImportsIfUnchanged()) return
}
// if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted
if (textLength < 1000 && doConversionAndInsertImportsIfUnchanged()) return
fun convert() {
if (conversionResult == null) {
if (doConversionAndInsertImportsIfUnchanged()) return
}
if (conversionResult == null && doConversionAndInsertImportsIfUnchanged()) return
val (text, referenceData, explicitImports) = conversionResult!!
text!! // otherwise we should get true from doConversionAndInsertImportsIfUnchanged and return above
val boundsAfterReplace =
runWriteAction {
val startOffset = bounds.startOffset
document.replaceString(startOffset, bounds.endOffset, text)
val boundsAfterReplace = runWriteAction {
val startOffset = bounds.startOffset
document.replaceString(startOffset, bounds.endOffset, text)
val endOffsetAfterCopy = startOffset + text.length
editor.caretModel.moveToOffset(endOffsetAfterCopy)
TextRange(startOffset, endOffsetAfterCopy)
}
val endOffsetAfterCopy = startOffset + text.length
editor.caretModel.moveToOffset(endOffsetAfterCopy)
TextRange(startOffset, endOffsetAfterCopy)
}
val newBounds = insertImports(boundsAfterReplace, referenceData, explicitImports)
@@ -181,7 +196,12 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
}
}
private fun buildReferenceData(text: String, parseContext: ParseContext, importsAndPackage: String, targetFile: KtFile): Collection<KotlinReferenceData> {
private fun buildReferenceData(
text: String,
parseContext: ParseContext,
importsAndPackage: String,
targetFile: KtFile
): Collection<KotlinReferenceData> {
var blockStart: Int? = null
var blockEnd: Int? = null
val fileText = buildString {
@@ -202,7 +222,6 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
}
val dummyFile = KtPsiFactory(targetFile.project).createAnalyzableFile("dummy.kt", fileText, targetFile)
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArrayOf(blockStart!!), intArrayOf(blockEnd!!))
}
@@ -216,16 +235,17 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
}
companion object {
@get:TestOnly var conversionPerformed: Boolean = false
@get:TestOnly
var conversionPerformed: Boolean = false
}
}
internal class ConversionResult(
val text: String,
val parseContext: ParseContext,
val importsToAdd: Set<FqName>,
val textChanged: Boolean,
val converterContext: ConverterContext?
val text: String,
val parseContext: ParseContext,
val importsToAdd: Set<FqName>,
val textChanged: Boolean,
val converterContext: ConverterContext?
)
internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModule: Module?, useNewJ2k: Boolean): ConversionResult {
@@ -237,16 +257,16 @@ internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModu
IdeaJavaToKotlinServices
)
val inputElements = this.toList().filterIsInstance<PsiElement>()
val inputElements = toList().filterIsInstance<PsiElement>()
val (results, _, converterContext) =
ProgressManager.getInstance().runProcessWithProgressSynchronously(
ThrowableComputable<Result, Exception> {
runReadAction { converter.elementsToKotlin(inputElements) }
},
JavaToKotlinAction.title,
false,
project
)
ProgressManager.getInstance().runProcessWithProgressSynchronously(
ThrowableComputable<Result, Exception> {
runReadAction { converter.elementsToKotlin(inputElements) }
},
JavaToKotlinAction.title,
false,
project
)
val importsToAdd = LinkedHashSet<FqName>()
@@ -267,8 +287,7 @@ internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModu
parseContext = result.parseContext
}
importsToAdd.addAll(result.importsToAdd)
}
else { // failed to convert element to Kotlin, insert "as is"
} else { // failed to convert element to Kotlin, insert "as is"
convertedCodeBuilder.append(originalText)
}
}
@@ -297,11 +316,11 @@ internal fun isNoConversionPosition(file: KtFile, offset: Int): Boolean {
if (token !is PsiWhiteSpace && token.endOffset != offset) return true // pasting into the middle of token
for (element in token.parentsWithSelf) {
if (element is PsiComment) {
return element.node.elementType == KtTokens.EOL_COMMENT || offset != element.endOffset
when (element) {
is PsiComment -> return element.node.elementType == KtTokens.EOL_COMMENT || offset != element.endOffset
is KtStringTemplateEntryWithExpression -> return false
is KtStringTemplateExpression -> return true
}
if (element is KtStringTemplateEntryWithExpression) return false
if (element is KtStringTemplateExpression) return true
}
return false
}
@@ -61,11 +61,17 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
override fun setOffsets(offsets: IntArray?, index: Int) = index
companion object {
val DATA_FLAVOR: DataFlavor = DataFlavor(ConvertTextJavaCopyPasteProcessor::class.java, "class: ConvertTextJavaCopyPasteProcessor")
val DATA_FLAVOR: DataFlavor =
DataFlavor(ConvertTextJavaCopyPasteProcessor::class.java, "class: ConvertTextJavaCopyPasteProcessor")
}
}
override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): List<TextBlockTransferableData> {
override fun collectTransferableData(
file: PsiFile,
editor: Editor,
startOffsets: IntArray,
endOffsets: IntArray
): List<TextBlockTransferableData> {
if (file is KtFile) return listOf(CopiedKotlinCode(file.text, startOffsets, endOffsets))
return emptyList()
}
@@ -73,22 +79,28 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
override fun extractTransferableData(content: Transferable): List<TextBlockTransferableData> {
try {
if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
if (content.isDataFlavorSupported(CopiedKotlinCode.DATA_FLAVOR) ||
/* Handled by ConvertJavaCopyPasteProcessor */
content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)) return emptyList()
content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)
) return emptyList()
val text = content.getTransferData(DataFlavor.stringFlavor) as String
return listOf(MyTransferableData(text))
}
}
catch (e: Throwable) {
} catch (e: Throwable) {
LOG.error(e)
}
return emptyList()
}
override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, values: List<TextBlockTransferableData>) {
override fun processTransferableData(
project: Project,
editor: Editor,
bounds: RangeMarker,
caretOffset: Int,
indented: Ref<Boolean>,
values: List<TextBlockTransferableData>
) {
if (DumbService.getInstance(project).isDumb) return
if (!KotlinEditorOptions.getInstance().isEnableJavaToKotlinConversion) return //TODO: use another option?
@@ -100,11 +112,8 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
val useNewJ2k = checkUseNewJ2k(targetFile)
val targetModule = targetFile.module
val pasteTarget = detectPasteTarget(targetFile, bounds.startOffset, bounds.endOffset) ?: return
val conversionContext = detectConversionContext(pasteTarget.pasteContext, text, project) ?: return
if (!confirmConvertJavaOnPaste(project, isPlainText = true)) return
val copiedJavaCode = prepareCopiedJavaCodeByContext(text, conversionContext, pasteTarget)
@@ -121,6 +130,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
val importsInsertOffset = targetFile.importList?.endOffset ?: 0
if (targetFile.importDirectives.isEmpty() && importsInsertOffset > 0)
convertedImportsText = "\n" + convertedImportsText
if (convertedImportsText.isNotBlank())
editor.document.insertString(importsInsertOffset, convertedImportsText)
@@ -139,9 +149,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
conversionPerformed = true
}
val conversionTime = measureTimeMillis {
convert()
}
val conversionTime = measureTimeMillis { convert() }
logJ2kConversionStatistics(
ConversionType.TEXT_EXPRESSION,
checkUseNewJ2k(targetFile),
@@ -151,7 +159,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
)
}
private fun DataForConversion.convertCodeToKotlin(project: Project, targetModule: Module?,useNewJ2k: Boolean): ConversionResult {
private fun DataForConversion.convertCodeToKotlin(project: Project, targetModule: Module?, useNewJ2k: Boolean): ConversionResult {
return elementsAndTexts.convertCodeToKotlin(project, targetModule, useNewJ2k)
}
@@ -168,7 +176,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
val fileText = file.text
val dummyDeclarationText = "fun dummy(){}"
val newFileText = fileText.substring(0, startOffset) + " " + dummyDeclarationText + "\n" + fileText.substring(endOffset)
val newFileText = "${fileText.substring(0, startOffset)} $dummyDeclarationText\n${fileText.substring(endOffset)}"
val newFile = parseAsFile(newFileText, KotlinFileType.INSTANCE, file.project)
(newFile as KtFile).analysisContext = file
@@ -183,9 +191,8 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
private fun detectConversionContext(pasteContext: KotlinContext, text: String, project: Project): JavaContext? {
if (isParsedAsKotlinCode(text, pasteContext, project)) return null
fun JavaContext.check(): JavaContext? {
return takeIf { isParsedAsJavaCode(text, it, project) }
}
fun JavaContext.check(): JavaContext? =
takeIf { isParsedAsJavaCode(text, it, project) }
when (pasteContext) {
KotlinContext.TOP_LEVEL -> {
@@ -238,19 +245,21 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
}
private fun parseAsFile(text: String, fileType: LanguageFileType, project: Project): PsiFile {
return PsiFileFactory.getInstance(project).createFileFromText("Dummy." + fileType.defaultExtension, fileType, text, LocalTimeCounter.currentTime(), true)
return PsiFileFactory.getInstance(project)
.createFileFromText("Dummy." + fileType.defaultExtension, fileType, text, LocalTimeCounter.currentTime(), true)
}
private fun DataForConversion.tryResolveImports(targetFile: KtFile): ElementAndTextList {
val importResolver = PlainTextPasteImportResolver(this, targetFile)
importResolver.addImportsFromTargetFile()
importResolver.tryResolveReferences()
return ElementAndTextList(importResolver.addedImports.flatMap { listOf("\n", it) } + "\n\n") //TODO Non-manual formatting for import list
return ElementAndTextList(importResolver.addedImports.flatMap { importStatement ->
listOf("\n", importStatement)
} + "\n\n") //TODO Non-manual formatting for import list
}
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 {
@@ -265,16 +274,25 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
val classDef = when (context) {
JavaContext.TOP_LEVEL -> ""
JavaContext.CLASS_BODY,
JavaContext.IN_BLOCK,
JavaContext.EXPRESSION -> {
JavaContext.CLASS_BODY, JavaContext.IN_BLOCK, JavaContext.EXPRESSION -> {
val lightClass = target.getParentOfType<KtClass>(false)?.toLightClass()
buildString {
append("class ")
append(lightClass?.name ?: "Dummy")
lightClass?.extendsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " extends ") { it.getCanonicalText(true) } }
lightClass?.implementsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " implements ") { it.getCanonicalText(true) } }
lightClass?.extendsListTypes?.ifNotEmpty {
joinTo(
this@buildString,
prefix = " extends "
) { it.getCanonicalText(true) }
}
lightClass?.implementsListTypes?.ifNotEmpty {
joinTo(this@buildString, prefix = " implements ") {
it.getCanonicalText(
true
)
}
}
}
}
}
@@ -300,6 +318,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
}
companion object {
@get:TestOnly var conversionPerformed: Boolean = false
@get:TestOnly
var conversionPerformed: Boolean = false
}
}
@@ -34,11 +34,10 @@ 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
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 {
@@ -65,7 +64,6 @@ 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 }
@@ -126,8 +124,7 @@ data class DataForConversion private constructor(
val offset = this[i]
if (offset >= range.end) {
this[i] = offset - range.length
}
else {
} else {
assert(offset <= range.start)
}
}
@@ -141,7 +138,10 @@ data class DataForConversion private constructor(
}
private fun PsiElement.maximalParentToClip(range: TextRange): PsiElement? {
val firstNotInRange = parentsWithSelf.takeWhile { it !is PsiDirectory }.firstOrNull { it.range !in range } ?: return null
val firstNotInRange = parentsWithSelf
.takeWhile { it !is PsiDirectory }
.firstOrNull { it.range !in range }
?: return null
return firstNotInRange.parentsWithSelf.lastOrNull { it.minimizedTextRange() in range }
}
@@ -155,41 +155,42 @@ data class DataForConversion private constructor(
private fun canDropElementFromText(element: PsiElement): Boolean {
return when (element) {
is PsiWhiteSpace, is PsiComment, is PsiModifierList, is PsiAnnotation -> true
is PsiJavaToken -> {
when (element.tokenType) {
// modifiers
JavaTokenType.PUBLIC_KEYWORD, JavaTokenType.PROTECTED_KEYWORD, JavaTokenType.PRIVATE_KEYWORD,
JavaTokenType.STATIC_KEYWORD, JavaTokenType.ABSTRACT_KEYWORD, JavaTokenType.FINAL_KEYWORD,
JavaTokenType.NATIVE_KEYWORD, JavaTokenType.SYNCHRONIZED_KEYWORD, JavaTokenType.STRICTFP_KEYWORD,
JavaTokenType.TRANSIENT_KEYWORD, JavaTokenType.VOLATILE_KEYWORD, JavaTokenType.DEFAULT_KEYWORD -> element.getParent() is PsiModifierList
JavaTokenType.TRANSIENT_KEYWORD, JavaTokenType.VOLATILE_KEYWORD, JavaTokenType.DEFAULT_KEYWORD ->
element.getParent() is PsiModifierList
JavaTokenType.SEMICOLON -> true
else -> false
}
}
is PsiCodeBlock -> element.getParent() is PsiMethod
else -> element.firstChild == null
}
}
private fun tryClipLeftSide(element: PsiElement, leftBound: Int)
= tryClipSide(element, leftBound, { range }, { allChildren })
private fun tryClipLeftSide(element: PsiElement, leftBound: Int) =
tryClipSide(element, leftBound, { range }, { allChildren })
private fun tryClipRightSide(element: PsiElement, rightBound: Int): Int? {
fun Int.transform() = Int.MAX_VALUE - this
fun TextRange.transform() = TextRange(end.transform(), start.transform())
return tryClipSide(element, rightBound.transform(), { range.transform() }, { lastChild.siblings(forward = false) })?.transform()
return tryClipSide(
element,
rightBound.transform(),
{ range.transform() },
{ lastChild.siblings(forward = false) }
)?.transform()
}
private fun tryClipSide(
element: PsiElement,
rangeBound: Int,
rangeFunction: PsiElement.() -> TextRange,
childrenFunction: PsiElement.() -> Sequence<PsiElement>
element: PsiElement,
rangeBound: Int,
rangeFunction: PsiElement.() -> TextRange,
childrenFunction: PsiElement.() -> Sequence<PsiElement>
): Int? {
if (element.firstChild == null) return null
@@ -202,12 +203,10 @@ data class DataForConversion private constructor(
if (childRange.start >= rangeBound) { // we have cut enough already
break
}
else if (childRange.end <= rangeBound) { // need to drop the whole element
} else if (childRange.end <= rangeBound) { // need to drop the whole element
if (!canDropElementFromText(child)) return null
clipTo = childRange.end
}
else { // rangeBound is inside child's range
} else { // rangeBound is inside child's range
if (child is PsiWhiteSpace) break // no need to cut whitespace - we can leave it as is
return tryClipSide(child, rangeBound, rangeFunction, childrenFunction)
}
@@ -217,15 +216,14 @@ data class DataForConversion private constructor(
}
private fun ElementAndTextList.collectElementsToConvert(
file: PsiJavaFile,
fileText: String,
range: TextRange
file: PsiJavaFile,
fileText: String,
range: TextRange
) {
val elements = file.elementsInRange(range)
if (elements.isEmpty()) {
add(fileText.substring(range.start, range.end))
}
else {
} else {
add(fileText.substring(range.start, elements.first().range.start))
elements.forEach {
if (shouldExpandToChildren(it))
@@ -243,7 +241,7 @@ data class DataForConversion private constructor(
private fun buildImportsAndPackage(sourceFile: PsiJavaFile): String {
return buildString {
val packageName = sourceFile.packageName
if (!packageName.isEmpty()) {
if (packageName.isNotEmpty()) {
append("package $packageName\n")
}
@@ -253,8 +251,7 @@ data class DataForConversion private constructor(
val qualifiedName = import.qualifiedName ?: continue
if (import.isOnDemand) {
append("import $qualifiedName.*\n")
}
else {
} else {
val fqName = FqNameUnsafe(qualifiedName)
// skip explicit imports of platform classes mapped into Kotlin classes
if (fqName.isSafe && JavaToKotlinClassMap.isJavaPlatformClass(fqName.toSafe())) continue
@@ -42,10 +42,10 @@ class ElementAndTextList() {
fun toList(): List<Any> = elementsAndTexts.toList()
fun process(processor: ElementsAndTextsProcessor) {
elementsAndTexts.forEach {
when (it) {
is PsiElement -> processor.processElement(it)
is String -> processor.processText(it)
elementsAndTexts.forEach { element ->
when (element) {
is PsiElement -> processor.processElement(element)
is String -> processor.processText(element)
}
}
}
@@ -26,7 +26,6 @@ data class ContextDeclarations(
val memberDeclarationsJavaStubs: String
)
class JavaContextDeclarationRenderer {
private val KtElement.memberDeclarations
get() = parents()
@@ -34,6 +34,7 @@ class KotlinFilePasteProvider : PasteProvider {
val ktFile = KtPsiFactory(project).createFile(text)
val fileName = (ktFile.declarations.firstOrNull()?.name ?: return) + ".kt"
@Suppress("UsePropertyAccessSyntax")
val directory = ideView.getOrChooseDirectory() ?: return
project.executeWriteCommand("Create Kotlin file") {
val file = try {
@@ -47,9 +48,9 @@ class KotlinFilePasteProvider : PasteProvider {
if (document != null) {
document.setText(text)
documentManager.commitDocument(document)
val qualName = JavaDirectoryService.getInstance()?.getPackage(directory)?.qualifiedName
if (qualName != null && file is KtFile) {
file.packageFqName = FqName(qualName)
val qualifiedName = JavaDirectoryService.getInstance()?.getPackage(directory)?.qualifiedName
if (qualifiedName != null && file is KtFile) {
file.packageFqName = FqName(qualifiedName)
}
OpenFileDescriptor(project, file.virtualFile).navigate(true)
}
@@ -64,7 +65,8 @@ class KotlinFilePasteProvider : PasteProvider {
//todo: KT-25329, to remove these heuristics
if (text.contains(";\n") ||
((text.contains("public interface") || text.contains("public class")) &&
!text.contains("fun "))) return false //Optimisation for Java. Kotlin doesn't need that...
!text.contains("fun "))
) return false //Optimisation for Java. Kotlin doesn't need that...
val file = KtPsiFactory(project).createFile(text)
return !PsiTreeUtil.hasErrorElements(file)
}
@@ -37,8 +37,7 @@ import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
import java.util.*
class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val targetFile: KtFile) {
class PlainTextPasteImportResolver(private val dataForConversion: DataForConversion, val targetFile: KtFile) {
private val file = dataForConversion.file
private val project = targetFile.project
@@ -59,8 +58,8 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
private fun canBeImported(descriptor: DeclarationDescriptorWithVisibility?): Boolean {
return descriptor != null
&& descriptor.canBeReferencedViaImport()
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
&& descriptor.canBeReferencedViaImport()
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
}
private fun addImport(importStatement: PsiImportStatementBase, shouldAddToTarget: Boolean = false) {
@@ -76,46 +75,54 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
val importedReference = importDirective.importedReference
if (importPath != null && !importPath.hasAlias() && importedReference is KtDotQualifiedExpression) {
val receiver = importedReference
.receiverExpression
.referenceExpression()
?.mainReference
?.resolve()
.receiverExpression
.referenceExpression()
?.mainReference
?.resolve()
val selector = importedReference
.selectorExpression
?.referenceExpression()
?.mainReference
?.resolve()
.selectorExpression
?.referenceExpression()
?.mainReference
?.resolve()
val isPackageReceiver = receiver is PsiPackage
val isClassReceiver = receiver is PsiClass
val isClassSelector = selector is PsiClass
if (importPath.isAllUnder) {
if (isClassReceiver)
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*"))
else if (isPackageReceiver)
addImport(psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName))
}
else {
if (isClassSelector)
addImport(psiElementFactory.createImportStatement(selector as PsiClass))
else if (isClassReceiver)
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, importPath.importedName!!.asString()))
when {
isClassReceiver ->
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*"))
isPackageReceiver ->
addImport(psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName))
}
} else {
when {
isClassSelector ->
addImport(psiElementFactory.createImportStatement(selector as PsiClass))
isClassReceiver ->
addImport(
psiElementFactory.createImportStaticStatement(
receiver as PsiClass,
importPath.importedName!!.asString()
)
)
}
}
}
}
if (importList !in dataForConversion.elementsAndTexts.toList())
if (importList !in dataForConversion.elementsAndTexts.toList()) {
runWriteAction {
targetFile.importDirectives.forEach(::tryConvertKotlinImport)
}
}
}
fun tryResolveReferences() {
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) {
it.reference != null
&& it.reference is PsiQualifiedReference
&& it.reference?.resolve() == null
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) { element ->
element.reference != null
&& element.reference is PsiQualifiedReference
&& element.reference?.resolve() == null
}
fun tryResolveReference(reference: PsiQualifiedReference): Boolean {
@@ -123,28 +130,27 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
val referenceName = reference.referenceName ?: return false
if (referenceName in failedToResolveReferenceNames) return false
val classes = shortNameCache.getClassesByName(referenceName, scope)
.mapNotNull { psiClass ->
val containingFile = psiClass.containingFile
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
}
else {
null
}
}
.filter { canBeImported(it.second) }
.mapNotNull { psiClass ->
val containingFile = psiClass.containingFile
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
} else null
}.filter { canBeImported(it.second) }
classes.find { (_, descriptor) -> JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty() }
?.let { (psiClass, _) -> addImport(psiElementFactory.createImportStatement(psiClass)) }
classes.find { (_, descriptor) ->
JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty()
}?.let { (psiClass, _) ->
addImport(psiElementFactory.createImportStatement(psiClass))
}
if (reference.resolve() != null) return true
classes.singleOrNull()?.let { (psiClass, _) ->
addImport(psiElementFactory.createImportStatement(psiClass), true)
}
if (reference.resolve() != null) return true
else {
if (classes.isNotEmpty()) {
when {
reference.resolve() != null -> return true
classes.isNotEmpty() -> {
ambiguityInResolution = true
return false
}
@@ -163,14 +169,10 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
addImport(psiElementFactory.createImportStaticStatement(psiMember.containingClass!!, psiMember.name!!), true)
}
if (reference.resolve() != null) return false
else {
if (members.isNotEmpty()) {
ambiguityInResolution = true
}
else {
couldNotResolve = true
}
when {
reference.resolve() != null -> return false
members.isNotEmpty() -> ambiguityInResolution = true
else -> couldNotResolve = true
}
return false
}