From b0b93f05f4da60141baae3dd0f6ff403d0b3bceb Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 9 Jul 2023 13:56:53 +0200 Subject: [PATCH] Fix indent of explicit constructor (#2118) * Fix indent of explicit constructor (also see #2115) * Fix error in `hasNewLineInClosedRange` and `noNewLineInClosedRange` --- .../rule/engine/core/api/ASTNodeExtension.kt | 32 +++++--- .../engine/core/api/ASTNodeExtensionTest.kt | 71 +++++++++++++---- .../engine/internal/rules/InternalRule.kt | 10 +-- .../ktlint/rule/engine/api/KtLintTest.kt | 14 ++-- .../engine/internal/RuleProviderSorterTest.kt | 8 +- .../InternalRuleProvidersFilterTest.kt | 8 +- .../rulefilter/RunAfterRuleFilterTest.kt | 8 +- .../ktlint/ruleset/standard/StandardRule.kt | 10 +-- .../ruleset/standard/rules/IndentationRule.kt | 77 ++++++++++++++++++- .../standard/rules/IndentationRuleTest.kt | 61 +++++++++++---- 10 files changed, 226 insertions(+), 73 deletions(-) diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt index 9e1aa32d11..201e7dac43 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt @@ -333,10 +333,7 @@ private fun String.replaceTabAndNewline(): String = replace("\t", "\\t").replace public fun hasNewLineInClosedRange( from: ASTNode, to: ASTNode, -): Boolean = - from.isWhiteSpaceWithNewline() || - leavesInOpenRange(from, to).any { it.textContains('\n') } || - to.isWhiteSpaceWithNewline() +): Boolean = leavesInClosedRange(from, to).any { it.textContains('\n') } /** * Verifies that no leaf contains a newline in the closed range [from] - [to]. Also, the boundary nodes [from] and [to] @@ -345,10 +342,7 @@ public fun hasNewLineInClosedRange( public fun noNewLineInClosedRange( from: ASTNode, to: ASTNode, -): Boolean = - !from.isWhiteSpaceWithNewline() && - noNewLineInOpenRange(from, to) && - !to.isWhiteSpaceWithNewline() +): Boolean = leavesInClosedRange(from, to).none { it.textContains('\n') } /** * Verifies that no leaf contains a newline in the open range [from] - [to]. This means that the boundary nodes are excluded from the range @@ -373,7 +367,27 @@ public fun leavesInOpenRange( ): Sequence = from .leaves() - .takeWhile { it != to && it != to.firstChildNode } + .takeWhile { it != to && it != to.lastChildLeafOrSelf() } + +/** + * Creates a sequence of leaf nodes in the closed range [from] - [to]. This means that the boundary nodes are included from the range in + * case they would happen to be a leaf node. In case [from] is a [CompositeElement] than the first leaf node in the sequence is the first + * leaf node in this [CompositeElement]. In case [to] is a [CompositeElement] than the last node in the sequence is the last leaf node of + * this [CompositeElement]. + */ +public fun leavesInClosedRange( + from: ASTNode, + to: ASTNode, +): Sequence { + val stopAtLeaf = + to + .lastChildLeafOrSelf() + .nextLeaf() + return from + .firstChildLeafOrSelf() + .leavesIncludingSelf() + .takeWhile { it != stopAtLeaf } +} public fun ASTNode.isValOrVarKeyword(): Boolean = elementType == VAL_KEYWORD || elementType == VAR_KEYWORD || elementType == VARARG_KEYWORD diff --git a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt index c9c141d88f..314059d452 100644 --- a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt +++ b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt @@ -52,6 +52,36 @@ class ASTNodeExtensionTest { ) } + @Test + fun `Given an enum class body then get all leaves in the closed range of the class body`() { + val code = + """ + enum class Shape { + FOO, FOOBAR, BAR + } + """.trimIndent() + val enumClassBody = code.transformAst(::toEnumClassBodySequence) + + val actual = + leavesInClosedRange(enumClassBody.first(), enumClassBody.last()) + .map { it.text } + .toList() + + assertThat(actual).containsExactly( + "{", + "\n ", + "FOO", + ",", + " ", + "FOOBAR", + ",", + " ", + "BAR", + "\n", + "}", + ) + } + @Nested inner class NoNewLineInOpenRange { @Test @@ -111,14 +141,9 @@ class ASTNodeExtensionTest { fun `Given an enum class with no whitespace leaf containing a newline between the first and last enum entry`() { val code = """ - enum class Shape { - FOO, FOOBAR, BAR - } + enum class Shape { FOO, FOOBAR, BAR } """.trimIndent() - val enumEntries = - code - .transformAst(::toEnumClassBodySequence) - .filter { it.elementType == ENUM_ENTRY } + val enumEntries = code.transformAst(::toEnumClassBodySequence) val actual = hasNewLineInClosedRange(enumEntries.first(), enumEntries.last()) @@ -129,12 +154,16 @@ class ASTNodeExtensionTest { fun `Given a range of nodes starting with a whitespace leaf containing a newline but other whitespace leaves not containing a newline`() { val code = """ - enum class Shape { - FOO, FOOBAR, BAR } // Malformed on purpose for test + enum class Shape + { FOO, FOOBAR, BAR } // Malformed on purpose for test """.trimIndent() - val enumClassBody = code.transformAst(::toEnumClassBodySequence) + val enumClass = code.transformAst(::toEnumClassSequence) - val actual = hasNewLineInClosedRange(enumClassBody.first(), enumClassBody.last()) + val actual = + hasNewLineInClosedRange( + enumClass.first { it.isWhiteSpaceWithNewline() }, + enumClass.last(), + ) assertThat(actual).isTrue } @@ -163,9 +192,13 @@ class ASTNodeExtensionTest { enum class Shape { FOO, FOOBAR, BAR } // Malformed on purpose for test """.trimIndent() - val enumClassBody = code.transformAst(::toEnumClassBodySequence) + val enumBodyClass = code.transformAst(::toEnumClassBodySequence) - val actual = hasNewLineInClosedRange(enumClassBody.first(), enumClassBody.last()) + val actual = + hasNewLineInClosedRange( + enumBodyClass.first(), + enumBodyClass.last { it.isWhiteSpaceWithNewline() }, + ) assertThat(actual).isTrue } @@ -601,15 +634,21 @@ class ASTNodeExtensionTest { ?.children() .orEmpty() + private fun toEnumClassSequence(fileASTNode: FileASTNode) = + fileASTNode + .findChildByType(CLASS) + ?.children() + .orEmpty() + /** * A dummy rule for testing. Optionally the rule can be created with a lambda to be executed for each node visited. */ private open class DummyRule( val block: (node: ASTNode) -> Unit = {}, ) : Rule( - ruleId = RuleId("test:dummy-rule"), - about = About(), - ) { + ruleId = RuleId("test:dummy-rule"), + about = About(), + ) { override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt index 963e302aec..cfc4f12f1d 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/InternalRule.kt @@ -19,8 +19,8 @@ public open class InternalRule internal constructor( override val visitorModifiers: Set = emptySet(), override val usesEditorConfigProperties: Set> = emptySet(), ) : Rule( - ruleId = RuleId("internal:$id"), - visitorModifiers = visitorModifiers, - usesEditorConfigProperties = usesEditorConfigProperties, - about = INTERNAL_RULE_ABOUT, -) + ruleId = RuleId("internal:$id"), + visitorModifiers = visitorModifiers, + usesEditorConfigProperties = usesEditorConfigProperties, + about = INTERNAL_RULE_ABOUT, + ) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index fb5ad9a401..cee497ac86 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -473,9 +473,9 @@ class KtLintTest { private open class DummyRule( val block: (node: ASTNode) -> Unit = {}, ) : Rule( - ruleId = RuleId("test:dummy"), - about = About(), -) { + ruleId = RuleId("test:dummy"), + about = About(), + ) { override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, @@ -536,10 +536,10 @@ private class SimpleTestRule( private val stopTraversalInAfterVisitChildNodes: (ASTNode) -> Boolean = { false }, private val stopTraversalInAfterLastNode: Boolean = false, ) : Rule( - ruleId = ruleId, - about = About(), - visitorModifiers, -) { + ruleId = ruleId, + about = About(), + visitorModifiers, + ) { override fun beforeFirstNode(editorConfig: EditorConfig) { ruleExecutionCalls.add(RuleExecutionCall(ruleId, BEFORE_FIRST)) if (stopTraversalInBeforeFirstNode) { diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt index e9db9eb535..aa1a2379a2 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleProviderSorterTest.kt @@ -450,10 +450,10 @@ class RuleProviderSorterTest { ruleId: RuleId, visitorModifiers: Set = emptySet(), ) : Rule( - ruleId = ruleId, - about = About(), - visitorModifiers, - ) { + ruleId = ruleId, + about = About(), + visitorModifiers, + ) { constructor(ruleId: RuleId, visitorModifier: VisitorModifier) : this(ruleId, setOf(visitorModifier)) override fun beforeVisitChildNodes( diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt index 433fe7dce2..686b463830 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/InternalRuleProvidersFilterTest.kt @@ -43,10 +43,10 @@ class InternalRuleProvidersFilterTest { ruleId: RuleId, visitorModifiers: Set = emptySet(), ) : Rule( - ruleId = ruleId, - about = About(), - visitorModifiers, - ) { + ruleId = ruleId, + about = About(), + visitorModifiers, + ) { override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt index fc02cce9eb..6e0ca177ec 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rulefilter/RunAfterRuleFilterTest.kt @@ -363,10 +363,10 @@ class RunAfterRuleFilterTest { ruleId: RuleId, visitorModifiers: Set = emptySet(), ) : Rule( - ruleId = ruleId, - about = About(), - visitorModifiers, - ) { + ruleId = ruleId, + about = About(), + visitorModifiers, + ) { constructor(ruleId: RuleId, visitorModifier: VisitorModifier) : this(ruleId, setOf(visitorModifier)) override fun beforeVisitChildNodes( diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt index a52312a115..19b68659ba 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRule.kt @@ -21,8 +21,8 @@ public open class StandardRule internal constructor( override val visitorModifiers: Set = emptySet(), override val usesEditorConfigProperties: Set> = emptySet(), ) : Rule( - ruleId = RuleId("${RuleSetId.STANDARD.value}:$id"), - visitorModifiers = visitorModifiers, - usesEditorConfigProperties = usesEditorConfigProperties, - about = STANDARD_RULE_ABOUT, -) + ruleId = RuleId("${RuleSetId.STANDARD.value}:$id"), + visitorModifiers = visitorModifiers, + usesEditorConfigProperties = usesEditorConfigProperties, + about = STANDARD_RULE_ABOUT, + ) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt index 71e7a66898..2949883301 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRule.kt @@ -17,6 +17,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLOSING_QUOTE import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON +import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONDITION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONSTRUCTOR_DELEGATION_CALL import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONSTRUCTOR_KEYWORD @@ -206,7 +207,6 @@ public class IndentationRule : node.elementType == CLASS_BODY || node.elementType == CONTEXT_RECEIVER_LIST || node.elementType == LONG_STRING_TEMPLATE_ENTRY || - node.elementType == SUPER_TYPE_CALL_ENTRY || node.elementType == STRING_TEMPLATE || node.elementType == VALUE_ARGUMENT_LIST -> startIndentContext( @@ -214,6 +214,31 @@ public class IndentationRule : lastChildIndent = "", ) + node.elementType == SUPER_TYPE_CALL_ENTRY -> { + if (codeStyle == ktlint_official && node.isPartOfClassWithAMultilinePrimaryConstructor()) { + // Contrary to the default IntelliJ IDEA formatter, indent the super type call entry so that it looks better in case it + // is followed by another super type: + // class Foo( + // val bar1: Bar, + // val bar2: Bar, + // ) : FooBar( + // bar1, + // bar2 + // ), + // BarFoo, + startIndentContext( + fromAstNode = node, + childIndent = indentConfig.indent, + activated = true, + ) + } else { + startIndentContext( + fromAstNode = node, + lastChildIndent = "", + ) + } + } + node.elementType == VALUE_ARGUMENT -> visitValueArgument(node) @@ -342,6 +367,11 @@ public class IndentationRule : } } + private fun ASTNode.isPartOfClassWithAMultilinePrimaryConstructor() = + parent { it.elementType == CLASS } + ?.findChildByType(PRIMARY_CONSTRUCTOR) + ?.textContains('\n') == true + private fun visitValueArgument(node: ASTNode) { if (codeStyle == ktlint_official) { // Deviate from standard IntelliJ IDEA formatting to allow formatting below: @@ -608,16 +638,53 @@ public class IndentationRule : val primaryConstructor = node.findChildByType(PRIMARY_CONSTRUCTOR) val containsConstructorKeyword = primaryConstructor?.findChildByType(CONSTRUCTOR_KEYWORD) != null if (codeStyle == ktlint_official && primaryConstructor != null && containsConstructorKeyword) { - // Indent both constructor and super type list + // Contrary to the default IntelliJ IDEA formatter, ident both constructor and super type list as follows: + // class Foo + // @Bar1 @Bar2 + // constructor( + // foo1: Foo1, + // foo2: Foo2, + // ) : Foobar1( + // "foobar1", + // "foobar2", + // ), + // FooBar2, + val superTypeList = node.findChildByType(SUPER_TYPE_LIST) nextToAstNode = startIndentContext( fromAstNode = primaryConstructor.getPrecedingLeadingCommentsAndWhitespaces(), toAstNode = - node - .findChildByType(SUPER_TYPE_LIST) + superTypeList ?.lastChildLeafOrSelf() ?: nextToAstNode, ).prevCodeLeaf() + + superTypeList + ?.findChildByType(COMMA) + ?.let { comma -> + // In case of a multiline primary constructor the first super type is merged with the closing parenthesis of the + // constructor. The start of the super type list does not activate the indent because it is not preceded by a newline. + // To fix this, the super type entries on the next line (e.g. after the comma) have to be double indented. + // Allow: + // class Foo( + // val bar1: Bar, + // val bar2: Bar, + // ) : FooBar( + // bar1, + // bar2 + // ), + // BarFoo1 + val prevCodeLeaf = + startIndentContext( + fromAstNode = comma, + toAstNode = superTypeList.lastChildLeafOrSelf(), + childIndent = indentConfig.indent.repeat(2), + ).prevCodeLeaf() + startIndentContext( + fromAstNode = primaryConstructor.getPrecedingLeadingCommentsAndWhitespaces(), + toAstNode = prevCodeLeaf, + ).prevCodeLeaf() + } } else { node .findChildByType(SUPER_TYPE_LIST) @@ -922,6 +989,7 @@ public class IndentationRule : childIndent: String = indentConfig.indent, firstChildIndent: String = childIndent, lastChildIndent: String = childIndent, + activated: Boolean = false, ): IndentContext = IndentContext( fromASTNode = fromAstNode, @@ -930,6 +998,7 @@ public class IndentationRule : firstChildIndent = firstChildIndent, childIndent = childIndent, lastChildIndent = lastChildIndent, + activated = activated, ).also { newIndentContext -> LOGGER.trace { val nodeIndentLevel = indentConfig.indentLevelFrom(newIndentContext.nodeIndent) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt index 64966e8204..95cbce439b 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleTest.kt @@ -5021,9 +5021,9 @@ internal class IndentationRuleTest { foo1: Foo1, foo2: Foo2, ) : Foobar( - "foobar1", - "foobar2", - ) { + "foobar1", + "foobar2", + ) { fun foo() = "foo" } """.trimIndent() @@ -5035,9 +5035,9 @@ internal class IndentationRuleTest { LintViolation(4, 1, "Unexpected indentation (4) (should be 8)"), LintViolation(5, 1, "Unexpected indentation (4) (should be 8)"), LintViolation(6, 1, "Unexpected indentation (0) (should be 4)"), - LintViolation(7, 1, "Unexpected indentation (4) (should be 8)"), - LintViolation(8, 1, "Unexpected indentation (4) (should be 8)"), - LintViolation(9, 1, "Unexpected indentation (0) (should be 4)"), + LintViolation(7, 1, "Unexpected indentation (4) (should be 12)"), + LintViolation(8, 1, "Unexpected indentation (4) (should be 12)"), + LintViolation(9, 1, "Unexpected indentation (0) (should be 8)"), ).isFormattedAs(formattedCode) } @@ -5053,7 +5053,9 @@ internal class IndentationRuleTest { ) : Foobar1( "foobar1", "foobar2", - ), FooBar2 { + ), + FooBar2, + FooBar3 { fun foo() = "foo" } """.trimIndent() @@ -5065,9 +5067,11 @@ internal class IndentationRuleTest { foo1: Foo1, foo2: Foo2, ) : Foobar1( - "foobar1", - "foobar2", - ), FooBar2 { + "foobar1", + "foobar2", + ), + FooBar2, + FooBar3 { fun foo() = "foo" } """.trimIndent() @@ -5079,9 +5083,11 @@ internal class IndentationRuleTest { LintViolation(4, 1, "Unexpected indentation (4) (should be 8)"), LintViolation(5, 1, "Unexpected indentation (4) (should be 8)"), LintViolation(6, 1, "Unexpected indentation (0) (should be 4)"), - LintViolation(7, 1, "Unexpected indentation (4) (should be 8)"), - LintViolation(8, 1, "Unexpected indentation (4) (should be 8)"), - LintViolation(9, 1, "Unexpected indentation (0) (should be 4)"), + LintViolation(7, 1, "Unexpected indentation (4) (should be 12)"), + LintViolation(8, 1, "Unexpected indentation (4) (should be 12)"), + LintViolation(9, 1, "Unexpected indentation (0) (should be 8)"), + LintViolation(10, 1, "Unexpected indentation (4) (should be 8)"), + LintViolation(11, 1, "Unexpected indentation (4) (should be 8)"), ).isFormattedAs(formattedCode) } @@ -5089,10 +5095,20 @@ internal class IndentationRuleTest { fun `Issue 2115 - Given a class without an explicit constructor and with a long super type list then do not indent the class body`() { val code = """ - class Foo( + class Foo1 : + FooBar( + "bar1", + "bar2", + ) { + // body + } + class Foo2( val bar1: Bar, val bar2: Bar, - ) : FooBar(bar1, bar2), + ) : FooBar( + bar1, + bar2 + ), BarFoo1, BarFoo2 { // body @@ -5102,6 +5118,21 @@ internal class IndentationRuleTest { .withEditorConfigOverride(CODE_STYLE_PROPERTY to ktlint_official) .hasNoLintViolations() } + + @Test + fun `Issue 2115 - xxx`() { + val code = + """ + class Foo(bar: Bar) : + FooBar( + "foo", + bar, + ) + """.trimIndent() + indentationRuleAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to ktlint_official) + .hasNoLintViolations() + } } @Test