From 156c60c9a322cb23e12b0a6a76493ae2959a9708 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Mon, 20 Mar 2023 20:28:55 +0100 Subject: [PATCH 1/2] Add more types of expressions that need to be wrapped in case it is a multiline expression --- .../rules/MultilineExpressionWrapping.kt | 72 +- .../rules/MultilineExpressionWrappingTest.kt | 687 +++++++++++++++--- 2 files changed, 634 insertions(+), 125 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt index 01aa0f9f6d..63b55bc3d5 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt @@ -1,13 +1,26 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.ElementType +import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARRAY_ACCESS_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_WITH_TYPE +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN +import com.pinterest.ktlint.rule.engine.core.api.ElementType.IF +import com.pinterest.ktlint.rule.engine.core.api.ElementType.IS_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_LITERAL +import com.pinterest.ktlint.rule.engine.core.api.ElementType.PREFIX_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.REFERENCE_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR import com.pinterest.ktlint.rule.engine.core.api.ElementType.SAFE_ACCESS_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TRY +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.Companion.DEFAULT_INDENT_CONFIG import com.pinterest.ktlint.rule.engine.core.api.Rule @@ -18,9 +31,11 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPE import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.leavesIncludingSelf +import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -73,19 +88,23 @@ public class MultilineExpressionWrapping : emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, autoCorrect: Boolean, ) { - if (node.containsWhitespaceWithNewline() && node.isValueInAnAssignment()) { + if (node.containsWhitespaceWithNewline() && node.needToWrapMultilineExpression()) { node .prevLeaf { !it.isPartOfComment() } .let { prevLeaf -> - val expectedIndent = - node - .treeParent - .indent() - .plus(indentConfig.indent) - if (prevLeaf != null && prevLeaf.text != expectedIndent) { + if (prevLeaf != null && !prevLeaf.textContains('\n')) { emit(node.startOffset, "A multiline expression should start on a new line", true) if (autoCorrect) { - node.upsertWhitespaceBeforeMe(expectedIndent) + node.upsertWhitespaceBeforeMe( + node + .treeParent + .indent() + .plus(indentConfig.indent) + ) + node + .lastChildLeafOrSelf() + .nextLeaf { !it.isWhiteSpace() && it.elementType != COMMA } + ?.upsertWhitespaceBeforeMe(node.treeParent.indent()) } } } @@ -97,9 +116,19 @@ public class MultilineExpressionWrapping : return firstChildLeafOrSelf() .leavesIncludingSelf() .takeWhile { it != lastLeaf } - .any { it.isWhiteSpaceWithNewline() } + .any { it.isWhiteSpaceWithNewline() || it.isRegularStringPartWithNewline() } } + private fun ASTNode.isRegularStringPartWithNewline() = + elementType == ElementType.REGULAR_STRING_PART && + text.startsWith("\n") + + private fun ASTNode.needToWrapMultilineExpression() = + isValueInAnAssignment() || + isLambdaExpression() || + isValueArgument() || + isAfterArrow() + private fun ASTNode.isValueInAnAssignment() = null != prevCodeSibling() @@ -115,13 +144,38 @@ public class MultilineExpressionWrapping : ?.prevCodeLeaf() ?.takeIf { it.elementType == RPAR } + private fun ASTNode.isLambdaExpression() = + null != + treeParent + .takeIf { + // Function literals in lambda expression have an implicit block (no LBRACE and RBRACE). So only wrap when the node is + // the first node in the block + it.elementType == BLOCK && it.firstChildNode == this + }?.treeParent + ?.takeIf { it.elementType == ElementType.FUNCTION_LITERAL } + ?.treeParent + ?.takeIf { it.elementType == ElementType.LAMBDA_EXPRESSION } + + private fun ASTNode.isValueArgument() = treeParent.elementType == VALUE_ARGUMENT + + private fun ASTNode.isAfterArrow() = prevCodeLeaf()?.elementType == ARROW + private companion object { + // Based on https://kotlinlang.org/spec/expressions.html#expressions val CHAINABLE_EXPRESSION = setOf( + ARRAY_ACCESS_EXPRESSION, + BINARY_WITH_TYPE, CALL_EXPRESSION, DOT_QUALIFIED_EXPRESSION, + IF, + IS_EXPRESSION, + OBJECT_LITERAL, + PREFIX_EXPRESSION, REFERENCE_EXPRESSION, SAFE_ACCESS_EXPRESSION, + TRY, + WHEN, ) } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingTest.kt index 068cb3d2c1..3c904ede40 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrappingTest.kt @@ -4,214 +4,561 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERT import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.test.KtLintAssertThat import com.pinterest.ktlint.test.LintViolation +import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class MultilineExpressionWrappingTest { private val multilineExpressionWrappingAssertThat = KtLintAssertThat.assertThatRule { MultilineExpressionWrapping() } + @Nested + inner class `Given a function call using a named parameter` { + @Test + fun `Given value argument for a named parameter in a function with a multiline dot qualified expression on the same line as the assignment`() { + val code = + """ + val foo = foo( + parameterName = "The quick brown fox " + .plus("jumps ") + .plus("over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + parameterName = + "The quick brown fox " + .plus("jumps ") + .plus("over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(2, 21, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline safe access expression on the same line as the assignment`() { + val code = + """ + val foo = foo( + parameterName = theQuickBrownFoxOrNull + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + parameterName = + theQuickBrownFoxOrNull + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(2, 21, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline combination of a safe access expression and a call expression on the same line as the assignment`() { + val code = + """ + val foo = foo( + parameterName = theQuickBrownFoxOrNull() + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + parameterName = + theQuickBrownFoxOrNull() + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(2, 21, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline combination of a dot qualified and a safe access expression on the same line as the assignment`() { + val code = + """ + val foo = foo( + parameterName = "The quick brown fox " + .takeIf { it.jumps } + ?.plus("jumps over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + parameterName = + "The quick brown fox " + .takeIf { it.jumps } + ?.plus("jumps over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(2, 21, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline call expression on the same line as the assignment`() { + val code = + """ + val foo = foo( + parameterName = bar( + "bar" + ) + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + parameterName = + bar( + "bar" + ) + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(2, 21, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + } + + @Nested + inner class `Given a function call using an unnamed parameter` { + @Test + fun `Given value argument in a function with a multiline binary expression on the same line as the assignment`() { + val code = + """ + val foo = foo("The quick brown fox " + + "jumps " + + "over the lazy dog", + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + "The quick brown fox " + + "jumps " + + "over the lazy dog", + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(1, 15, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline safe access expression on the same line as the assignment`() { + val code = + """ + val foo = foo(theQuickBrownFoxOrNull + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + theQuickBrownFoxOrNull + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(1, 15, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline combination of a safe access expression and a call expression on the same line as the assignment`() { + val code = + """ + val foo = foo(theQuickBrownFoxOrNull() + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + theQuickBrownFoxOrNull() + ?.plus("jumps ") + ?.plus("over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(1, 15, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline combination of a dot qualified and a safe access expression on the same line as the assignment`() { + val code = + """ + val foo = foo("The quick brown fox " + .takeIf { it.jumps } + ?.plus("jumps over the lazy dog"), + ) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + "The quick brown fox " + .takeIf { it.jumps } + ?.plus("jumps over the lazy dog"), + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(1, 15, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given value argument in a function with a multiline call expression on the same line as the assignment`() { + val code = + """ + val foo = foo(bar( + "bar" + )) + """.trimIndent() + val formattedCode = + """ + val foo = + foo( + bar( + "bar" + ) + ) + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .addAdditionalRuleProvider { ParameterWrappingRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolations( + LintViolation(1, 11, "A multiline expression should start on a new line"), + LintViolation(1, 15, "A multiline expression should start on a new line"), + ).isFormattedAs(formattedCode) + } + } + @Test - fun `Given value argument in a function with a multiline dot qualified expression on the same line as the assignment`() { + fun `Given a return statement with a multiline expression then do not reformat as it would result in a compilation error`() { val code = """ - val foo = foo( - parameterName = "The quick brown fox " - .plus("jumps ") - .plus("over the lazy dog"), + fun foo() { + return bar( + "bar" + ) + } + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasNoLintViolations() + } + + @Test + fun `Given a function with a multiline body expression`() { + val code = + """ + fun foo() = bar( + "bar" ) """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - "The quick brown fox " - .plus("jumps ") - .plus("over the lazy dog"), + fun foo() = + bar( + "bar" ) """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(1, 13, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given value argument in a function with a multiline binary expression on the same line as the assignment`() { + fun `Given a function with a multiline signature without a return type but with a multiline expression body starting on same line as closing parenthesis of function`() { val code = """ - val foo = foo( - parameterName = "The quick brown fox " + - "jumps " + - "over the lazy dog", + fun foo( + foobar: String + ) = bar( + foobar ) """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasNoLintViolations() + } + + @Test + fun `Given a function with a multiline lambda expression containing a binary expression`() { + val code = + """ + val string: String + by lazy { "The quick brown fox " + + "jumps " + + "over the lazy dog" + } + """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - "The quick brown fox " + - "jumps " + - "over the lazy dog", - ) + val string: String + by lazy { + "The quick brown fox " + + "jumps " + + "over the lazy dog" + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(2, 15, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given value argument in a function with a multiline safe access expression on the same line as the assignment`() { + fun `Given a function with a lambda expression containing a multiline string template`() { val code = """ - val foo = foo( - parameterName = theQuickBrownFoxOrNull - ?.plus("jumps ") - ?.plus("over the lazy dog"), - ) + val string: String + by lazy { ${MULTILINE_STRING_QUOTE}The quick brown fox + jumps + over the lazy dog$MULTILINE_STRING_QUOTE.trimIndent() + } """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - theQuickBrownFoxOrNull - ?.plus("jumps ") - ?.plus("over the lazy dog"), - ) + val string: String + by lazy { + ${MULTILINE_STRING_QUOTE}The quick brown fox + jumps + over the lazy dog$MULTILINE_STRING_QUOTE.trimIndent() + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(2, 15, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given value argument in a function with a multiline combination of a safe access expression and a call expression on the same line as the assignment`() { + fun `Given a function with a multiline lambda expression`() { val code = """ - val foo = foo( - parameterName = theQuickBrownFoxOrNull() - ?.plus("jumps ") - ?.plus("over the lazy dog"), - ) + val string = + listOf("The quick brown fox", "jumps", "over the lazy dog") + .map { it + .lowercase() + .substringAfter("o") + } """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - theQuickBrownFoxOrNull() - ?.plus("jumps ") - ?.plus("over the lazy dog"), - ) + val string = + listOf("The quick brown fox", "jumps", "over the lazy dog") + .map { + it + .lowercase() + .substringAfter("o") + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(3, 16, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given value argument in a function with a multiline combination of a dot qualified and a safe access expression on the same line as the assignment`() { + fun `Given a multiline string template in a function parameter to a new line`() { val code = """ - val foo = foo( - parameterName = "The quick brown fox " - .takeIf { it.jumps } - ?.plus("jumps over the lazy dog"), - ) + fun someFunction() { + println($MULTILINE_STRING_QUOTE + The quick brown fox + jumps over the lazy dog + $MULTILINE_STRING_QUOTE.trimIndent()) + } """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - "The quick brown fox " - .takeIf { it.jumps } - ?.plus("jumps over the lazy dog"), + fun someFunction() { + println( + $MULTILINE_STRING_QUOTE + The quick brown fox + jumps over the lazy dog + $MULTILINE_STRING_QUOTE.trimIndent() ) + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(2, 13, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given value argument in a function with a multiline call expression on the same line as the assignment`() { + fun `Given a multiline string template after an arrow`() { val code = """ - val foo = foo( - parameterName = bar( - "bar" - ) - ) + fun foo(bar: String) = + when (bar) { + "bar bar bar bar bar bar bar bar bar" -> $MULTILINE_STRING_QUOTE + The quick brown fox + jumps over the lazy dog + $MULTILINE_STRING_QUOTE.trimIndent() + else -> "" + } """.trimIndent() val formattedCode = """ - val foo = - foo( - parameterName = - bar( - "bar" - ) - ) + fun foo(bar: String) = + when (bar) { + "bar bar bar bar bar bar bar bar bar" -> + $MULTILINE_STRING_QUOTE + The quick brown fox + jumps over the lazy dog + $MULTILINE_STRING_QUOTE.trimIndent() + else -> "" + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasLintViolations( - LintViolation(1, 11, "A multiline expression should start on a new line"), - LintViolation(2, 21, "A multiline expression should start on a new line"), - ).isFormattedAs(formattedCode) + .hasLintViolation(3, 50, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given a return statement with a multiline expression then do not reformat as it would result in a compilation error`() { + fun `Move a multiline when statement as part of an assignment`() { val code = """ - fun foo() { - return bar( - "bar" - ) + fun foo(bar: String) = when (bar) { + "bar" -> true + else -> false } """.trimIndent() + val formattedCode = + """ + fun foo(bar: String) = + when (bar) { + "bar" -> true + else -> false + } + """.trimIndent() multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) - .hasNoLintViolations() + .hasLintViolation(1, 24, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) } @Test - fun `Given a function with a multiline body expression`() { + fun `Move a multiline if statement as part of an assignment`() { val code = """ - fun foo() = bar( + fun foo(bar: Boolean) = if (bar) { "bar" - ) + } else { + "foo" + } """.trimIndent() val formattedCode = """ - fun foo() = - bar( + fun foo(bar: Boolean) = + if (bar) { "bar" - ) + } else { + "foo" + } + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 25, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Move a multiline try catch as part of an assignment`() { + val code = + """ + fun foo() = try { + // do something that might cause an exception + } catch(e: Exception) { + // handle exception + } + """.trimIndent() + val formattedCode = + """ + fun foo() = + try { + // do something that might cause an exception + } catch(e: Exception) { + // handle exception + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } @@ -221,14 +568,122 @@ class MultilineExpressionWrappingTest { } @Test - fun `Given a function with a multiline signature without a return type but with a multiline expression body starting on same line as closing parenthesis of function`() { + fun `Move a multiline is-expression as part of an assignment`() { val code = """ - fun foo( - foobar: String - ) = bar( - foobar - ) + fun foo(any: Any) = any is + Foo + """.trimIndent() + // TODO: Fix formatting by indent rule + val formattedCode = + """ + fun foo(any: Any) = + any is + Foo + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 21, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Move a multiline binary with type as part of an assignment`() { + val code = + """ + fun foo(any: Any) = any as + Foo + """.trimIndent() + val formattedCode = + """ + fun foo(any: Any) = + any as + Foo + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 21, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Move a multiline prefix expression as part of an assignment`() { + val code = + """ + fun foo(any: Int) = ++ + 42 + """.trimIndent() + // TODO: Fix formatting by indent rule + val formattedCode = + """ + fun foo(any: Int) = + ++ + 42 + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 21, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Move a multiline array access expression as part of an assignment`() { + val code = + """ + fun foo(any: Array) = any[ + 42 + ] + """.trimIndent() + val formattedCode = + """ + fun foo(any: Array) = + any[ + 42 + ] + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 31, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Move a multiline object literal as part of an assignment`() { + val code = + """ + fun foo() = object : + Foo() {} + """.trimIndent() + val formattedCode = + """ + fun foo() = + object : + Foo() {} + """.trimIndent() + multilineExpressionWrappingAssertThat(code) + .addAdditionalRuleProvider { IndentationRule() } + .withEditorConfigOverride(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official) + .hasLintViolation(1, 13, "A multiline expression should start on a new line") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a lambda expression with a multiline expression starting on a new line then do not report a violation`() { + val code = + """ + val foo = + listOf("foo") + .let { bar -> + if (fooBar > 42) { + "foo" + } else { + "bar" + } + } """.trimIndent() multilineExpressionWrappingAssertThat(code) .addAdditionalRuleProvider { IndentationRule() } From 9899acd4622f7a027fc3d96f63656fc82807d318 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Tue, 21 Mar 2023 11:40:44 +0100 Subject: [PATCH 2/2] Fix lint violations --- build-logic/build.gradle.kts | 15 ++-- .../cli/reporter/format/FormatReporter.kt | 45 +++++----- .../cli/reporter/plain/PlainReporter.kt | 11 +-- .../ktlint/cli/internal/FileUtils.kt | 73 ++++++++-------- .../ktlint/cli/internal/GitHookInstaller.kt | 40 ++++----- .../ktlint/cli/internal/KtlintCommandLine.kt | 84 ++++++++++--------- .../ktlint/cli/internal/ReporterAggregator.kt | 21 ++--- .../ktlint/cli/CommandLineTestRunner.kt | 21 +++-- .../rule/engine/core/api/IndentConfig.kt | 20 +++-- .../engine/core/api/ASTNodeExtensionTest.kt | 5 +- .../engine/internal/KotlinPsiFileFactory.kt | 12 +-- .../engine/internal/RuleExecutionContext.kt | 12 +-- .../rule/engine/internal/SuppressHandler.kt | 11 +-- .../engine/internal/EditorConfigLoaderTest.kt | 11 +-- .../ruleset/standard/rules/AnnotationRule.kt | 12 +-- .../standard/rules/AnnotationSpacingRule.kt | 7 +- .../rules/ArgumentListWrappingRule.kt | 12 +-- .../standard/rules/FunctionSignatureRule.kt | 11 +-- .../standard/rules/ImportOrderingRule.kt | 32 +++---- .../ruleset/standard/rules/IndentationRule.kt | 40 ++++----- .../standard/rules/ModifierListSpacingRule.kt | 11 +-- .../rules/MultilineExpressionWrapping.kt | 2 +- .../standard/rules/NoTrailingSpacesRule.kt | 27 +++--- .../standard/rules/NoUnusedImportsRule.kt | 11 +-- .../standard/rules/NoWildcardImportsRule.kt | 23 ++--- .../rules/ParameterListWrappingRule.kt | 23 ++--- .../rules/SpacingAroundDoubleColonRule.kt | 24 +++--- .../standard/rules/SpacingAroundParensRule.kt | 54 ++++++------ .../standard/rules/StringTemplateRule.kt | 11 +-- .../rules/TrailingCommaOnCallSiteRule.kt | 66 ++++++++------- .../TrailingCommaOnDeclarationSiteRule.kt | 20 +++-- .../ruleset/standard/rules/WrappingRule.kt | 11 +-- .../ruleset/testtooling/DumpASTRule.kt | 27 +++--- .../pinterest/ktlint/test/RuleExtension.kt | 22 ++--- 34 files changed, 432 insertions(+), 395 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 778c507377..b5e85d2187 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -7,13 +7,14 @@ repositories { } dependencies { - val kotlinPlugin = if (project.hasProperty("kotlinDev")) { - // Pass '-PkotlinDev' to command line to enable kotlin-in-development version - logger.warn("Enabling kotlin dev version!") - libs.kotlin.plugin.dev - } else { - libs.kotlin.plugin - } + val kotlinPlugin = + if (project.hasProperty("kotlinDev")) { + // Pass '-PkotlinDev' to command line to enable kotlin-in-development version + logger.warn("Enabling kotlin dev version!") + libs.kotlin.plugin.dev + } else { + libs.kotlin.plugin + } implementation(kotlinPlugin) implementation(libs.dokka) } diff --git a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt index 70868deebf..98400cf7d2 100644 --- a/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt +++ b/ktlint-cli-reporter-format/src/main/kotlin/com/pinterest/ktlint/cli/reporter/format/FormatReporter.kt @@ -38,28 +38,29 @@ public class FormatReporter( override fun after(file: String) { val canNotBeAutocorrected = countCanNotBeAutoCorrected.getOrDefault(file, 0) - val result = when { - canNotBeAutocorrected == 1 -> - if (format) { - "Format not completed (1 violation needs manual fixing)" - } else { - "Format required (1 violation needs manual fixing)" - } - canNotBeAutocorrected > 1 -> - if (format) { - "Format not completed ($canNotBeAutocorrected violations need manual fixing)" - } else { - "Format required ($canNotBeAutocorrected violations need manual fixing)" - } - countAutoCorrectPossibleOrDone.getOrDefault(file, 0) > 0 -> - if (format) { - "Format completed (all violations have been fixed)" - } else { - "Format required (all violations can be autocorrected)" - } - else -> - "Format not needed (no violations found)" - } + val result = + when { + canNotBeAutocorrected == 1 -> + if (format) { + "Format not completed (1 violation needs manual fixing)" + } else { + "Format required (1 violation needs manual fixing)" + } + canNotBeAutocorrected > 1 -> + if (format) { + "Format not completed ($canNotBeAutocorrected violations need manual fixing)" + } else { + "Format required ($canNotBeAutocorrected violations need manual fixing)" + } + countAutoCorrectPossibleOrDone.getOrDefault(file, 0) > 0 -> + if (format) { + "Format completed (all violations have been fixed)" + } else { + "Format required (all violations can be autocorrected)" + } + else -> + "Format not needed (no violations found)" + } out.println( "${colorFileName(file)}${":".colored()} $result", ) diff --git a/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporter.kt b/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporter.kt index 74e7f4111f..f12bbb4e83 100644 --- a/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporter.kt +++ b/ktlint-cli-reporter-plain/src/main/kotlin/com/pinterest/ktlint/cli/reporter/plain/PlainReporter.kt @@ -53,11 +53,12 @@ public class PlainReporter( val errList = acc[file] ?: return out.println(colorFileName(file)) for ((line, col, ruleId, detail) in errList) { - val column = if (pad) { - String.format("%-3s", col) - } else { - col - } + val column = + if (pad) { + String.format("%-3s", col) + } else { + col + } out.println( " $line${":$column".colored()} $detail ${"($ruleId)".colored()}", ) diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt index 6968365012..d90bcb3aee 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/FileUtils.kt @@ -178,46 +178,47 @@ private fun FileSystem.toGlob( path: String, rootDir: Path, ): List { - val negation = if (path.startsWith(NEGATION_PREFIX)) { - NEGATION_PREFIX - } else { - "" - } + val negation = + if (path.startsWith(NEGATION_PREFIX)) { + NEGATION_PREFIX + } else { + "" + } val pathWithoutNegationPrefix = path .removePrefix(NEGATION_PREFIX) - val expandedPatterns = try { - val resolvedPath = - rootDir - .resolve(pathWithoutNegationPrefix) - .normalize() - if (resolvedPath.isDirectory()) { - resolvedPath - .expandPathToDefaultPatterns() - .also { - LOGGER.trace { "Expanding resolved directory path '$resolvedPath' to patterns: [$it]" } - } - } else { - resolvedPath - .pathString - .expandDoubleStarPatterns() - .also { - LOGGER.trace { "Expanding resolved path '$resolvedPath` to patterns: [$it]" } - } - } - } catch (e: InvalidPathException) { - if (onWindowsOS) { - // Windows throws an exception when passing a wildcard (*) to Path#resolve. - pathWithoutNegationPrefix - .expandDoubleStarPatterns() - .also { - LOGGER.trace { "On WindowsOS: expanding unresolved path '$pathWithoutNegationPrefix` to patterns: [$it]" } - } - } else { - emptyList() + val expandedPatterns = + try { + val resolvedPath = + rootDir + .resolve(pathWithoutNegationPrefix) + .normalize() + if (resolvedPath.isDirectory()) { + resolvedPath + .expandPathToDefaultPatterns() + .also { + LOGGER.trace { "Expanding resolved directory path '$resolvedPath' to patterns: [$it]" } + } + } else { + resolvedPath + .pathString + .expandDoubleStarPatterns() + .also { + LOGGER.trace { "Expanding resolved path '$resolvedPath` to patterns: [$it]" } + } + } + } catch (e: InvalidPathException) { + if (onWindowsOS) { + // Windows throws an exception when passing a wildcard (*) to Path#resolve. + pathWithoutNegationPrefix + .expandDoubleStarPatterns() + .also { + LOGGER.trace { "On WindowsOS: expanding unresolved path '$pathWithoutNegationPrefix` to patterns: [$it]" } + } + } else { + emptyList() + } } - } - return expandedPatterns .map { originalPattern -> if (onWindowsOS) { diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/GitHookInstaller.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/GitHookInstaller.kt index 9313c4d047..3a2d74b474 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/GitHookInstaller.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/GitHookInstaller.kt @@ -16,13 +16,13 @@ internal object GitHookInstaller { gitHookName: String, hookContentProvider: () -> ByteArray, ) { - val gitHooksDir = try { - resolveGitHooksDir() - } catch (e: IOException) { - System.err.println(e.message) - exitKtLintProcess(1) - } - + val gitHooksDir = + try { + resolveGitHooksDir() + } catch (e: IOException) { + System.err.println(e.message) + exitKtLintProcess(1) + } val gitHookFile = gitHooksDir.resolve(gitHookName) val hookContent = hookContentProvider() @@ -54,19 +54,19 @@ internal object GitHookInstaller { // Try to find the .git directory automatically, falling back to `./.git` private fun getGitDir(): File { - val gitDir = try { - val root = - Runtime.getRuntime().exec("git rev-parse --show-toplevel") - .inputStream - .bufferedReader() - .readText() - .trim() - - File(root).resolve(".git") - } catch (_: IOException) { - File(".git") - } - + val gitDir = + try { + val root = + Runtime.getRuntime().exec("git rev-parse --show-toplevel") + .inputStream + .bufferedReader() + .readText() + .trim() + + File(root).resolve(".git") + } catch (_: IOException) { + File(".git") + } if (!gitDir.isDirectory) { throw IOException(".git directory not found. Are you sure you are inside project directory?") } diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt index 406c5d9520..743b9112d0 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt @@ -296,12 +296,12 @@ internal class KtlintCommandLine { isInvokedFromCli = true, ) - val baseline = if (stdin || baselinePath.isBlank()) { - Baseline(status = Baseline.Status.DISABLED) - } else { - loadBaseline(baselinePath) - } - + val baseline = + if (stdin || baselinePath.isBlank()) { + Baseline(status = Baseline.Status.DISABLED) + } else { + loadBaseline(baselinePath) + } val aggregatedReporter = ReporterAggregator( baseline, @@ -484,11 +484,12 @@ internal class KtlintCommandLine { lintError .detail .applyIf(corrected) { "$this (cannot be auto-corrected)" }, - status = if (corrected) { - FORMAT_IS_AUTOCORRECTED - } else { - LINT_CAN_NOT_BE_AUTOCORRECTED - }, + status = + if (corrected) { + FORMAT_IS_AUTOCORRECTED + } else { + LINT_CAN_NOT_BE_AUTOCORRECTED + }, ) if (baselineLintErrors.doesNotContain(ktlintCliError)) { ktlintCliErrors.add(ktlintCliError) @@ -543,11 +544,12 @@ internal class KtlintCommandLine { col = lintError.col, ruleId = lintError.ruleId.value, detail = lintError.detail, - status = if (lintError.canBeAutoCorrected) { - LINT_CAN_BE_AUTOCORRECTED - } else { - LINT_CAN_NOT_BE_AUTOCORRECTED - }, + status = + if (lintError.canBeAutoCorrected) { + LINT_CAN_BE_AUTOCORRECTED + } else { + LINT_CAN_NOT_BE_AUTOCORRECTED + }, ) if (baselineLintErrors.doesNotContain(ktlintCliError)) { ktlintCliErrors.add(ktlintCliError) @@ -588,11 +590,12 @@ internal class KtlintCommandLine { status = KOTLIN_PARSE_EXCEPTION, ) is KtLintRuleException -> { - val codeSource = if (code.isStdIn) { - "code" - } else { - "file '${code.fileName}'" - } + val codeSource = + if (code.isStdIn) { + "code" + } else { + "file '${code.fileName}'" + } logger.debug("Internal Error (${e.ruleId}) in $codeSource at position '${e.line}:${e.col}", e) KtlintCliError( line = e.line, @@ -646,30 +649,31 @@ internal class KtlintCommandLine { cb: (T) -> Unit, numberOfThreads: Int = Runtime.getRuntime().availableProcessors(), ) { - val pill = object : Future { - override fun isDone(): Boolean { - throw UnsupportedOperationException() - } + val pill = + object : Future { + override fun isDone(): Boolean { + throw UnsupportedOperationException() + } - override fun get( - timeout: Long, - unit: TimeUnit, - ): T { - throw UnsupportedOperationException() - } + override fun get( + timeout: Long, + unit: TimeUnit, + ): T { + throw UnsupportedOperationException() + } - override fun get(): T { - throw UnsupportedOperationException() - } + override fun get(): T { + throw UnsupportedOperationException() + } - override fun cancel(mayInterruptIfRunning: Boolean): Boolean { - throw UnsupportedOperationException() - } + override fun cancel(mayInterruptIfRunning: Boolean): Boolean { + throw UnsupportedOperationException() + } - override fun isCancelled(): Boolean { - throw UnsupportedOperationException() + override fun isCancelled(): Boolean { + throw UnsupportedOperationException() + } } - } val q = ArrayBlockingQueue>(numberOfThreads) val producer = thread(start = true) { diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt index 664831fd93..ac56161667 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt @@ -136,19 +136,20 @@ internal class ReporterAggregator( "Initializing \"${reporterConfiguration.id}\" reporter with ${reporterConfiguration.additionalConfig}" + (reporterConfiguration.output?.let { ", output=$it" } ?: "") } - val stream = when { - reporterConfiguration.output != null -> { - File(reporterConfiguration.output).parentFile?.mkdirsOrFail(); PrintStream(reporterConfiguration.output, "UTF-8") - } + val stream = + when { + reporterConfiguration.output != null -> { + File(reporterConfiguration.output).parentFile?.mkdirsOrFail(); PrintStream(reporterConfiguration.output, "UTF-8") + } - stdin -> { - System.err - } + stdin -> { + System.err + } - else -> { - System.out + else -> { + System.out + } } - } return get(stream, reporterConfiguration.additionalConfig) .let { reporterV2 -> if (reporterConfiguration.output != null) { diff --git a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt index 0f1c613ba7..a6961e6205 100644 --- a/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt +++ b/ktlint-cli/src/test/kotlin/com/pinterest/ktlint/cli/CommandLineTestRunner.kt @@ -184,17 +184,16 @@ class CommandLineTestRunner(private val tempDir: Path) { private fun ProcessBuilder.prependPathWithJavaBinHome() { val environment = environment() - val pathKey = when { - /* - * On Windows, environment keys are case-insensitive, which is not - * handled by the JVM. - */ - isWindows() -> environment.keys.firstOrNull { key -> - key.equals(PATH, ignoreCase = true) - } ?: PATH - - else -> PATH - } + val pathKey = + when { + isWindows() -> { + // On Windows, environment keys are case-insensitive, which is not handled by the JVM + environment.keys.firstOrNull { key -> + key.equals(PATH, ignoreCase = true) + } ?: PATH + } + else -> PATH + } environment[pathKey] = "$JAVA_HOME_BIN_DIR${File.pathSeparator}${OsEnvironment()[PATH]}" } diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt index 7674cf03c7..dee2821dfd 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/IndentConfig.kt @@ -36,10 +36,11 @@ public data class IndentConfig( */ tabWidth: Int, ) : this( - indentStyle = when (indentStyle) { - PropertyType.IndentStyleValue.tab -> TAB - PropertyType.IndentStyleValue.space -> SPACE - }, + indentStyle = + when (indentStyle) { + PropertyType.IndentStyleValue.tab -> TAB + PropertyType.IndentStyleValue.space -> SPACE + }, tabWidth = tabWidth, ) @@ -104,11 +105,12 @@ public data class IndentConfig( private fun getTextAfterLastNewLine(text: String): String { val index = text.indexOfLast { it == '\n' } - val indent = if (index == -1) { - text - } else { - text.substring(index + 1, text.length) - } + val indent = + if (index == -1) { + text + } else { + text.substring(index + 1, text.length) + } return indent } 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 f4cec0bbb1..931c14ad90 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 @@ -88,13 +88,14 @@ class ASTNodeExtensionTest { @Test fun `Given a range of nodes not starting or ending with a whitespace leaf containing a newline but containing another whitespace leaf containing a newline`() { - val code = """ + val code = + """ enum class Shape { FOO, FOOBAR, BAR } - """.trimIndent() + """.trimIndent() val enumClassBody = code.transformAst(::toEnumClassBodySequence) val actual = hasNewLineInClosedRange(enumClassBody.first(), enumClassBody.last()) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt index 1a7d99c5bc..52b7224050 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt @@ -56,12 +56,12 @@ internal fun initPsiFileFactory(isFromCli: Boolean): PsiFileFactory { val disposable = Disposer.newDisposable() try { - val project = KotlinCoreEnvironment.createForProduction( - disposable, - compilerConfiguration, - EnvironmentConfigFiles.JVM_CONFIG_FILES, - ).project as MockProject - + val project = + KotlinCoreEnvironment.createForProduction( + disposable, + compilerConfiguration, + EnvironmentConfigFiles.JVM_CONFIG_FILES, + ).project as MockProject project.enableASTMutations() return PsiFileFactory.getInstance(project) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt index 8bc0171cd7..3006f3aec3 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/RuleExecutionContext.kt @@ -149,12 +149,12 @@ internal class RuleExecutionContext private constructor( } else { "File.kt" } - val psiFile = psiFileFactory.createFileFromText( - psiFileName, - KotlinLanguage.INSTANCE, - normalizedText, - ) as KtFile - + val psiFile = + psiFileFactory.createFileFromText( + psiFileName, + KotlinLanguage.INSTANCE, + normalizedText, + ) as KtFile psiFile .findErrorElement() ?.let { errorElement -> diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt index ae2af9425f..80ccbdec49 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/SuppressHandler.kt @@ -19,11 +19,12 @@ internal class SuppressHandler( ruleId, ) val autoCorrect = this.autoCorrect && !suppress - val emit = if (suppress) { - SUPPRESS_EMIT - } else { - this.emit - } + val emit = + if (suppress) { + SUPPRESS_EMIT + } else { + this.emit + } function(autoCorrect, emit) } diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/EditorConfigLoaderTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/EditorConfigLoaderTest.kt index 6e3c460d85..a3a2af0424 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/EditorConfigLoaderTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/EditorConfigLoaderTest.kt @@ -546,11 +546,12 @@ internal class EditorConfigLoaderTest { private fun EditorConfig.convertToPropertyValues(): List = map { - val value = if (it.isUnset) { - "unset" - } else { - it.sourceValue - } + val value = + if (it.isUnset) { + "unset" + } else { + it.sourceValue + } "${it.name} = $value" }.toList() diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt index 14ff65b29b..c53c0afb28 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationRule.kt @@ -353,12 +353,12 @@ public class AnnotationRule : } private fun ASTNode.findAnnotatedConstruct(): ASTNode { - val astNode = if (treeParent.elementType == MODIFIER_LIST) { - treeParent - } else { - this - } - + val astNode = + if (treeParent.elementType == MODIFIER_LIST) { + treeParent + } else { + this + } return checkNotNull( astNode.lastChildLeafOrSelf().nextCodeLeaf(), ) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt index 71613d0f29..f45e0bff74 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/AnnotationSpacingRule.kt @@ -146,9 +146,10 @@ public class AnnotationSpacingRule : StandardRule("annotation-spacing") { } private fun removeExtraLineBreaks(node: ASTNode) { - val next = node.nextSibling { - it.isWhiteSpaceWithNewline() - } as? LeafPsiElement + val next = + node.nextSibling { + it.isWhiteSpaceWithNewline() + } as? LeafPsiElement if (next != null) { rawReplaceExtraLineBreaks(next) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt index f611036703..55e8ce2d22 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt @@ -173,12 +173,12 @@ public class ArgumentListWrappingRule : // ... LPAR // VALUE_PARAMETER... // RPAR - val intendedIndent = if (child.elementType == ElementType.VALUE_ARGUMENT) { - indent + editorConfigIndent.indent - } else { - indent - } - + val intendedIndent = + if (child.elementType == ElementType.VALUE_ARGUMENT) { + indent + editorConfigIndent.indent + } else { + indent + } val prevLeaf = child.prevWhiteSpaceWithNewLine() ?: child.prevLeaf() if (prevLeaf is PsiWhiteSpace) { if (prevLeaf.getText().contains("\n")) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt index 77bb32e0c2..33bb82c912 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt @@ -125,11 +125,12 @@ public class FunctionSignatureRule : } private fun ASTNode.getFirstCodeChild(): ASTNode? { - val funNode = if (elementType == FUN_KEYWORD) { - this.treeParent - } else { - this - } + val funNode = + if (elementType == FUN_KEYWORD) { + this.treeParent + } else { + this + } funNode ?.findChildByType(MODIFIER_LIST) ?.let { modifierList -> diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt index 24bb3e227b..3876514190 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ImportOrderingRule.kt @@ -212,10 +212,11 @@ public class ImportOrderingRule : private val EDITOR_CONFIG_PROPERTY_PARSER: (String, String?) -> PropertyType.PropertyValue> = { _, value -> when { - value.isNullOrBlank() -> PropertyType.PropertyValue.invalid( - value, - "Import layout must contain at least one entry of a wildcard symbol (*)", - ) + value.isNullOrBlank() -> + PropertyType.PropertyValue.invalid( + value, + "Import layout must contain at least one entry of a wildcard symbol (*)", + ) value == "idea" -> { LOGGER.warn { "`idea` is deprecated! Please use `*,java.**,javax.**,kotlin.**,^` instead to ensure that the Kotlin IDE " + @@ -235,17 +236,18 @@ public class ImportOrderingRule : ASCII_PATTERN, ) } - else -> try { - PropertyType.PropertyValue.valid( - value, - parseImportsLayout(value), - ) - } catch (e: IllegalArgumentException) { - PropertyType.PropertyValue.invalid( - value, - "Unexpected imports layout: $value", - ) - } + else -> + try { + PropertyType.PropertyValue.valid( + value, + parseImportsLayout(value), + ) + } catch (e: IllegalArgumentException) { + PropertyType.PropertyValue.invalid( + value, + "Unexpected imports layout: $value", + ) + } } } 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 f690c7ea6c..50f8b7f213 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 @@ -1056,22 +1056,23 @@ public class IndentationRule : private fun ASTNode.acceptableTrailingSpaces(): String { require(elementType == WHITE_SPACE) - val acceptableTrailingSpaces = when (nextLeaf()?.elementType) { - KDOC_LEADING_ASTERISK, KDOC_END -> { - // The indentation of a KDoc comment contains a space as the last character regardless of the indentation - // style (tabs or spaces) except for the starting line of the KDoc comment - KDOC_CONTINUATION_INDENT - } + val acceptableTrailingSpaces = + when (nextLeaf()?.elementType) { + KDOC_LEADING_ASTERISK, KDOC_END -> { + // The indentation of a KDoc comment contains a space as the last character regardless of the indentation + // style (tabs or spaces) except for the starting line of the KDoc comment + KDOC_CONTINUATION_INDENT + } - TYPE_CONSTRAINT -> { - // 6 spaces (length of "where" keyword plus a separator space) to indent type constraints as below: - // where A1 : RecyclerView.Adapter, - // A1 : ComposableAdapter.ViewTypeProvider, - TYPE_CONSTRAINT_CONTINUATION_INDENT - } + TYPE_CONSTRAINT -> { + // 6 spaces (length of "where" keyword plus a separator space) to indent type constraints as below: + // where A1 : RecyclerView.Adapter, + // A1 : ComposableAdapter.ViewTypeProvider, + TYPE_CONSTRAINT_CONTINUATION_INDENT + } - else -> "" - } + else -> "" + } val nodeIndent = text.substringAfterLast("\n") return if (nodeIndent.endsWith(acceptableTrailingSpaces)) { return acceptableTrailingSpaces @@ -1161,11 +1162,12 @@ public class IndentationRule : private fun ASTNode.textWithEscapedTabAndNewline() = text.textWithEscapedTabAndNewline() private fun String.textWithEscapedTabAndNewline(): String { - val (prefix, suffix) = if (this.all { it.isWhitespace() }) { - Pair("[", "]") - } else { - Pair("", "") - } + val (prefix, suffix) = + if (this.all { it.isWhitespace() }) { + Pair("[", "]") + } else { + Pair("", "") + } return prefix .plus( this diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt index 2241f96ff7..981da39dd9 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt @@ -56,11 +56,12 @@ public class ModifierListSpacingRule : if (node.isAnnotationElement() || (node.elementType == MODIFIER_LIST && node.lastChildNode.isAnnotationElement()) ) { - val expectedWhiteSpace = if (whitespace.textContains('\n')) { - node.indent() - } else { - " " - } + val expectedWhiteSpace = + if (whitespace.textContains('\n')) { + node.indent() + } else { + " " + } if (whitespace.text != expectedWhiteSpace) { emit( whitespace.startOffset, diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt index 63b55bc3d5..7da4fa6ddf 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MultilineExpressionWrapping.kt @@ -99,7 +99,7 @@ public class MultilineExpressionWrapping : node .treeParent .indent() - .plus(indentConfig.indent) + .plus(indentConfig.indent), ) node .lastChildLeafOrSelf() diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt index 81ad0aff5e..eae7c01721 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt @@ -39,20 +39,21 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") { val modifiedLines = lines .mapIndexed { index, line -> - val modifiedLine = when { - node.elementType != EOL_COMMENT && index == lines.size - 1 && node.nextLeaf() != null -> - // Do not change the last line as it contains the indentation of the next element except - // when it is an EOL comment which may also not contain trailing spaces - line - line.hasTrailingSpace() -> { - val modifiedLine = line.trimEnd() - val firstTrailingSpaceOffset = violationOffset + modifiedLine.length - emit(firstTrailingSpaceOffset, "Trailing space(s)", true) - violated = true - modifiedLine + val modifiedLine = + when { + node.elementType != EOL_COMMENT && index == lines.size - 1 && node.nextLeaf() != null -> + // Do not change the last line as it contains the indentation of the next element except + // when it is an EOL comment which may also not contain trailing spaces + line + line.hasTrailingSpace() -> { + val modifiedLine = line.trimEnd() + val firstTrailingSpaceOffset = violationOffset + modifiedLine.length + emit(firstTrailingSpaceOffset, "Trailing space(s)", true) + violated = true + modifiedLine + } + else -> line } - else -> line - } violationOffset += line.length + 1 modifiedLine } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt index 101be4e287..959901c399 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoUnusedImportsRule.kt @@ -84,11 +84,12 @@ public class NoUnusedImportsRule : StandardRule("no-unused-imports") { } REFERENCE_EXPRESSION, OPERATION_REFERENCE -> { if (!node.isPartOf(IMPORT_DIRECTIVE)) { - val identifier = if (node is CompositeElement) { - node.findChildByType(IDENTIFIER) - } else { - node - } + val identifier = + if (node is CompositeElement) { + node.findChildByType(IDENTIFIER) + } else { + node + } identifier ?.let { identifier.text } ?.takeIf { it.isNotBlank() } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt index ea1328534c..968db05728 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoWildcardImportsRule.kt @@ -66,17 +66,18 @@ public class NoWildcardImportsRule : private val PACKAGES_TO_USE_ON_DEMAND_IMPORT_PROPERTY_PARSER: (String, String?) -> PropertyType.PropertyValue> = { _, value -> when { - else -> try { - PropertyType.PropertyValue.valid( - value, - value?.let(Companion::parseAllowedWildcardImports) ?: emptyList(), - ) - } catch (e: IllegalArgumentException) { - PropertyType.PropertyValue.invalid( - value, - "Unexpected imports layout: $value", - ) - } + else -> + try { + PropertyType.PropertyValue.valid( + value, + value?.let(Companion::parseAllowedWildcardImports) ?: emptyList(), + ) + } catch (e: IllegalArgumentException) { + PropertyType.PropertyValue.invalid( + value, + "Unexpected imports layout: $value", + ) + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt index dfe3a60d61..0b3fcae21d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRule.kt @@ -202,12 +202,12 @@ public class ParameterListWrappingRule : // ... LPAR // VALUE_PARAMETER... // RPAR - val intendedIndent = if (child.elementType == VALUE_PARAMETER) { - indent + indentConfig.indent - } else { - indent - } - + val intendedIndent = + if (child.elementType == VALUE_PARAMETER) { + indent + indentConfig.indent + } else { + indent + } val prevLeaf = child.prevLeaf() if (prevLeaf is PsiWhiteSpace) { if (prevLeaf.getText().contains("\n")) { @@ -250,11 +250,12 @@ public class ParameterListWrappingRule : private fun ASTNode.hasTypeParameterListInFront(): Boolean { val parent = this.treeParent - val typeParameterList = if (parent.elementType == PRIMARY_CONSTRUCTOR) { - parent.prevSibling { it.elementType == TYPE_PARAMETER_LIST } - } else { - parent.children().firstOrNull { it.elementType == TYPE_PARAMETER_LIST } - } + val typeParameterList = + if (parent.elementType == PRIMARY_CONSTRUCTOR) { + parent.prevSibling { it.elementType == TYPE_PARAMETER_LIST } + } else { + parent.children().firstOrNull { it.elementType == TYPE_PARAMETER_LIST } + } val typeListNode = typeParameterList ?: parent.psi.collectDescendantsOfType().firstOrNull()?.node diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt index 8ab6716f75..cea0b21d42 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundDoubleColonRule.kt @@ -23,18 +23,18 @@ public class SpacingAroundDoubleColonRule : StandardRule("double-colon-spacing") val nextLeaf = node.nextLeaf() var removeSingleWhiteSpace = false - val spacingBefore = when { - node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true // Clazz::class - node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> // String::length, ::isOdd - if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd - removeSingleWhiteSpace = true - !prevLeaf.textContains('\n') && prevLeaf.psi.textLength > 1 - } else { // String::length, List::isEmpty - !prevLeaf.textContains('\n') - } - else -> false - } - + val spacingBefore = + when { + node.isPartOf(CLASS_LITERAL_EXPRESSION) && prevLeaf is PsiWhiteSpace -> true // Clazz::class + node.isPartOf(CALLABLE_REFERENCE_EXPRESSION) && prevLeaf is PsiWhiteSpace -> // String::length, ::isOdd + if (node.treePrev == null) { // compose(length, ::isOdd), val predicate = ::isOdd + removeSingleWhiteSpace = true + !prevLeaf.textContains('\n') && prevLeaf.psi.textLength > 1 + } else { // String::length, List::isEmpty + !prevLeaf.textContains('\n') + } + else -> false + } val spacingAfter = nextLeaf is PsiWhiteSpace when { spacingBefore && spacingAfter -> { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt index e120ddb79e..bdfa0fa3fb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundParensRule.kt @@ -32,32 +32,34 @@ public class SpacingAroundParensRule : StandardRule("paren-spacing") { if (node.elementType == LPAR || node.elementType == RPAR) { val prevLeaf = node.prevLeaf() val nextLeaf = node.nextLeaf() - val spacingBefore = if (node.elementType == LPAR) { - prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') && - ( - prevLeaf.prevLeaf()?.elementType == IDENTIFIER && - // val foo: @Composable () -> Unit - node.treeParent?.treeParent?.elementType != FUNCTION_TYPE || - // Super keyword needs special-casing - prevLeaf.prevLeaf()?.elementType == SUPER_KEYWORD || - prevLeaf.prevLeaf()?.treeParent?.elementType == PRIMARY_CONSTRUCTOR - ) && - ( - node.treeParent?.elementType == VALUE_PARAMETER_LIST || - node.treeParent?.elementType == VALUE_ARGUMENT_LIST - ) - } else { - prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') && - prevLeaf.prevLeaf()?.elementType != LPAR - } - val spacingAfter = if (node.elementType == LPAR) { - nextLeaf is PsiWhiteSpace && - (!nextLeaf.textContains('\n') || nextLeaf.nextLeaf()?.elementType == RPAR) && - !nextLeaf.isNextLeafAComment() - } else { - nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') && - nextLeaf.nextLeaf()?.elementType == RPAR - } + val spacingBefore = + if (node.elementType == LPAR) { + prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') && + ( + prevLeaf.prevLeaf()?.elementType == IDENTIFIER && + // val foo: @Composable () -> Unit + node.treeParent?.treeParent?.elementType != FUNCTION_TYPE || + // Super keyword needs special-casing + prevLeaf.prevLeaf()?.elementType == SUPER_KEYWORD || + prevLeaf.prevLeaf()?.treeParent?.elementType == PRIMARY_CONSTRUCTOR + ) && + ( + node.treeParent?.elementType == VALUE_PARAMETER_LIST || + node.treeParent?.elementType == VALUE_ARGUMENT_LIST + ) + } else { + prevLeaf is PsiWhiteSpace && !prevLeaf.textContains('\n') && + prevLeaf.prevLeaf()?.elementType != LPAR + } + val spacingAfter = + if (node.elementType == LPAR) { + nextLeaf is PsiWhiteSpace && + (!nextLeaf.textContains('\n') || nextLeaf.nextLeaf()?.elementType == RPAR) && + !nextLeaf.isNextLeafAComment() + } else { + nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') && + nextLeaf.nextLeaf()?.elementType == RPAR + } when { spacingBefore && spacingAfter -> { emit(node.startOffset, "Unexpected spacing around \"${node.text}\"", true) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt index 3b41697a40..add82e8dea 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt @@ -68,11 +68,12 @@ public class StringTemplateRule : StandardRule("string-template") { node.removeChild(leftCurlyBraceNode) node.removeChild(rightCurlyBraceNode) val remainingNode = node.firstChildNode - val newNode = if (remainingNode.elementType == DOT_QUALIFIED_EXPRESSION) { - LeafPsiElement(REGULAR_STRING_PART, "\$${remainingNode.text}") - } else { - LeafPsiElement(remainingNode.elementType, "\$${remainingNode.text}") - } + val newNode = + if (remainingNode.elementType == DOT_QUALIFIED_EXPRESSION) { + LeafPsiElement(REGULAR_STRING_PART, "\$${remainingNode.text}") + } else { + LeafPsiElement(remainingNode.elementType, "\$${remainingNode.text}") + } node.replaceChild(node.firstChildNode, newNode) } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt index 48d074d51b..8cde204494 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt @@ -145,35 +145,38 @@ public class TrailingCommaOnCallSiteRule : ) { val prevLeaf = inspectNode.prevLeaf() val trailingCommaNode = prevLeaf?.findPreviousTrailingCommaNodeOrNull() - val trailingCommaState = when { - this.isMultiline() -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING - else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS - } + val trailingCommaState = + when { + this.isMultiline() -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING + else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS + } when (trailingCommaState) { - TrailingCommaState.EXISTS -> if (!isTrailingCommaAllowed) { - emit( - trailingCommaNode!!.startOffset, - "Unnecessary trailing comma before \"${inspectNode.text}\"", - true, - ) - if (autoCorrect) { - this.removeChild(trailingCommaNode) + TrailingCommaState.EXISTS -> + if (!isTrailingCommaAllowed) { + emit( + trailingCommaNode!!.startOffset, + "Unnecessary trailing comma before \"${inspectNode.text}\"", + true, + ) + if (autoCorrect) { + this.removeChild(trailingCommaNode) + } } - } - TrailingCommaState.MISSING -> if (isTrailingCommaAllowed) { - val prevNode = inspectNode.prevCodeLeaf()!! - emit( - prevNode.startOffset + prevNode.textLength, - "Missing trailing comma before \"${inspectNode.text}\"", - true, - ) - if (autoCorrect) { - val prevPsi = inspectNode.prevCodeSibling()!!.psi - val parentPsi = prevPsi.parent - val psiFactory = KtPsiFactory(prevPsi) - parentPsi.addAfter(psiFactory.createComma(), prevPsi) + TrailingCommaState.MISSING -> + if (isTrailingCommaAllowed) { + val prevNode = inspectNode.prevCodeLeaf()!! + emit( + prevNode.startOffset + prevNode.textLength, + "Missing trailing comma before \"${inspectNode.text}\"", + true, + ) + if (autoCorrect) { + val prevPsi = inspectNode.prevCodeSibling()!!.psi + val parentPsi = prevPsi.parent + val psiFactory = KtPsiFactory(prevPsi) + parentPsi.addAfter(psiFactory.createComma(), prevPsi) + } } - } TrailingCommaState.REDUNDANT -> { emit( trailingCommaNode!!.startOffset, @@ -206,11 +209,12 @@ public class TrailingCommaOnCallSiteRule : private fun ASTNode.hasAtLeastOneArgument() = children().any { it.elementType == VALUE_ARGUMENT } private fun ASTNode.findPreviousTrailingCommaNodeOrNull(): ASTNode? { - val codeLeaf = if (isCodeLeaf()) { - this - } else { - prevCodeLeaf() - } + val codeLeaf = + if (isCodeLeaf()) { + this + } else { + prevCodeLeaf() + } return codeLeaf?.takeIf { it.elementType == ElementType.COMMA } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt index fd3cfec5f8..0033e5151f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt @@ -244,10 +244,11 @@ public class TrailingCommaOnDeclarationSiteRule : ) { val prevLeaf = inspectNode.prevLeaf() val trailingCommaNode = prevLeaf?.findPreviousTrailingCommaNodeOrNull() - val trailingCommaState = when { - isMultiline(psi) -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING - else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS - } + val trailingCommaState = + when { + isMultiline(psi) -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING + else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS + } when (trailingCommaState) { TrailingCommaState.EXISTS -> if (isTrailingCommaAllowed) { @@ -386,11 +387,12 @@ public class TrailingCommaOnDeclarationSiteRule : } private fun ASTNode.findPreviousTrailingCommaNodeOrNull(): ASTNode? { - val codeLeaf = if (isCodeLeaf()) { - this - } else { - prevCodeLeaf() - } + val codeLeaf = + if (isCodeLeaf()) { + this + } else { + prevCodeLeaf() + } return codeLeaf?.takeIf { it.elementType == ElementType.COMMA } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt index 807eb23013..78616b5966 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt @@ -327,11 +327,12 @@ public class WrappingRule : emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, ) { for (c in node.children()) { - val hasLineBreak = when (c.elementType) { - VALUE_ARGUMENT -> c.hasLineBreak(LAMBDA_EXPRESSION, FUN) - VALUE_PARAMETER, ANNOTATION -> c.hasLineBreak() - else -> false - } + val hasLineBreak = + when (c.elementType) { + VALUE_ARGUMENT -> c.hasLineBreak(LAMBDA_EXPRESSION, FUN) + VALUE_PARAMETER, ANNOTATION -> c.hasLineBreak() + else -> false + } if (hasLineBreak) { // rearrange // diff --git a/ktlint-ruleset-test-tooling/src/main/kotlin/com/pinterest/ruleset/testtooling/DumpASTRule.kt b/ktlint-ruleset-test-tooling/src/main/kotlin/com/pinterest/ruleset/testtooling/DumpASTRule.kt index 912694fc71..9cd48b6cf1 100644 --- a/ktlint-ruleset-test-tooling/src/main/kotlin/com/pinterest/ruleset/testtooling/DumpASTRule.kt +++ b/ktlint-ruleset-test-tooling/src/main/kotlin/com/pinterest/ruleset/testtooling/DumpASTRule.kt @@ -83,19 +83,20 @@ public class DumpASTRule @JvmOverloads constructor( } private fun ASTNode.lineNumberOrUnknown(): String { - val lineNumber = try { - psi - .containingFile - ?.viewProvider - ?.document - ?.getLineNumber(this.startOffset) - ?.let { it + 1 } - ?.toString() - } catch (e: IndexOutOfBoundsException) { - // Due to autocorrect mutations in the AST it can happen that the node's offset becomes invalid. As a result - // the line number can not be determined. - null - } + val lineNumber = + try { + psi + .containingFile + ?.viewProvider + ?.document + ?.getLineNumber(this.startOffset) + ?.let { it + 1 } + ?.toString() + } catch (e: IndexOutOfBoundsException) { + // Due to autocorrect mutations in the AST it can happen that the node's offset becomes invalid. As a result + // the line number can not be determined. + null + } return lineNumber ?: "Unknown" } diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt index b8011eff8d..d42bb1a260 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/RuleExtension.kt @@ -83,11 +83,12 @@ public fun Set.lint( ): List { val lintErrors = ArrayList() val ruleProviders = toRuleProviders() - val code = if (filePath != null) { - Code.fromFile(KTLINT_TEST_FILE_SYSTEM.resolve(filePath).toFile()) - } else { - Code.fromSnippet(text) - } + val code = + if (filePath != null) { + Code.fromFile(KTLINT_TEST_FILE_SYSTEM.resolve(filePath).toFile()) + } else { + Code.fromSnippet(text) + } KtLintRuleEngine( ruleProviders = ruleProviders, editorConfigOverride = @@ -140,11 +141,12 @@ public fun Set.format( ): Pair> { val lintErrors = ArrayList() val ruleProviders = toRuleProviders() - val code = if (filePath != null) { - Code.fromFile(KTLINT_TEST_FILE_SYSTEM.resolve(filePath).toFile()) - } else { - Code.fromSnippet(text) - } + val code = + if (filePath != null) { + Code.fromFile(KTLINT_TEST_FILE_SYSTEM.resolve(filePath).toFile()) + } else { + Code.fromSnippet(text) + } val formattedCode = KtLintRuleEngine( ruleProviders = ruleProviders,