Formatter: fix line break between declarations with comment

#KT-12490 Fixed
#KT-35088 Fixed
This commit is contained in:
Dmitry Gridin
2019-11-15 21:57:37 +07:00
parent b6b755506c
commit bfd539d5d1
49 changed files with 191 additions and 12 deletions
@@ -17,7 +17,8 @@ import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.idea.util.requireNode
import org.jetbrains.kotlin.lexer.KtTokens
import java.util.*
import org.jetbrains.kotlin.psi.psiUtil.children
import org.jetbrains.kotlin.psi.stubs.elements.KtModifierListElementType
import kotlin.math.max
fun CommonCodeStyleSettings.createSpaceBeforeRBrace(numSpacesOtherwise: Int, textRange: TextRange): Spacing? {
@@ -103,13 +104,20 @@ class KotlinSpacingBuilder(val commonCodeStyleSettings: CommonCodeStyleSettings,
val lastChild = left.node?.psi?.lastChild
val leftEndsWithComment = lastChild is PsiComment && lastChild.tokenType == KtTokens.EOL_COMMENT
val dependentSpacingRule = DependentSpacingRule(Trigger.HAS_LINE_FEEDS).registerData(Anchor.MIN_LINE_FEEDS, emptyLines + 1)
val textRange = left.node
?.children()
?.firstOrNull { it.elementType !in KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET }
?.startOffset
?.let { TextRange.create(it, left.textRange.endOffset) }
?: left.textRange
spacingBuilderUtil.createLineFeedDependentSpacing(
numSpacesOtherwise,
numSpacesOtherwise,
if (leftEndsWithComment) max(1, numberOfLineFeedsOtherwise) else numberOfLineFeedsOtherwise,
commonCodeStyleSettings.KEEP_LINE_BREAKS,
commonCodeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS,
left.textRange,
textRange,
dependentSpacingRule
)
}
@@ -200,3 +208,8 @@ fun rules(
builder.init()
return builder
}
internal fun ASTNode.startOfDeclaration(): ASTNode? = children().firstOrNull {
val elementType = it.elementType
elementType !is KtModifierListElementType<*> && elementType !in KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
}
@@ -12,6 +12,7 @@ import com.intellij.formatting.SpacingBuilder
import com.intellij.formatting.SpacingBuilder.RuleBuilder
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.codeStyle.CodeStyleSettings
@@ -32,6 +33,8 @@ val MODIFIERS_LIST_ENTRIES = TokenSet.orSet(TokenSet.create(ANNOTATION_ENTRY, AN
val EXTEND_COLON_ELEMENTS =
TokenSet.create(TYPE_CONSTRAINT, CLASS, OBJECT_DECLARATION, TYPE_PARAMETER, ENUM_ENTRY, SECONDARY_CONSTRUCTOR)
val DECLARATIONS = TokenSet.create(PROPERTY, FUN, CLASS, OBJECT_DECLARATION, ENUM_ENTRY, SECONDARY_CONSTRUCTOR, CLASS_INITIALIZER)
fun SpacingBuilder.beforeInside(element: IElementType, tokenSet: TokenSet, spacingFun: RuleBuilder.() -> Unit) {
tokenSet.types.forEach { inType -> beforeInside(element, inType).spacingFun() }
}
@@ -47,9 +50,6 @@ fun createSpacingBuilder(settings: CodeStyleSettings, builderUtil: KotlinSpacing
val kotlinCommonSettings = settings.kotlinCommonSettings
val kotlinCustomSettings = settings.kotlinCustomSettings
return rules(kotlinCommonSettings, builderUtil) {
val DECLARATIONS =
TokenSet.create(PROPERTY, FUN, CLASS, OBJECT_DECLARATION, ENUM_ENTRY, SECONDARY_CONSTRUCTOR, CLASS_INITIALIZER)
simple {
before(FILE_ANNOTATION_LIST).lineBreakInCode()
after(FILE_ANNOTATION_LIST).blankLines(1)
@@ -87,8 +87,21 @@ fun createSpacingBuilder(settings: CodeStyleSettings, builderUtil: KotlinSpacing
null
}
}
inPosition(right = BLOCK_COMMENT).spacing(commentSpacing(0))
inPosition(right = EOL_COMMENT).spacing(commentSpacing(1))
inPosition(leftSet = DECLARATIONS, rightSet = DECLARATIONS).customRule(fun(
_: ASTBlock,
_: ASTBlock,
right: ASTBlock
): Spacing? {
val firstChild = right.node?.firstChildNode as? PsiComment ?: return null
val whiteSpace = firstChild.nextSibling as? PsiWhiteSpace ?: return null
return if (StringUtil.containsLineBreak(firstChild.text) || StringUtil.containsLineBreak(whiteSpace.text)) {
createSpacing(0, minLineFeeds = 2)
} else
null
})
inPosition(left = CLASS, right = CLASS).emptyLinesIfLineBreakInLeft(1)
inPosition(left = CLASS, right = OBJECT_DECLARATION).emptyLinesIfLineBreakInLeft(1)
@@ -5,6 +5,7 @@ open class A() {
class C : A() {
val constant = 42
// Some comment
override val bar: Int
get() = <selection><caret>super.bar</selection>
@@ -33,6 +33,7 @@ class C1M
// -----
// test
object O6
interface T1
@@ -29,6 +29,7 @@ class C1M
// -----
// test
object O6
interface T1
@@ -1,9 +1,30 @@
interface Some {
fun f1()
fun f2()
fun f3()
/* test */
fun f4()
/* test */ fun f5()
/**
* test
* 2
*/
fun f6()
fun f7()
}
abstract class Abstract() {
abstract fun f1()
abstract fun f2()
// test
abstract fun f3()
// test
/* test */ abstract fun f4()
abstract fun f5()
abstract fun f6()
/* test */ abstract fun f7()
}
@@ -1,9 +1,26 @@
interface Some {
fun f1()
fun f2()
fun f3()
/* test */
fun f4()
/* test */ fun f5()
/**
* test
* 2
*/
fun f6()
fun f7()
}
abstract class Abstract() {
abstract fun f1()
abstract fun f2()
// test
abstract fun f3()
// test
/* test */ abstract fun f4()
abstract fun f5()
abstract fun f6()
/* test */ abstract fun f7()
}
@@ -0,0 +1,11 @@
class Some(b: Boolean) {
// Comment.
constructor(b: Int) : this(b == 0)
/**
* test
* 2
*/
constructor(b: String) : this(b.isEmpty())
constructor(b: Long) : this(b == 0L)
}
@@ -0,0 +1,10 @@
class Some(b: Boolean) {
// Comment.
constructor(b: Int) : this(b == 0)
/**
* test
* 2
*/
constructor(b: String) : this(b.isEmpty())
constructor(b: Long) : this(b == 0L)
}
@@ -8,11 +8,13 @@ class C2
// -----
// test
fun f3 = 1
class C3 {}
// -----
/*test*/
fun f4 = 2
class C4
@@ -8,11 +8,13 @@ class C2
// -----
// test
fun f3 = 1
class C3 {}
// -----
/*test*/
fun f4 = 2
class C4
@@ -13,6 +13,7 @@ class E
class F
// test
fun some() = 1
class G
+1
View File
@@ -10,6 +10,7 @@ class E
: Some
class F
// test
fun some() = 1
class G
fun some() = 1
@@ -26,6 +26,7 @@ enum class E7 {
}
enum class E8 {
// test
A, // A
B // B
}
@@ -28,6 +28,7 @@ enum class E7 {
}
enum class E8 {
// test
A, // A
B // B
}
@@ -25,6 +25,7 @@ enum class E7 {
}
enum class E8 {
// test
A, // A
B // B
}
@@ -1,9 +1,9 @@
// No lines
fun f1() {}
val p1 = 1
fun f2() {}
// test
fun f3() = 1
val p2 = 1
fun f4() = 1
@@ -3,6 +3,7 @@ fun f1() {}
val p1 = 1
fun f2() {}
// test
fun f3() = 1
val p2 = 1
fun f4() = 1
@@ -27,6 +27,9 @@ fun f6() = 1
fun f7() = 8
// test
fun f8() = 42
// Two lines between
fun l1() {}
+2
View File
@@ -25,6 +25,8 @@ fun f5() {
fun f6() = 1
fun f7() = 8
// test
fun f8() = 42
// Two lines between
fun l1() {}
@@ -1,3 +1,4 @@
// test
val p1 by Some
val p2 = 1
val p3: Int get() = 3
+1
View File
@@ -1,3 +1,4 @@
// test
val p1 by Some
val p2 = 1
val p3: Int get() = 3
+4 -1
View File
@@ -24,22 +24,26 @@ fun Int.extFun2() {
val fooVal1 = 1
val fooVal2 = 1
//-----------------------
var fooVar1 = 1
var fooVar2 = 2
//-----------------------
private val Int.extVal1 = 122
private
val
Int.extVal: Int = 1
//-----------------------
public var Int.extVar1: Int = 122
public
var
Int.extVar: Int = 1
//-----------------------
public var varWithAccessors1: Int
get() {
@@ -64,7 +68,6 @@ val
//-----------------------
annotation class A1
annotation class A2
private @[A1 A2 A1]
+1 -1
View File
@@ -2,7 +2,6 @@ typealias A = Int
typealias B = Int
//
typealias C = Int
typealias D =
Int
@@ -23,6 +22,7 @@ object O
typealias I = Int
fun foo() {}
//
fun foo() {}
typealias J = Int
@@ -6,6 +6,7 @@
expect class My {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
/**
* Dokka comment: Just does nothing
*/
@@ -7,9 +7,11 @@ expect sealed class My(x: Double) {
object First : My {
override val num: Int
}
class Some(num: Int) : My {
override val num: Int
}
object Best : My {
override val num: Int
override fun isGood(): Boolean
@@ -5,10 +5,13 @@ interface Z<T>
open class A<T : I, U : I, V> {
// INFO: {"checked": "true"}
var foo1: T
// INFO: {"checked": "true"}
var foo2: Z<T>
// INFO: {"checked": "true"}
var foo3: Any
// INFO: {"checked": "true"}
var foo4: Z<Any>
@@ -2,10 +2,13 @@
abstract class A {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true"}
abstract val t: Int
@@ -2,10 +2,13 @@
abstract class A {
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val z: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val t: Int
@@ -6,18 +6,25 @@ interface Z<T>
abstract class A<T: I, U: I, V>(x: T, y: Any?) {
// INFO: {"checked": "true"}
val foo1: T
// INFO: {"checked": "true"}
val foo2: Z<T>
// INFO: {"checked": "true"}
val foo3: Any?
// INFO: {"checked": "true"}
val foo4: Z<Any?>
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo5: T
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo6: Z<T>
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo7: Any?
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val foo8: Z<Any?>
@@ -2,10 +2,13 @@
interface T {
// INFO: {"checked": "true"}
val x: Int
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int
// INFO: {"checked": "true"}
val t: Int
@@ -38,6 +41,7 @@ abstract class B: T {
// INFO: {"checked": "true"}
override val x = 1
// INFO: {"checked": "true"}
override val z: Int by lazy { 3 }
@@ -2,10 +2,13 @@
interface T {
// INFO: {"checked": "true", "toAbstract": "true"}
val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val z: Int
// INFO: {"checked": "true", "toAbstract": "true"}
val t: Int
@@ -1,6 +1,7 @@
open class Temp1 {
// INFO: {"checked": "true"}
private val used: Int = 1
// INFO: {"checked": "true"}
private val using: Int = used + 1
}
@@ -1,12 +1,16 @@
open class A(p: Int) {
// INFO: {"checked": "true"}
val n: Int
// INFO: {"checked": "true"}
val x: Int = 3
// INFO: {"checked": "true"}
val y: Int = 4
// INFO: {"checked": "true"}
val a: Int = 1
// INFO: {"checked": "true"}
val b: Int = x + y
@@ -1,10 +1,13 @@
open class A {
// INFO: {"checked": "true"}
val n: Int
// INFO: {"checked": "true"}
val x: Int = 3
// INFO: {"checked": "true"}
val a: Int = 1
// INFO: {"checked": "true"}
val b: Int = x + y
}
@@ -1,5 +1,6 @@
open class A {
open fun bar() = 1
// INFO: {"checked": "true"}
fun foo() = bar()
}
@@ -5,10 +5,13 @@ abstract class A {
abstract class B : A() {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
// INFO: {"checked": "true"}
abstract val t: Int
@@ -32,14 +35,18 @@ abstract class B : A() {
class C : A() {
val t = 1
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
@@ -1,8 +1,10 @@
abstract class A {
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val z: Int
@@ -14,10 +16,13 @@ abstract class A {
abstract class B : A() {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
override val z: Int by lazy { 3 }
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val t: Int
@@ -41,14 +46,18 @@ abstract class B : A() {
class C : A() {
val t = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val x = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val y: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
override val z: Int by lazy { 3 }
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
@@ -7,6 +7,7 @@ open class A<T> {
class B<S> : A<Z<S>>() {
// INFO: {"checked": "true"}
val t1: Z<S>
// INFO: {"checked": "true"}
val t2: Z<Z<S>>
@@ -28,6 +29,7 @@ class B<S> : A<Z<S>>() {
class C<K> : A<B<K>>() {
// INFO: {"checked": "true"}
val t1: B<K>
// INFO: {"checked": "true"}
val t2: Z<B<K>>
@@ -5,6 +5,7 @@ interface I {
abstract class A : I {
// INFO: {"checked": "true"}
val x: Int get() = 2
// INFO: {"checked": "true"}
abstract val y: Int
@@ -22,10 +23,12 @@ abstract class A : I {
class B : I {
val y = 1
// INFO: {"checked": "true"}
val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
@@ -38,6 +41,7 @@ class B : I {
interface J : I {
// INFO: {"checked": "true"}
val x: Int get() = 2
// INFO: {"checked": "true"}
val y: Int
@@ -55,10 +59,12 @@ interface J : I {
interface K : I {
val y: Int get() = 1
// INFO: {"checked": "true"}
val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
@@ -10,6 +10,7 @@ interface I {
abstract class A : I {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val y: Int
@@ -27,10 +28,12 @@ abstract class A : I {
class B : I {
val y = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
@@ -43,6 +46,7 @@ class B : I {
interface J : I {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int
@@ -60,10 +64,12 @@ interface J : I {
interface K : I {
val y: Int get() = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
@@ -270,6 +270,11 @@ public class FormatterTestGenerated extends AbstractFormatterTest {
runTest("idea/testData/formatter/EmptyLineBetweeAbstractFunctions.after.kt");
}
@TestMetadata("EmptyLineBetweeSecondaryConstructors.after.kt")
public void testEmptyLineBetweeSecondaryConstructors() throws Exception {
runTest("idea/testData/formatter/EmptyLineBetweeSecondaryConstructors.after.kt");
}
@TestMetadata("EmptyLineBetweenClassAndFunction.after.kt")
public void testEmptyLineBetweenClassAndFunction() throws Exception {
runTest("idea/testData/formatter/EmptyLineBetweenClassAndFunction.after.kt");
+1
View File
@@ -14,6 +14,7 @@ internal class A {
private /*it's private*/ val field = 0
/*it's public*/ fun foo(s: String?): Char {}
protected /*it's protected*/ fun foo(c: Char) {}
/**
* Method description.
* Multi-line method description.
+1
View File
@@ -8,6 +8,7 @@ internal class A(// comment for field2 setter
// Comment for field1 getter
// Comment for field1
var field1 = 0
// comment for field3 setter
// comment for field3 getter
// comment before field3
@@ -5,6 +5,7 @@ internal class A {
var field1 = 0
@Transient
var field2 = 1
// Should work even for bad modifiers
@Strictfp
var field3 = 2.0
@@ -6,7 +6,6 @@ internal class F {
//c3
//c4
fun f2() {}
var i = 0
fun f3() {} //c5
}
@@ -2,7 +2,6 @@ internal class F {
//c3
//c4
fun f2() {}
fun f3() {}
fun f4() {}
@@ -10,8 +9,8 @@ internal class F {
//c1
/*c2*/
fun f1() {}
var i = 0
//c5
fun f5() {} //c6
}
@@ -6,7 +6,6 @@ internal object F {
//c3
//c4
fun f2() {}
var i = 0
fun f3() {} //c5
}
+1
View File
@@ -49,6 +49,7 @@ internal class C {
*/
//comments
var l = 0
/*two*/ /*comments*/ /*line*/
var z = 0
}