Skip to content

Commit

Permalink
Fix indentation of delegated super type call entry (pinterest#1305)
Browse files Browse the repository at this point in the history
* Fix indentation of delegated super type call entry

Forces the BY keyword in a super type call entry to be indented when preceded by a new line

Closes pinterest#1210

* Fix negative indent level after formatting a supertype delegate

Co-authored-by: Paul Dingemans <pdingemans@bol.com>
  • Loading branch information
paul-dingemans and Paul Dingemans committed Dec 20, 2021
1 parent 7525d3f commit 3866e09
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Do not remove import which is used as markdown link in KDoc only (`no-unused-imports`) ([#1282](https://github.com/pinterest/ktlint/issues/1282))
- Fix indentation of secondary constructor (`indent`) ([#1222](https://github.com/pinterest/ktlint/issues/1222))
- Fix alignment of arrow when trailing comma is missing in when entry (`trailing-comma`) ([#1312](https://github.com/pinterest/ktlint/issues/1312))
- Fix indent of delegated super type entry (`indent`) ([#1210](https://github.com/pinterest/ktlint/issues/1210))

### Changed
- Update Kotlin version to `1.6.0` release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,12 +518,16 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast {
val pairedLeft = n.pairedLeft()
if (prevBlockLine != blockLine && !pairedLeft.isAfterLambdaArgumentOnSameLine()) {
expectedIndent--
debug { "--on(${n.elementType}) -> $expectedIndent" }

val byKeywordOnSameLine = pairedLeft?.prevLeafOnSameLine(BY_KEYWORD)
if (byKeywordOnSameLine != null &&
byKeywordOnSameLine.prevLeaf()?.isWhiteSpaceWithNewline() == true &&
n.leavesOnSameLine(forward = true).all { it.isWhiteSpace() || it.isPartOfComment() }
) expectedIndent--
debug { "--${n.text} -> $expectedIndent" }
) {
expectedIndent--
debug { "--on same line as by keyword ${n.text} -> $expectedIndent" }
}
}
}
LT ->
Expand Down Expand Up @@ -641,6 +645,7 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast {
visitWhiteSpace(n, autoCorrect, emit, editorConfig)
if (ctx.localAdj != 0) {
expectedIndent += ctx.localAdj
debug { "++${ctx.localAdj} on whitespace containing new line (${n.elementType}) -> $expectedIndent" }
ctx.localAdj = 0
}
} else if (n.isPartOf(KDOC)) {
Expand Down Expand Up @@ -734,8 +739,15 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast {
}

private fun adjustExpectedIndentAfterSuperTypeList(n: ASTNode) {
expectedIndent--
debug { "--after(${n.elementType}) -> $expectedIndent" }
val byKeywordLeaf = n
.findChildByType(DELEGATED_SUPER_TYPE_ENTRY)
?.findChildByType(BY_KEYWORD)
if (n.prevLeaf()?.textContains('\n') == true && byKeywordLeaf?.prevLeaf().isWhiteSpaceWithNewline()) {
Unit
} else {
expectedIndent--
debug { "--after(${n.elementType}) -> $expectedIndent" }
}
}

private fun adjustExpectedIndentInsideSuperTypeCall(n: ASTNode, ctx: IndentContext) {
Expand Down Expand Up @@ -997,13 +1009,17 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast {
// instead of expected
// val i: Int
// by lazy { 1 }
nextLeafElementType == BY_KEYWORD ->
if (node.isPartOf(DELEGATED_SUPER_TYPE_ENTRY)) {
nextLeafElementType == BY_KEYWORD -> {
if (node.isPartOf(DELEGATED_SUPER_TYPE_ENTRY) &&
node.treeParent.prevLeaf()?.textContains('\n') == true
) {
0
} else {
expectedIndent++
debug { "++whitespace followed by BY keyword -> $expectedIndent" }
1
}
}
// IDEA quirk:
// var value: DataClass =
// DataClass("too long line")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -952,23 +952,42 @@ internal class IndentationRuleTest {
}

@Test
fun `lint delegation 2`() {
assertThat(
IndentationRule().lint(
"""
interface Foo
fun `lint and format delegation 2`() {
val code =
"""
interface Foo
class Bar(a: Int, b: Int, c: Int) : Foo
class Bar(a: Int, b: Int, c: Int) : Foo
class Test2 : Foo
by Bar(
a = 1,
b = 2,
c = 3
)
""".trimIndent()
val formattedCode =
"""
interface Foo
class Test2 : Foo
class Bar(a: Int, b: Int, c: Int) : Foo
class Test2 : Foo
by Bar(
a = 1,
b = 2,
c = 3
)
""".trimIndent()
)
).isEmpty()
""".trimIndent()

assertThat(IndentationRule().lint(code)).containsExactly(
LintError(6, 1, "indent", "Unexpected indentation (0) (should be 4)"),
LintError(7, 1, "indent", "Unexpected indentation (4) (should be 8)"),
LintError(8, 1, "indent", "Unexpected indentation (4) (should be 8)"),
LintError(9, 1, "indent", "Unexpected indentation (4) (should be 8)"),
LintError(10, 1, "indent", "Unexpected indentation (0) (should be 4)"),
)
assertThat(IndentationRule().format(code)).isEqualTo(formattedCode)
}

@Test
Expand Down Expand Up @@ -1556,6 +1575,67 @@ internal class IndentationRuleTest {
)
}

@Test
fun `Issue 1210 - format supertype delegate`() {
val code =
"""
object ApplicationComponentFactory : ApplicationComponent.Factory
by DaggerApplicationComponent.factory()
""".trimIndent()
val formattedCode =
"""
object ApplicationComponentFactory : ApplicationComponent.Factory
by DaggerApplicationComponent.factory()
""".trimIndent()
assertThat(IndentationRule().lint(code)).containsExactly(
LintError(2, 1, "indent", "Unexpected indentation (0) (should be 4)")
)
assertThat(IndentationRule().format(code)).isEqualTo(formattedCode)
}

@Test
fun `Issue 1210 - format of statements after supertype delegated entry 1`() {
val code =
"""
object Issue1210 : ApplicationComponent.Factory
by DaggerApplicationComponent.factory()
// The next line ensures that the fix regarding the expectedIndex due to alignment of "by" keyword in
// class above, is still in place. Without this fix, the expectedIndex would hold a negative value,
// resulting in the formatting to crash on the next line.
val bar = 1
""".trimIndent()

assertThat(IndentationRule().lint(code)).isEmpty()
assertThat(IndentationRule().format(code)).isEqualTo(code)
}

@Test
fun `Issue 1210 - format of statements after supertype delegated entry 2`() {
val code =
"""
interface Foo
class Bar(a: Int, b: Int, c: Int) : Foo
class Test4 :
Foo
by Bar(
a = 1,
b = 2,
c = 3
)
// The next line ensures that the fix regarding the expectedIndex due to alignment of "by" keyword in
// class above, is still in place. Without this fix, the expectedIndex would hold a negative value,
// resulting in the formatting to crash on the next line.
val bar = 1
""".trimIndent()

assertThat(IndentationRule().lint(code)).isEmpty()
assertThat(IndentationRule().format(code)).isEqualTo(code)
}

private companion object {
const val MULTILINE_STRING_QUOTE = "${'"'}${'"'}${'"'}"
const val TAB = "${'\t'}"
Expand Down

0 comments on commit 3866e09

Please sign in to comment.