From 52ecb3f9efc71ee22c1ca92a110c0516e311c94b Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 3 Dec 2024 16:00:36 +0100 Subject: [PATCH] Do not wrap a context receiver in a function parameter type reference (#2892) Closes #2854 --- .../release-latest/docs/rules/standard.md | 4 +-- documentation/snapshot/docs/rules/standard.md | 4 +-- .../rules/ContextReceiverWrappingRule.kt | 26 ++++++++++++++++--- .../rules/ContextReceiverWrappingRuleTest.kt | 12 +++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/documentation/release-latest/docs/rules/standard.md b/documentation/release-latest/docs/rules/standard.md index c196abb5de..6f42ace382 100644 --- a/documentation/release-latest/docs/rules/standard.md +++ b/documentation/release-latest/docs/rules/standard.md @@ -4343,9 +4343,9 @@ Suppress or disable rule (1) ktlint_standard_condition-wrapping = disabled ``` -### Content receiver wrapping +### Context receiver wrapping -Wraps the content receiver list to a separate line regardless of maximum line length. If the maximum line length is configured and is exceeded, wrap the context receivers and if needed its projection types to separate lines. +Wraps the context receiver list to a separate line regardless of maximum line length. If the maximum line length is configured and is exceeded, wrap the context receivers and if needed its projection types to separate lines. === "[:material-heart:](#) Ktlint" diff --git a/documentation/snapshot/docs/rules/standard.md b/documentation/snapshot/docs/rules/standard.md index c196abb5de..fc3872c02b 100644 --- a/documentation/snapshot/docs/rules/standard.md +++ b/documentation/snapshot/docs/rules/standard.md @@ -4343,9 +4343,9 @@ Suppress or disable rule (1) ktlint_standard_condition-wrapping = disabled ``` -### Content receiver wrapping +### Context receiver wrapping -Wraps the content receiver list to a separate line regardless of maximum line length. If the maximum line length is configured and is exceeded, wrap the context receivers and if needed its projection types to separate lines. +Wraps the context receiver list of a function to a separate line regardless of maximum line length. If the maximum line length is configured and is exceeded, wrap the context receivers and if needed its projection types to separate lines. === "[:material-heart:](#) Ktlint" diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt index 5d6bdb91e3..c812cd15f8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRule.kt @@ -3,10 +3,15 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONTEXT_RECEIVER import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONTEXT_RECEIVER_LIST +import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN +import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE import com.pinterest.ktlint.rule.engine.core.api.ElementType.GT import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PROJECTION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_REFERENCE +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST 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.RuleId @@ -80,10 +85,11 @@ public class ContextReceiverWrappingRule : node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, ) { - // Context receiver must be followed by new line or comment + // Context receiver must be followed by new line or comment unless it is a type reference of a parameter node - .lastChildLeafOrSelf() - .nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() } + .takeUnless { it.isTypeReferenceParameterInFunction() } + ?.lastChildLeafOrSelf() + ?.nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() } ?.takeIf { !it.isWhiteSpaceWithNewline() } ?.let { nodeAfterContextReceiver -> emit(nodeAfterContextReceiver.startOffset, "Expected a newline after the context receiver", true) @@ -126,6 +132,20 @@ public class ContextReceiverWrappingRule : } } + private fun ASTNode.isTypeReferenceParameterInFunction() = + takeIf { it.elementType == CONTEXT_RECEIVER_LIST } + ?.treeParent + ?.takeIf { it.elementType == FUNCTION_TYPE } + ?.treeParent + ?.takeIf { it.elementType == TYPE_REFERENCE } + ?.treeParent + ?.takeIf { it.elementType == VALUE_PARAMETER } + ?.treeParent + ?.takeIf { it.elementType == VALUE_PARAMETER_LIST } + ?.treeParent + ?.let { it.elementType == FUN } + ?: false + private fun visitContextReceiverTypeArgumentList( node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision, diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRuleTest.kt index 779168608b..dea5e3c12f 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ContextReceiverWrappingRuleTest.kt @@ -199,4 +199,16 @@ class ContextReceiverWrappingRuleTest { LintViolation(2, 36, "Newline expected before closing parenthesis as max line length is violated"), ).isFormattedAs(formattedCode) } + + @Test + fun `Issue 2854 - Given a function parameter with a context receiver then do not wrap after the context receiver`() { + val code = + """ + fun bar1(foo: context(Foo) () -> Unit = { foobar() }) {} + fun bar2( + foo: context(Foo) () -> Unit = { foobar() } + ) {} + """.trimIndent() + contextReceiverWrappingRuleAssertThat(code).hasNoLintViolations() + } }