From 0acbc72f4ba951225cf90b001b1a122f7cd1e982 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 11 Dec 2021 18:01:24 +0100 Subject: [PATCH 1/2] 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 #1210 --- CHANGELOG.md | 1 + .../ruleset/standard/IndentationRule.kt | 5 +- .../ruleset/standard/IndentationRuleTest.kt | 57 +++++++++++++++---- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7139e7ca4d..750a6a33d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed - Fix false positive in rule spacing-between-declarations-with-annotations ([#1281](https://github.com/pinterest/ktlint/issues/1281)) - Fix NoSuchElementException for property accessor (`trailing-comma`) ([#1280](https://github.com/pinterest/ktlint/issues/1280)) +- 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 diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt index b50dbf294d..69486ba666 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt @@ -955,13 +955,14 @@ 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.text != "\n") { 0 } else { expectedIndent++ 1 } + } // IDEA quirk: // var value: DataClass = // DataClass("too long line") diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt index d42d1036ca..c04f545e32 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt @@ -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 + class Test2 : Foo + by Bar( + a = 1, + b = 2, + c = 3 + ) + """.trimIndent() + val formattedCode = + """ + interface 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 @@ -1343,6 +1362,24 @@ internal class IndentationRuleTest { assertThat(IndentationRule().format(codeTabs, INDENT_STYLE_TABS)).isEqualTo(codeTabs) } + @Test + fun `Issue 1210 - format by`() { + 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) + } + private companion object { const val MULTILINE_STRING_QUOTE = "${'"'}${'"'}${'"'}" const val TAB = "${'\t'}" From 46132e5bf3657df1bc6212add2740e0149eb4bbb Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 12 Dec 2021 17:40:13 +0100 Subject: [PATCH 2/2] Fix negative indent level after formatting a supertype delegate --- .../ruleset/standard/IndentationRule.kt | 25 +++++++-- .../ruleset/standard/IndentationRuleTest.kt | 51 +++++++++++++++++-- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt index 69486ba666..491238b37f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt @@ -516,12 +516,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 -> @@ -637,6 +641,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)) { @@ -730,8 +735,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) { @@ -956,10 +968,13 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast { // val i: Int // by lazy { 1 } nextLeafElementType == BY_KEYWORD -> { - if (node.isPartOf(DELEGATED_SUPER_TYPE_ENTRY) && node.text != "\n") { + if (node.isPartOf(DELEGATED_SUPER_TYPE_ENTRY) && + node.treeParent.prevLeaf()?.textContains('\n') == true + ) { 0 } else { expectedIndent++ + debug { "++whitespace followed by BY keyword -> $expectedIndent" } 1 } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt index c04f545e32..fbdf796106 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt @@ -1363,7 +1363,7 @@ internal class IndentationRuleTest { } @Test - fun `Issue 1210 - format by`() { + fun `Issue 1210 - format supertype delegate`() { val code = """ object ApplicationComponentFactory : ApplicationComponent.Factory @@ -1374,12 +1374,55 @@ internal class IndentationRuleTest { 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().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'}"