J2K: preserving line breaks between enum members from original code

This commit is contained in:
Valentin Kipyatkov
2015-06-16 12:41:53 +03:00
parent f9afb4f95b
commit fdff2c7153
11 changed files with 91 additions and 71 deletions
+66 -36
View File
@@ -21,9 +21,13 @@ import com.intellij.psi.*
import com.intellij.psi.javadoc.PsiDocComment
import org.jetbrains.kotlin.j2k.ast.CommentsAndSpacesInheritance
import org.jetbrains.kotlin.j2k.ast.Element
import org.jetbrains.kotlin.j2k.ast.SpacesInheritance
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import java.util.*
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.ArrayList
import java.util.HashSet
import java.util.LinkedHashSet
import kotlin.platform.platformName
fun<T> CodeBuilder.append(generators: Collection<() -> T>, separator: String, prefix: String = "", suffix: String = ""): CodeBuilder {
@@ -117,31 +121,25 @@ class CodeBuilder(private val topElement: PsiElement?) {
}
val notInsideElements = HashSet<PsiElement>()
val prefixElements = ArrayList<PsiElement>(1)
var prefix = Prefix.Empty
val postfixElements = ArrayList<PsiElement>(1)
for ((prototype, inheritance) in element.prototypes!!) {
assert(prototype !is PsiComment)
assert(prototype !is PsiWhiteSpace)
if (!topElement.isAncestor(prototype)) continue
prefixElements.collectPrefixElements(prototype, inheritance, notInsideElements)
prefix += collectPrefixElements(prototype, inheritance, notInsideElements)
postfixElements.collectPostfixElements(prototype, inheritance, notInsideElements)
}
commentsAndSpacesUsed.addAll(prefixElements)
commentsAndSpacesUsed.addAll(postfixElements)
for ((i, e) in prefixElements.withIndex()) {
if (i == 0 && e is PsiWhiteSpace) {
val blankLines = e.newLinesCount() - 1
for (_ in 1..blankLines) {
append("\n", false)
}
}
else {
appendCommentOrWhiteSpace(e)
if (prefix.lineBreaksBefore > 0) {
val lineBreaksToAdd = prefix.lineBreaksBefore - builder.trailingLineBreakCount()
for (_ in 1..lineBreaksToAdd) {
append("\n", false)
}
}
prefix.elements.forEach { appendCommentOrWhiteSpace(it) }
element.generateCode(this)
// scan for all comments inside which are not yet used in the text and put them here to not loose any comment from code
@@ -164,29 +162,52 @@ class CodeBuilder(private val topElement: PsiElement?) {
return this
}
private fun MutableList<PsiElement>.collectPrefixElements(element: PsiElement,
inheritance: CommentsAndSpacesInheritance,
notInsideElements: MutableSet<PsiElement>) {
private data class Prefix(val elements: Collection<PsiElement>, val lineBreaksBefore: Int) {
fun plus(other: Prefix) = Prefix(elements + other.elements, Math.max(lineBreaksBefore, other.lineBreaksBefore))
companion object {
val Empty = Prefix(emptyList(), 0)
}
}
private fun collectPrefixElements(
element: PsiElement,
inheritance: CommentsAndSpacesInheritance,
notInsideElements: MutableSet<PsiElement>
): Prefix {
val before = ArrayList<PsiElement>(1).collectCommentsAndSpacesBefore(element)
val atStart = ArrayList<PsiElement>(1).collectCommentsAndSpacesAtStart(element)
notInsideElements.addAll(atStart)
if (!inheritance.blankLinesBefore && !inheritance.commentsBefore) return
if (inheritance.spacesBefore == SpacesInheritance.NONE && !inheritance.commentsBefore) return Prefix.Empty
val firstSpace = before.lastOrNull() as? PsiWhiteSpace
if (!inheritance.commentsBefore) { // take only first whitespace
if (firstSpace != null) {
add(firstSpace)
var lineBreaks = 0
if (firstSpace != null) {
lineBreaks = firstSpace.newLinesCount()
when (inheritance.spacesBefore) {
SpacesInheritance.NONE -> lineBreaks = 0
SpacesInheritance.LINE_BREAKS -> commentsAndSpacesUsed.add(firstSpace)
SpacesInheritance.BLANK_LINES_ONLY -> {
commentsAndSpacesUsed.add(firstSpace)
if (lineBreaks == 1) lineBreaks = 0
}
}
return
}
if (!inheritance.blankLinesBefore && firstSpace != null) {
if (!inheritance.commentsBefore) { // take only whitespace
return Prefix(emptyList(), lineBreaks)
}
if (firstSpace != null) {
before.remove(before.lastIndex)
}
addAll(before.reverse())
addAll(atStart)
val elements = before.reverse() + atStart
commentsAndSpacesUsed.addAll(elements)
return Prefix(elements, lineBreaks)
}
private fun MutableList<PsiElement>.collectPostfixElements(element: PsiElement, inheritance: CommentsAndSpacesInheritance, notInsideElements: MutableSet<PsiElement>) {
@@ -205,6 +226,9 @@ class CodeBuilder(private val topElement: PsiElement?) {
addAll(atEnd.reverse())
addAll(after)
commentsAndSpacesUsed.addAll(atEnd)
commentsAndSpacesUsed.addAll(after)
}
private fun MutableList<PsiElement>.collectCommentsAndSpacesBefore(element: PsiElement): MutableList<PsiElement> {
@@ -253,14 +277,14 @@ class CodeBuilder(private val topElement: PsiElement?) {
private fun MutableList<PsiElement>.collectCommentsAndSpacesAtStart(element: PsiElement): MutableList<PsiElement> {
var child = element.getFirstChild()
while(child != null) {
if (child!!.isCommentOrSpace()) {
if (child !in commentsAndSpacesUsed) add(child!!) else break
if (child.isCommentOrSpace()) {
if (child !in commentsAndSpacesUsed) add(child) else break
}
else if (!child!!.isEmptyElement()) {
collectCommentsAndSpacesAtStart(child!!)
else if (!child.isEmptyElement()) {
collectCommentsAndSpacesAtStart(child)
break
}
child = child!!.getNextSibling()
child = child.getNextSibling()
}
return this
}
@@ -268,14 +292,14 @@ class CodeBuilder(private val topElement: PsiElement?) {
private fun MutableList<PsiElement>.collectCommentsAndSpacesAtEnd(element: PsiElement): MutableList<PsiElement> {
var child = element.getLastChild()
while(child != null) {
if (child!!.isCommentOrSpace()) {
if (child !in commentsAndSpacesUsed) add(child!!) else break
if (child.isCommentOrSpace()) {
if (child !in commentsAndSpacesUsed) add(child) else break
}
else if (!child!!.isEmptyElement()) {
collectCommentsAndSpacesAtEnd(child!!)
else if (!child.isEmptyElement()) {
collectCommentsAndSpacesAtEnd(child)
break
}
child = child!!.getPrevSibling()
child = child.getPrevSibling()
}
return this
}
@@ -289,5 +313,11 @@ class CodeBuilder(private val topElement: PsiElement?) {
private fun PsiWhiteSpace.newLinesCount() = StringUtil.getLineBreakCount(getText()!!)
private fun PsiWhiteSpace.hasNewLines() = StringUtil.containsLineBreak(getText()!!)
private fun CharSequence.trailingLineBreakCount(): Int {
val index = ((length()-1 downTo 0).firstOrNull { val c = this[it]; c != '\n' && c != '\r' } ?: -1) + 1
if (index == length()) return 0
return StringUtil.getLineBreakCount(subSequence(index, length()))
}
}
@@ -214,7 +214,7 @@ class ConstructorConverter(
if (isVal(converter.referenceSearcher, field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
converter.convertAnnotations(parameter) + converter.convertAnnotations(field),
accessModifiers,
default).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
default).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(spacesBefore = SpacesInheritance.NONE))
}
},
correctCodeConverter = { correct() })
@@ -244,7 +244,7 @@ class Converter private constructor(
private fun convertAnnotationType(psiClass: PsiClass): Class {
val paramModifiers = Modifiers(listOf(Modifier.PUBLIC)).assignNoPrototype()
val noBlankLinesInheritance = CommentsAndSpacesInheritance(blankLinesBefore = false)
val noBlankLinesInheritance = CommentsAndSpacesInheritance(spacesBefore = SpacesInheritance.NONE)
val annotationMethods = psiClass.getMethods().filterIsInstance<PsiAnnotationMethod>()
val (methodsNamedValue, otherMethods) = annotationMethods.partition { it.getName() == "value" }
@@ -313,13 +313,14 @@ class Converter private constructor(
}
val name = correction?.identifier ?: field.declarationIdentifier()
val converted = if (field is PsiEnumConstant) {
if (field is PsiEnumConstant) {
val argumentList = field.getArgumentList()
val params = deferredElement { codeConverter ->
ExpressionList(codeConverter.convertExpressions(argumentList?.getExpressions() ?: arrayOf<PsiExpression>())).assignPrototype(argumentList)
}
val body = field.getInitializingClass()?.let { convertAnonymousClassBody(it) }
EnumConstant(name, annotations, modifiers, params, body)
return EnumConstant(name, annotations, modifiers, params, body)
.assignPrototype(field, CommentsAndSpacesInheritance(spacesBefore = SpacesInheritance.LINE_BREAKS))
}
else {
val isVal = isVal(referenceSearcher, field)
@@ -330,7 +331,7 @@ class Converter private constructor(
addUsageProcessing(FieldToPropertyProcessing(field, correction?.name ?: field.getName(), propertyType.isNullable))
Property(name,
return Property(name,
annotations,
modifiers,
propertyType,
@@ -338,9 +339,8 @@ class Converter private constructor(
isVal,
typeToDeclare != null,
shouldGenerateDefaultInitializer(referenceSearcher, field),
if (correction != null) correction.setterAccess else modifiers.accessModifier())
if (correction != null) correction.setterAccess else modifiers.accessModifier()).assignPrototype(field)
}
return converted.assignPrototype(field)
}
public fun variableTypeToDeclare(variable: PsiVariable, specifyAlways: Boolean, canChangeType: Boolean): Type? {
@@ -556,7 +556,7 @@ class Converter private constructor(
public fun convertModifiers(owner: PsiModifierListOwner): Modifiers {
return Modifiers(MODIFIERS_MAP.filter { owner.hasModifierProperty(it.first) }.map { it.second })
.assignPrototype(owner.getModifierList(), CommentsAndSpacesInheritance(blankLinesBefore = false))
.assignPrototype(owner.getModifierList(), CommentsAndSpacesInheritance(spacesBefore = SpacesInheritance.NONE))
}
public fun convertAnonymousClassBody(anonymousClass: PsiAnonymousClass): AnonymousClassBody {
@@ -45,7 +45,7 @@ class ClassBody (
else {
val (constants, otherMembers) = membersFiltered.partition { it is EnumConstant }
builder.append(constants, ",\n")
builder.append(constants, ", ")
if (otherMembers.isNotEmpty() || companionObjectMembers.isNotEmpty()) {
builder.append(";\n")
@@ -43,7 +43,7 @@ class PrimaryConstructor(
// assign prototypes later because we don't know yet whether the body is empty or not
converter.addPostUnfoldDeferredElementsAction {
val inheritance = CommentsAndSpacesInheritance(blankLinesBefore = false, commentsAfter = body!!.isEmpty, commentsInside = body.isEmpty)
val inheritance = CommentsAndSpacesInheritance(spacesBefore = SpacesInheritance.NONE, commentsAfter = body!!.isEmpty, commentsInside = body.isEmpty)
signature.assignPrototypesFrom(this, inheritance)
}
@@ -45,10 +45,16 @@ fun <TElement: Element> TElement.assignPrototypesFrom(element: Element, inherita
data class PrototypeInfo(val element: PsiElement, val commentsAndSpacesInheritance: CommentsAndSpacesInheritance)
data class CommentsAndSpacesInheritance(val blankLinesBefore: Boolean = true,
val commentsBefore: Boolean = true,
val commentsAfter: Boolean = true,
val commentsInside: Boolean = true)
enum class SpacesInheritance {
NONE, BLANK_LINES_ONLY, LINE_BREAKS
}
data class CommentsAndSpacesInheritance(
val spacesBefore: SpacesInheritance = SpacesInheritance.BLANK_LINES_ONLY,
val commentsBefore: Boolean = true,
val commentsAfter: Boolean = true,
val commentsInside: Boolean = true
)
fun Element.canonicalCode(): String {
val builder = CodeBuilder(null)
@@ -2,8 +2,7 @@
annotation class Anon(public val value: String) {
public enum class E {
A,
B
A, B
}
companion object {
@@ -1,7 +1,3 @@
enum class Color private constructor(public val code: Int) {
WHITE(21),
BLACK(22),
RED(23),
YELLOW(24),
BLUE(25)
WHITE(21), BLACK(22), RED(23), YELLOW(24), BLUE(25)
}
+1 -5
View File
@@ -1,9 +1,5 @@
enum class Color {
WHITE,
BLACK,
RED,
YELLOW,
BLUE;
WHITE, BLACK, RED, YELLOW, BLUE;
override fun toString(): String {
return "COLOR"
}
+1 -5
View File
@@ -1,9 +1,5 @@
enum class Color : Runnable {
WHITE,
BLACK,
RED,
YELLOW,
BLUE;
WHITE, BLACK, RED, YELLOW, BLUE;
override fun run() {
println("name()=" + name() + ", toString()=" + toString())
+1 -4
View File
@@ -1,6 +1,3 @@
enum class Coin {
PENNY,
NICKEL,
DIME,
QUARTER
PENNY, NICKEL, DIME, QUARTER
}