Skip to content

Commit 85b2524

Browse files
Fix conflict between modifier-list-spacing and context-receiver-list-wrapping (#3077)
Wrapping of a context receiver list in `modifier-list-spacing` may not conflict with the wrapping in `context-receiver-list-wrapping` Closes #3065
1 parent 2d7a94a commit 85b2524

File tree

3 files changed

+117
-18
lines changed

3 files changed

+117
-18
lines changed

ktlint-ruleset-standard/api/ktlint-ruleset-standard.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/MixedConditionOpe
476476

477477
public final class com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
478478
public fun <init> ()V
479+
public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V
479480
public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V
480481
}
481482

ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRule.kt

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ package com.pinterest.ktlint.ruleset.standard.rules
33
import com.pinterest.ktlint.rule.engine.core.api.AutocorrectDecision
44
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION
55
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY
6+
import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONTEXT_RECEIVER_LIST
67
import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST
8+
import com.pinterest.ktlint.rule.engine.core.api.IndentConfig
9+
import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.Companion.DEFAULT_INDENT_CONFIG
10+
import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED
711
import com.pinterest.ktlint.rule.engine.core.api.RuleId
812
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
913
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL
1014
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE
1115
import com.pinterest.ktlint.rule.engine.core.api.children20
16+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig
17+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY
18+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY
1219
import com.pinterest.ktlint.rule.engine.core.api.ifAutocorrectAllowed
1320
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment20
1421
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace20
@@ -24,7 +31,30 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode
2431
*/
2532
@SinceKtlint("0.45", EXPERIMENTAL)
2633
@SinceKtlint("0.49", STABLE)
27-
public class ModifierListSpacingRule : StandardRule("modifier-list-spacing") {
34+
public class ModifierListSpacingRule :
35+
StandardRule(
36+
id = "modifier-list-spacing",
37+
visitorModifiers =
38+
setOf(
39+
VisitorModifier.RunAfterRule(ANNOTATION_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED),
40+
VisitorModifier.RunAfterRule(MODIFIER_ORDER_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED),
41+
),
42+
usesEditorConfigProperties =
43+
setOf(
44+
INDENT_SIZE_PROPERTY,
45+
INDENT_STYLE_PROPERTY,
46+
),
47+
) {
48+
private var indentConfig = DEFAULT_INDENT_CONFIG
49+
50+
override fun beforeFirstNode(editorConfig: EditorConfig) {
51+
indentConfig =
52+
IndentConfig(
53+
indentStyle = editorConfig[INDENT_STYLE_PROPERTY],
54+
tabWidth = editorConfig[INDENT_SIZE_PROPERTY],
55+
)
56+
}
57+
2858
override fun beforeVisitChildNodes(
2959
node: ASTNode,
3060
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> AutocorrectDecision,
@@ -48,33 +78,47 @@ public class ModifierListSpacingRule : StandardRule("modifier-list-spacing") {
4878
node
4979
.nextSibling { it.isWhiteSpace20 && it.nextLeaf?.isPartOfComment20 != true }
5080
?.takeUnless {
51-
// Regardless of element type, a single white space is always ok and does not need to be checked.
52-
it.text == " "
53-
}?.takeUnless {
5481
// A single newline after a comment is always ok and does not need further checking.
5582
it.text.trim(' ', '\t').contains('\n') && it.prevLeaf?.isPartOfComment20 == true
5683
}?.let { whitespace ->
57-
if (node.isAnnotationElement() ||
58-
(node.elementType == MODIFIER_LIST && node.lastChildNode.isAnnotationElement())
59-
) {
60-
if (whitespace.text.contains("\n\n")) {
61-
emit(whitespace.startOffset, "Single newline expected after annotation", true)
62-
.ifAutocorrectAllowed {
63-
whitespace.replaceTextWith("\n".plus(whitespace.text.substringAfterLast("\n")))
64-
}
65-
} else if (!whitespace.text.contains('\n') && whitespace.text != " ") {
66-
emit(whitespace.startOffset, "Single whitespace or newline expected after annotation", true)
84+
when {
85+
node.isAnnotation() -> {
86+
if (whitespace.text.contains("\n\n")) {
87+
emit(whitespace.startOffset, "Single newline expected after annotation", true)
88+
.ifAutocorrectAllowed {
89+
whitespace.replaceTextWith("\n".plus(whitespace.text.substringAfterLast("\n")))
90+
}
91+
} else if (!whitespace.text.contains('\n') && whitespace.text != " ") {
92+
emit(whitespace.startOffset, "Single whitespace or newline expected after annotation", true)
93+
.ifAutocorrectAllowed { whitespace.replaceTextWith(" ") }
94+
}
95+
Unit
96+
}
97+
98+
node.isContextReceiverList() -> {
99+
if (!whitespace.text.contains("\n")) {
100+
emit(whitespace.startOffset, "Single newline expected after context receiver list", true)
101+
.ifAutocorrectAllowed {
102+
whitespace.replaceTextWith(indentConfig.parentIndentOf(node))
103+
}
104+
}
105+
}
106+
107+
whitespace.text != " " -> {
108+
emit(whitespace.startOffset, "Single whitespace expected after modifier", true)
67109
.ifAutocorrectAllowed { whitespace.replaceTextWith(" ") }
68110
}
69-
Unit
70-
} else {
71-
emit(whitespace.startOffset, "Single whitespace expected after modifier", true)
72-
.ifAutocorrectAllowed { whitespace.replaceTextWith(" ") }
73111
}
74112
}
75113
}
76114

115+
private fun ASTNode.isAnnotation(): Boolean =
116+
isAnnotationElement() || (elementType == MODIFIER_LIST && lastChildNode.isAnnotationElement())
117+
77118
private fun ASTNode?.isAnnotationElement() = this != null && (elementType == ANNOTATION || elementType == ANNOTATION_ENTRY)
119+
120+
private fun ASTNode.isContextReceiverList(): Boolean =
121+
elementType == CONTEXT_RECEIVER_LIST || (elementType == MODIFIER_LIST && lastChildNode.isContextReceiverList())
78122
}
79123

80124
public val MODIFIER_LIST_SPACING_RULE_ID: RuleId = ModifierListSpacingRule().ruleId

ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ModifierListSpacingRuleTest.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,58 @@ class ModifierListSpacingRuleTest {
185185
""".trimIndent()
186186
modifierListSpacingRuleAssertThat(code).hasNoLintViolations()
187187
}
188+
189+
@Test
190+
fun `Issue 3065 - Given a context parameter on same line as function then wrap to new line after context parameter`() {
191+
val code =
192+
"""
193+
class Bar {
194+
context(Foo) fun foo()
195+
context(_: Foo) fun foo()
196+
197+
context(Foooooooooooooooo<Foo, Bar>) fun fooBar()
198+
context(_: Foooooooooooooooo<Foo, Bar>) fun fooBar()
199+
}
200+
""".trimIndent()
201+
val formattedCode =
202+
"""
203+
class Bar {
204+
context(Foo)
205+
fun foo()
206+
context(_: Foo)
207+
fun foo()
208+
209+
context(Foooooooooooooooo<Foo, Bar>)
210+
fun fooBar()
211+
context(_: Foooooooooooooooo<Foo, Bar>)
212+
fun fooBar()
213+
}
214+
""".trimIndent()
215+
modifierListSpacingRuleAssertThat(code)
216+
.hasLintViolations(
217+
LintViolation(2, 17, "Single newline expected after context receiver list"),
218+
LintViolation(3, 20, "Single newline expected after context receiver list"),
219+
LintViolation(5, 41, "Single newline expected after context receiver list"),
220+
LintViolation(6, 44, "Single newline expected after context receiver list"),
221+
).isFormattedAs(formattedCode)
222+
}
223+
224+
@Test
225+
fun `Given an annotation, a context parameter, and other modifiers on a single line in incorrect order`() {
226+
val code =
227+
"""
228+
@Suppress("DEPRECATED") open context(_: Foo) public fun foo() {}
229+
""".trimIndent()
230+
val formattedCode =
231+
"""
232+
@Suppress("DEPRECATED")
233+
context(_: Foo)
234+
public open fun foo() {}
235+
""".trimIndent()
236+
modifierListSpacingRuleAssertThat(code)
237+
.addAdditionalRuleProvider { ModifierOrderRule() }
238+
.addAdditionalRuleProvider { AnnotationRule() }
239+
.hasLintViolation(1, 45, "Single newline expected after context receiver list")
240+
.isFormattedAs(formattedCode)
241+
}
188242
}

0 commit comments

Comments
 (0)