New J2K: separate logic in code builder to different classes

This commit is contained in:
Ilya Kirillov
2019-09-27 22:45:39 +03:00
parent 043a669812
commit 41935bdd34
7 changed files with 286 additions and 255 deletions
@@ -31,6 +31,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.j2k.*
import org.jetbrains.kotlin.nj2k.printing.JKCodeBuilder
import org.jetbrains.kotlin.nj2k.types.JKTypeFactory import org.jetbrains.kotlin.nj2k.types.JKTypeFactory
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
@@ -135,7 +136,7 @@ class NewJavaToKotlinConverter(
processor.updateState(i, 1, phaseDescription) processor.updateState(i, 1, phaseDescription)
val (element, ast) = elementWithAst val (element, ast) = elementWithAst
if (ast == null) return@mapIndexed null if (ast == null) return@mapIndexed null
val code = NewCodeBuilder(context).run { printCodeOut(ast) } val code = JKCodeBuilder(context).run { printCodeOut(ast) }
val parseContext = when (element) { val parseContext = when (element) {
is PsiStatement, is PsiExpression -> ParseContext.CODE_BLOCK is PsiStatement, is PsiExpression -> ParseContext.CODE_BLOCK
else -> ParseContext.TOP_LEVEL else -> ParseContext.TOP_LEVEL
@@ -3,76 +3,36 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * 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.nj2k package org.jetbrains.kotlin.nj2k.printing
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.j2k.ast.Nullability import org.jetbrains.kotlin.j2k.ast.Nullability
import org.jetbrains.kotlin.nj2k.symbols.JKSymbol import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.symbols.getDisplayFqName import org.jetbrains.kotlin.nj2k.symbols.getDisplayFqName
import org.jetbrains.kotlin.nj2k.tree.* import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.tree.visitors.JKVisitorWithCommentsPrinting import org.jetbrains.kotlin.nj2k.tree.visitors.JKVisitorWithCommentsPrinting
import org.jetbrains.kotlin.nj2k.types.* import org.jetbrains.kotlin.nj2k.types.JKContextType
import org.jetbrains.kotlin.nj2k.types.isInterface
import org.jetbrains.kotlin.nj2k.types.updateNullability
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class NewCodeBuilder(context: NewJ2kConverterContext) { internal class JKCodeBuilder(context: NewJ2kConverterContext) {
private val elementInfoStorage = context.elementsInfoStorage private val elementInfoStorage = context.elementsInfoStorage
private val printer = JKPrinter(context.project, context.importStorage, elementInfoStorage) private val printer = JKPrinter(context.project, context.importStorage, elementInfoStorage)
private val commentPrinter = JKCommentPrinter(printer)
private fun classKindString(kind: JKClass.ClassKind): String = when (kind) { fun printCodeOut(root: JKTreeElement): String {
JKClass.ClassKind.ANNOTATION -> "annotation class" Visitor().also { root.accept(it) }
JKClass.ClassKind.CLASS -> "class" return printer.toString().replace("\r\n", "\n")
JKClass.ClassKind.ENUM -> "enum class"
JKClass.ClassKind.INTERFACE -> "interface"
JKClass.ClassKind.OBJECT -> "object"
JKClass.ClassKind.COMPANION -> "companion object"
} }
inner class Visitor : JKVisitorWithCommentsPrinting() { private inner class Visitor : JKVisitorWithCommentsPrinting() {
private val printedTokens = mutableSetOf<JKComment>()
//TODO move to ast transformation phase
private fun JKComment.shouldBeDropped(): Boolean =
text.startsWith("//noinspection")
private fun JKComment.createText() =
if (this !in printedTokens) {
printedTokens += this
text
} else null
private fun List<JKComment>.createText(): String = buildString {
var needNewLine = false
for (comment in this@createText) {
if (comment.shouldBeDropped()) continue
val text = comment.createText() ?: continue
if (needNewLine) appendln() else append(' ')
append(text)
needNewLine = text.startsWith("//") || '\n' in text
}
}
private fun String.hasNoLineBreakAfterSingleLineComment() = lastIndexOf('\n') < lastIndexOf("//")
override fun printLeftNonCodeElements(element: JKFormattingOwner) { override fun printLeftNonCodeElements(element: JKFormattingOwner) {
val text = element.trailingComments.createText() commentPrinter.printTrailingComments(element)
printer.print(text)
val addNewLine = element.hasTrailingLineBreak
|| element is JKDeclaration && element.trailingComments.isNotEmpty() // add new line between comment & declaration
|| text.hasNoLineBreakAfterSingleLineComment()
if (addNewLine) printer.println()
} }
override fun printRightNonCodeElements(element: JKFormattingOwner) { override fun printRightNonCodeElements(element: JKFormattingOwner) {
val text = element.leadingComments.createText() commentPrinter.printLeadingComments(element)
printer.print(text)
val addNewLine = element.hasLeadingLineBreak || text.hasNoLineBreakAfterSingleLineComment()
if (addNewLine) printer.println()
} }
private fun renderTokenElement(tokenElement: JKTokenElement) { private fun renderTokenElement(tokenElement: JKTokenElement) {
@@ -81,24 +41,18 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
printRightNonCodeElements(tokenElement) printRightNonCodeElements(tokenElement)
} }
override fun visitModifierElementRaw(modifierElement: JKModifierElement) {
if (modifierElement.modifier != Modality.FINAL) {
printer.print(modifierElement.modifier.text)
}
}
private fun renderExtraTypeParametersUpperBounds(typeParameterList: JKTypeParameterList) { private fun renderExtraTypeParametersUpperBounds(typeParameterList: JKTypeParameterList) {
val extraTypeBounds = typeParameterList.typeParameters val extraTypeBounds = typeParameterList.typeParameters
.filter { it.upperBounds.size > 1 } .filter { it.upperBounds.size > 1 }
if (extraTypeBounds.isNotEmpty()) { if (extraTypeBounds.isNotEmpty()) {
printer.print(" where ") printer.print(" where ")
val typeParametersWithBoudnds = val typeParametersWithBounds =
extraTypeBounds.flatMap { typeParameter -> extraTypeBounds.flatMap { typeParameter ->
typeParameter.upperBounds.map { bound -> typeParameter.upperBounds.map { bound ->
typeParameter.name to bound typeParameter.name to bound
} }
} }
printer.renderList(typeParametersWithBoudnds) { (name, bound) -> printer.renderList(typeParametersWithBounds) { (name, bound) ->
name.accept(this) name.accept(this)
printer.print(" : ") printer.print(" : ")
bound.accept(this) bound.accept(this)
@@ -106,10 +60,35 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
private fun renderModifiersList(modifiersList: JKModifiersListOwner) {
val hasOverrideModifier = modifiersList
.safeAs<JKOtherModifiersOwner>()
?.hasOtherModifier(OtherModifier.OVERRIDE) == true
modifiersList.forEachModifier { modifierElement ->
if (modifierElement.modifier == Modality.FINAL || modifierElement.modifier == Visibility.PUBLIC) {
if (hasOverrideModifier) {
modifierElement.accept(this)
} else {
printLeftNonCodeElements(modifierElement)
printRightNonCodeElements(modifierElement)
}
} else {
modifierElement.accept(this)
}
printer.print(" ")
}
}
override fun visitTreeElementRaw(treeElement: JKTreeElement) { override fun visitTreeElementRaw(treeElement: JKTreeElement) {
printer.print("/* !!! Hit visitElement for element type: ${treeElement::class} !!! */") printer.print("/* !!! Hit visitElement for element type: ${treeElement::class} !!! */")
} }
override fun visitModifierElementRaw(modifierElement: JKModifierElement) {
if (modifierElement.modifier != Modality.FINAL) {
printer.print(modifierElement.modifier.text)
}
}
override fun visitTreeRootRaw(treeRoot: JKTreeRoot) { override fun visitTreeRootRaw(treeRoot: JKTreeRoot) {
treeRoot.element.accept(this) treeRoot.element.accept(this)
} }
@@ -195,26 +174,6 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
breakStatement.label.accept(this) breakStatement.label.accept(this)
} }
private fun renderModifiersList(modifiersList: JKModifiersListOwner) {
val hasOverrideModifier = modifiersList.safeAs<JKOtherModifiersOwner>()
?.otherModifierElements
?.any { it.otherModifier == OtherModifier.OVERRIDE } == true
modifiersList.forEachModifier { modifierElement ->
if (modifierElement.modifier == Modality.FINAL || modifierElement.modifier == Visibility.PUBLIC) {
if (hasOverrideModifier) {
modifierElement.accept(this)
} else {
printLeftNonCodeElements(modifierElement)
printRightNonCodeElements(modifierElement)
}
} else {
modifierElement.accept(this)
}
printer.print(" ")
}
}
override fun visitClassRaw(klass: JKClass) { override fun visitClassRaw(klass: JKClass) {
klass.annotationList.accept(this) klass.annotationList.accept(this)
if (klass.annotationList.annotations.isNotEmpty()) { if (klass.annotationList.annotations.isNotEmpty()) {
@@ -222,7 +181,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
renderModifiersList(klass) renderModifiersList(klass)
printer.print(" ") printer.print(" ")
printer.print(classKindString(klass.classKind)) printer.print(klass.classKind.text)
printer.print(" ") printer.print(" ")
klass.name.accept(this) klass.name.accept(this)
klass.typeParameterList.accept(this) klass.typeParameterList.accept(this)
@@ -283,7 +242,6 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
override fun visitFieldRaw(field: JKField) { override fun visitFieldRaw(field: JKField) {
field.annotationList.accept(this) field.annotationList.accept(this)
if (field.annotationList.annotations.isNotEmpty()) { if (field.annotationList.annotations.isNotEmpty()) {
@@ -322,7 +280,6 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
override fun visitIsExpressionRaw(isExpression: JKIsExpression) { override fun visitIsExpressionRaw(isExpression: JKIsExpression) {
isExpression.expression.accept(this) isExpression.expression.accept(this)
printer.print(" is ") printer.print(" is ")
@@ -353,7 +310,9 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
override fun visitKtAnnotationArrayInitializerExpressionRaw(ktAnnotationArrayInitializerExpression: JKKtAnnotationArrayInitializerExpression) { override fun visitKtAnnotationArrayInitializerExpressionRaw(
ktAnnotationArrayInitializerExpression: JKKtAnnotationArrayInitializerExpression
) {
printer.print("[") printer.print("[")
printer.renderList(ktAnnotationArrayInitializerExpression.initializers) { printer.renderList(ktAnnotationArrayInitializerExpression.initializers) {
it.accept(this) it.accept(this)
@@ -431,7 +390,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
override fun visitTypeParameterListRaw(typeParameterList: JKTypeParameterList) { override fun visitTypeParameterListRaw(typeParameterList: JKTypeParameterList) {
if (typeParameterList.typeParameters.isNotEmpty()) { if (typeParameterList.typeParameters.isNotEmpty()) {
printer.par(ParenthesisKind.ANGLE) { printer.par(JKPrinterBase.ParenthesisKind.ANGLE) {
printer.renderList(typeParameterList.typeParameters) { printer.renderList(typeParameterList.typeParameters) {
it.accept(this) it.accept(this)
} }
@@ -472,9 +431,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
printer.print(" ") printer.print(" ")
} }
override fun visitLabelEmptyRaw(labelEmpty: JKLabelEmpty) { override fun visitLabelEmptyRaw(labelEmpty: JKLabelEmpty) {}
}
override fun visitLabelTextRaw(labelText: JKLabelText) { override fun visitLabelTextRaw(labelText: JKLabelText) {
printer.print("@") printer.print("@")
@@ -530,7 +487,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
override fun visitTypeArgumentListRaw(typeArgumentList: JKTypeArgumentList) { override fun visitTypeArgumentListRaw(typeArgumentList: JKTypeArgumentList) {
if (typeArgumentList.typeArguments.isNotEmpty()) { if (typeArgumentList.typeArguments.isNotEmpty()) {
printer.par(ParenthesisKind.ANGLE) { printer.par(JKPrinterBase.ParenthesisKind.ANGLE) {
printer.renderList(typeArgumentList.typeArguments) { printer.renderList(typeArgumentList.typeArguments) {
it.accept(this) it.accept(this)
} }
@@ -584,11 +541,9 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
override fun visitEmptyStatementRaw(emptyStatement: JKEmptyStatement) { override fun visitEmptyStatementRaw(emptyStatement: JKEmptyStatement) {}
}
override fun visitStubExpressionRaw(stubExpression: JKStubExpression) { override fun visitStubExpressionRaw(stubExpression: JKStubExpression) {}
}
override fun visitKtConvertedFromForLoopSyntheticWhileStatementRaw( override fun visitKtConvertedFromForLoopSyntheticWhileStatementRaw(
ktConvertedFromForLoopSyntheticWhileStatement: JKKtConvertedFromForLoopSyntheticWhileStatement ktConvertedFromForLoopSyntheticWhileStatement: JKKtConvertedFromForLoopSyntheticWhileStatement
@@ -605,7 +560,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
printer.renderSymbol(newExpression.classSymbol, newExpression) printer.renderSymbol(newExpression.classSymbol, newExpression)
newExpression.typeArgumentList.accept(this) newExpression.typeArgumentList.accept(this)
if (!newExpression.classSymbol.isInterface() || newExpression.arguments.arguments.isNotEmpty()) { if (!newExpression.classSymbol.isInterface() || newExpression.arguments.arguments.isNotEmpty()) {
printer.par(ParenthesisKind.ROUND) { printer.par(JKPrinterBase.ParenthesisKind.ROUND) {
newExpression.arguments.accept(this) newExpression.arguments.accept(this)
} }
} }
@@ -706,7 +661,7 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
private fun renderParameterList(parameters: List<JKParameter>) { private fun renderParameterList(parameters: List<JKParameter>) {
printer.par(ParenthesisKind.ROUND) { printer.par(JKPrinterBase.ParenthesisKind.ROUND) {
printer.renderList(parameters) { printer.renderList(parameters) {
it.accept(this) it.accept(this)
} }
@@ -740,14 +695,14 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} }
} }
override fun visitLambdaExpressionRaw(lambdaExpression: JKLambdaExpression) { override fun visitLambdaExpressionRaw(lambdaExpression: JKLambdaExpression) {
val printLambda = { val printLambda = {
printer.par(ParenthesisKind.CURVED) { printer.par(JKPrinterBase.ParenthesisKind.CURVED) {
if (lambdaExpression.statement.statements.size > 1) if (lambdaExpression.statement.statements.size > 1)
printer.println() printer.println()
lambdaExpression.parameters.firstOrNull()?.accept(this) printer.renderList(lambdaExpression.parameters) {
lambdaExpression.parameters.asSequence().drop(1).forEach { printer.print(", "); it.accept(this) } it.accept(this)
}
if (lambdaExpression.parameters.isNotEmpty()) { if (lambdaExpression.parameters.isNotEmpty()) {
printer.print(" -> ") printer.print(" -> ")
} }
@@ -758,14 +713,15 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
} else { } else {
statement.accept(this) statement.accept(this)
} }
if (lambdaExpression.statement.statements.size > 1) if (lambdaExpression.statement.statements.size > 1) {
printer.println() printer.println()
}
} }
} }
if (lambdaExpression.functionalType.present()) { if (lambdaExpression.functionalType.present()) {
printer.renderType(lambdaExpression.functionalType.type, lambdaExpression) printer.renderType(lambdaExpression.functionalType.type, lambdaExpression)
printer.print(" ") printer.print(" ")
printer.par(ParenthesisKind.ROUND, printLambda) printer.par(JKPrinterBase.ParenthesisKind.ROUND, printLambda)
} else { } else {
printLambda() printLambda()
} }
@@ -846,14 +802,13 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
JKClassLiteralExpression.ClassLiteralType.KOTLIN_CLASS -> printer.print("class") JKClassLiteralExpression.ClassLiteralType.KOTLIN_CLASS -> printer.print("class")
JKClassLiteralExpression.ClassLiteralType.JAVA_CLASS -> printer.print("class.java") JKClassLiteralExpression.ClassLiteralType.JAVA_CLASS -> printer.print("class.java")
JKClassLiteralExpression.ClassLiteralType.JAVA_PRIMITIVE_CLASS -> printer.print("class.javaPrimitiveType") JKClassLiteralExpression.ClassLiteralType.JAVA_PRIMITIVE_CLASS -> printer.print("class.javaPrimitiveType")
JKClassLiteralExpression.ClassLiteralType.JAVA_VOID_TYPE -> { JKClassLiteralExpression.ClassLiteralType.JAVA_VOID_TYPE -> Unit
}
} }
} }
} }
override fun visitKtWhenCaseRaw(ktWhenCase: JKKtWhenCase) { override fun visitKtWhenCaseRaw(ktWhenCase: JKKtWhenCase) {
printer.renderList(ktWhenCase.labels, ", ") { printer.renderList(ktWhenCase.labels) {
it.accept(this) it.accept(this)
} }
printer.print(" -> ") printer.print(" -> ")
@@ -868,148 +823,4 @@ class NewCodeBuilder(context: NewJ2kConverterContext) {
ktValueWhenLabel.expression.accept(this) ktValueWhenLabel.expression.accept(this)
} }
} }
fun printCodeOut(root: JKTreeElement): String {
Visitor().also { root.accept(it) }
return printer.toString().replace("\r\n", "\n")
}
}
enum class ParenthesisKind(val open: String, val close: String) {
ROUND("(", ")"),
CURVED("{", "}"),
ANGLE("<", ">")
}
private class JKPrinter(
project: Project,
importStorage: JKImportStorage,
private val elementInfoStorage: JKElementInfoStorage
) {
val symbolRenderer = JKSymbolRenderer(importStorage, project)
private val stringBuilder: StringBuilder = StringBuilder()
private var currentIndent = 0;
private val indentSymbol = " ".repeat(4)
private var lastSymbolIsLineBreak = false
override fun toString(): String = stringBuilder.toString()
fun print(value: String) {
if (value.isNotEmpty()) {
lastSymbolIsLineBreak = false
}
stringBuilder.append(value)
}
fun println() {
if (lastSymbolIsLineBreak) return
stringBuilder.append('\n')
repeat(currentIndent) {
stringBuilder.append(indentSymbol)
}
lastSymbolIsLineBreak = true
}
inline fun indented(block: () -> Unit) {
currentIndent++
block()
currentIndent--
}
inline fun block(crossinline body: () -> Unit) {
par(ParenthesisKind.CURVED) {
indented(body)
}
}
inline fun par(kind: ParenthesisKind = ParenthesisKind.ROUND, body: () -> Unit) {
print(kind.open)
body()
print(kind.close)
}
private fun JKType.renderTypeInfo() {
this@JKPrinter.print(elementInfoStorage.getOrCreateInfoForElement(this).render())
}
fun renderType(type: JKType, owner: JKTreeElement?) {
if (type is JKNoType) return
if (type is JKCapturedType) {
when (val wildcard = type.wildcardType) {
is JKVarianceTypeParameterType -> {
renderType(wildcard.boundType, owner)
}
is JKStarProjectionType -> {
type.renderTypeInfo()
this.print("Any?")
}
}
return
}
type.renderTypeInfo()
when (type) {
is JKClassType -> {
renderSymbol(type.classReference, owner)
}
is JKContextType -> return
is JKStarProjectionType ->
this.print("*")
is JKTypeParameterType ->
this.print(type.identifier.name)
is JKVarianceTypeParameterType -> {
when (type.variance) {
JKVarianceTypeParameterType.Variance.IN -> this.print("in ")
JKVarianceTypeParameterType.Variance.OUT -> this.print("out ")
}
renderType(type.boundType, null)
}
else -> this.print("Unit /* TODO: ${type::class} */")
}
if (type is JKParametrizedType && type.parameters.isNotEmpty()) {
par(ParenthesisKind.ANGLE) {
renderList(type.parameters, renderElement = { renderType(it, null) })
}
}
// we print undefined types as nullable because we need smartcast to work in nullability inference in post-processing
if (type !is JKWildCardType
&& (type.nullability == Nullability.Default
&& owner?.safeAs<JKLambdaExpression>()?.functionalType?.type != type
|| type.nullability == Nullability.Nullable)
) {
this.print("?")
}
}
fun renderSymbol(symbol: JKSymbol, owner: JKTreeElement?) {
print(symbolRenderer.renderSymbol(symbol, owner))
}
inline fun <T> renderList(list: List<T>, separator: String = ", ", renderElement: (T) -> Unit) =
renderList(list, { this.print(separator) }, renderElement)
inline fun <T> renderList(list: List<T>, separator: () -> Unit, renderElement: (T) -> Unit) {
val (head, tail) = list.headTail()
head?.let(renderElement) ?: return
tail?.forEach {
separator()
renderElement(it)
}
}
}
fun String.escapedAsQualifiedName(): String =
split('.')
.map { it.escaped() }
.joinToString(".") { it }
private fun <T> List<T>.headTail(): Pair<T?, List<T>?> {
val head = this.firstOrNull()
val tail = if (size <= 1) null else subList(1, size)
return head to tail
} }
@@ -0,0 +1,59 @@
/*
* 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.nj2k.printing
import org.jetbrains.kotlin.nj2k.tree.JKComment
import org.jetbrains.kotlin.nj2k.tree.JKDeclaration
import org.jetbrains.kotlin.nj2k.tree.JKFormattingOwner
internal class JKCommentPrinter(val printer: JKPrinter) {
private val printedTokens = mutableSetOf<JKComment>()
//TODO move to ast transformation phase
private fun JKComment.shouldBeDropped(): Boolean =
text.startsWith("//noinspection")
private fun JKComment.createText() =
if (this !in printedTokens) {
printedTokens += this
text
} else null
private fun List<JKComment>.createText(): String = buildString {
var needNewLine = false
for (comment in this@createText) {
if (comment.shouldBeDropped()) continue
val text = comment.createText() ?: continue
if (needNewLine) appendln() else append(' ')
append(text)
needNewLine = text.startsWith("//") || '\n' in text
}
}
private fun String.hasNoLineBreakAfterSingleLineComment() = lastIndexOf('\n') < lastIndexOf("//")
fun printLeadingComments(element: JKFormattingOwner) {
val text = element.leadingComments.createText()
printer.print(text)
val addNewLine = element.hasLeadingLineBreak || text.hasNoLineBreakAfterSingleLineComment()
if (addNewLine) printer.println()
}
fun printTrailingComments(element: JKFormattingOwner) {
val text = element.trailingComments.createText()
printer.print(text)
val addNewLine = element.hasTrailingLineBreak
|| element is JKDeclaration && element.trailingComments.isNotEmpty() // add new line between comment & declaration
|| text.hasNoLineBreakAfterSingleLineComment()
if (addNewLine) printer.println()
}
}
@@ -0,0 +1,141 @@
/*
* 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.nj2k.printing
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.j2k.ast.Nullability
import org.jetbrains.kotlin.nj2k.JKElementInfoStorage
import org.jetbrains.kotlin.nj2k.JKImportStorage
import org.jetbrains.kotlin.nj2k.symbols.JKSymbol
import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.types.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal open class JKPrinterBase {
private val stringBuilder: StringBuilder = StringBuilder()
var currentIndent = 0;
private val indentSymbol = " ".repeat(4)
private var lastSymbolIsLineBreak = false
override fun toString(): String = stringBuilder.toString()
fun print(value: String) {
if (value.isNotEmpty()) {
lastSymbolIsLineBreak = false
}
stringBuilder.append(value)
}
fun println() {
if (lastSymbolIsLineBreak) return
stringBuilder.append('\n')
repeat(currentIndent) {
stringBuilder.append(indentSymbol)
}
lastSymbolIsLineBreak = true
}
inline fun indented(block: () -> Unit) {
currentIndent++
block()
currentIndent--
}
inline fun block(body: () -> Unit) {
par(ParenthesisKind.CURVED) {
indented(body)
}
}
inline fun par(kind: ParenthesisKind = ParenthesisKind.ROUND, body: () -> Unit) {
print(kind.open)
body()
print(kind.close)
}
inline fun <T> renderList(list: List<T>, separator: String = ", ", renderElement: (T) -> Unit) =
renderList(list, { this.print(separator) }, renderElement)
inline fun <T> renderList(list: List<T>, separator: () -> Unit, renderElement: (T) -> Unit) {
if (list.isEmpty()) return
renderElement(list.first())
for (element in list.subList(1, list.size)) {
separator()
renderElement(element)
}
}
enum class ParenthesisKind(val open: String, val close: String) {
ROUND("(", ")"),
CURVED("{", "}"),
ANGLE("<", ">")
}
}
internal class JKPrinter(
project: Project,
importStorage: JKImportStorage,
private val elementInfoStorage: JKElementInfoStorage
) : JKPrinterBase() {
val symbolRenderer = JKSymbolRenderer(importStorage, project)
private fun JKType.renderTypeInfo() {
this@JKPrinter.print(elementInfoStorage.getOrCreateInfoForElement(this).render())
}
fun renderType(type: JKType, owner: JKTreeElement?) {
if (type is JKNoType) return
if (type is JKCapturedType) {
when (val wildcard = type.wildcardType) {
is JKVarianceTypeParameterType -> {
renderType(wildcard.boundType, owner)
}
is JKStarProjectionType -> {
type.renderTypeInfo()
this.print("Any?")
}
}
return
}
type.renderTypeInfo()
when (type) {
is JKClassType -> {
renderSymbol(type.classReference, owner)
}
is JKContextType -> return
is JKStarProjectionType ->
this.print("*")
is JKTypeParameterType ->
this.print(type.identifier.name)
is JKVarianceTypeParameterType -> {
when (type.variance) {
JKVarianceTypeParameterType.Variance.IN -> this.print("in ")
JKVarianceTypeParameterType.Variance.OUT -> this.print("out ")
}
renderType(type.boundType, null)
}
else -> this.print("Unit /* TODO: ${type::class} */")
}
if (type is JKParametrizedType && type.parameters.isNotEmpty()) {
par(ParenthesisKind.ANGLE) {
renderList(type.parameters, renderElement = { renderType(it, null) })
}
}
// we print undefined types as nullable because we need smartcast to work in nullability inference in post-processing
if (type !is JKWildCardType
&& (type.nullability == Nullability.Default
&& owner?.safeAs<JKLambdaExpression>()?.functionalType?.type != type
|| type.nullability == Nullability.Nullable)
) {
this.print("?")
}
}
fun renderSymbol(symbol: JKSymbol, owner: JKTreeElement?) {
print(symbolRenderer.renderSymbol(symbol, owner))
}
}
@@ -3,21 +3,22 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * 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.nj2k package org.jetbrains.kotlin.nj2k.printing
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.util.Processor import com.intellij.util.Processor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.nj2k.JKImportStorage
import org.jetbrains.kotlin.nj2k.escaped
import org.jetbrains.kotlin.nj2k.symbols.* import org.jetbrains.kotlin.nj2k.symbols.*
import org.jetbrains.kotlin.nj2k.tree.JKClassAccessExpression import org.jetbrains.kotlin.nj2k.tree.JKClassAccessExpression
import org.jetbrains.kotlin.nj2k.tree.JKQualifiedExpression import org.jetbrains.kotlin.nj2k.tree.JKQualifiedExpression
import org.jetbrains.kotlin.nj2k.tree.JKTreeElement import org.jetbrains.kotlin.nj2k.tree.JKTreeElement
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class JKSymbolRenderer(private val importStorage: JKImportStorage, project: Project) { internal class JKSymbolRenderer(private val importStorage: JKImportStorage, project: Project) {
private val canBeShortenedClassNameCache = CanBeShortenedCache(project) private val canBeShortenedClassNameCache = CanBeShortenedCache(project)
private fun JKSymbol.isFqNameExpected(owner: JKTreeElement?): Boolean { private fun JKSymbol.isFqNameExpected(owner: JKTreeElement?): Boolean {
@@ -0,0 +1,13 @@
/*
* 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.nj2k.printing
import org.jetbrains.kotlin.nj2k.escaped
internal fun String.escapedAsQualifiedName(): String =
split('.')
.map { it.escaped() }
.joinToString(".") { it }
@@ -35,8 +35,13 @@ class JKClass(
override var visibilityElement by child(visibilityElement) override var visibilityElement by child(visibilityElement)
override var modalityElement by child(modalityElement) override var modalityElement by child(modalityElement)
enum class ClassKind { enum class ClassKind(val text: String) {
ANNOTATION, CLASS, ENUM, INTERFACE, OBJECT, COMPANION ANNOTATION("annotation class"),
CLASS("class"),
ENUM("enum class"),
INTERFACE("interface"),
OBJECT("object"),
COMPANION("companion object")
} }
} }