Skip to content

Commit

Permalink
Move curly brace before all consecutive comments preceding that curly…
Browse files Browse the repository at this point in the history
… brace (#2375)

* Move curly brace before all consecutive comments preceding that curly brace

Closes #2355
  • Loading branch information
paul-dingemans authored Nov 26, 2023
1 parent 456d928 commit e5d944b
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 16 deletions.
1 change: 1 addition & 0 deletions ktlint-ruleset-standard/api/ktlint-ruleset-standard.api
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundComm

public final class com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundCurlyRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
public fun <init> ()V
public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V
public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLONCOLON
import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA
import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT
import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT
import com.pinterest.ktlint.rule.engine.core.api.ElementType.EXCLEXCL
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN
import com.pinterest.ktlint.rule.engine.core.api.ElementType.LAMBDA_EXPRESSION
Expand All @@ -17,26 +16,56 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACKET
import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR
import com.pinterest.ktlint.rule.engine.core.api.ElementType.SAFE_ACCESS
import com.pinterest.ktlint.rule.engine.core.api.ElementType.SEMICOLON
import com.pinterest.ktlint.rule.engine.core.api.IndentConfig
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.isLeaf
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment
import com.pinterest.ktlint.rule.engine.core.api.isPartOfString
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
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.prevLeaf
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.TreeElement
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtLambdaExpression

@SinceKtlint("0.1", STABLE)
public class SpacingAroundCurlyRule : StandardRule("curly-spacing") {
public class SpacingAroundCurlyRule :
StandardRule(
id = "curly-spacing",
usesEditorConfigProperties =
setOf(
CODE_STYLE_PROPERTY,
INDENT_SIZE_PROPERTY,
INDENT_STYLE_PROPERTY,
),
) {
private var codeStyle = CODE_STYLE_PROPERTY.defaultValue
private var indentConfig = IndentConfig.DEFAULT_INDENT_CONFIG

override fun beforeFirstNode(editorConfig: EditorConfig) {
codeStyle = editorConfig[CODE_STYLE_PROPERTY]
indentConfig =
IndentConfig(
indentStyle = editorConfig[INDENT_STYLE_PROPERTY],
tabWidth = editorConfig[INDENT_SIZE_PROPERTY],
)
if (indentConfig.disabled) {
stopTraversalOfAST()
}
}

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
Expand Down Expand Up @@ -76,19 +105,21 @@ public class SpacingAroundCurlyRule : StandardRule("curly-spacing") {
) {
emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true)
if (autoCorrect) {
val eolCommentExists =
if (prevLeaf.isPrecededByEolComment()) {
// All consecutive whitespaces and comments preceding the curly have to be moved after the curly brace
prevLeaf
.prevLeaf()
?.let { it is PsiComment && it.elementType == EOL_COMMENT }
?: false
if (eolCommentExists) {
val commentLeaf = prevLeaf.prevLeaf()!!
if (commentLeaf.prevLeaf() is PsiWhiteSpace) {
(commentLeaf.prevLeaf() as LeafPsiElement).rawRemove()
}
(node.treeParent.treeParent as TreeElement).removeChild(commentLeaf)
(node.treeParent as TreeElement).addChild(commentLeaf, node.treeNext)
node.upsertWhitespaceAfterMe(" ")
.leavesIncludingSelf(forward = false)
.takeWhile { it.isWhiteSpace() || it.isPartOfComment() }
.toList()
.reversed()
.takeIf { it.isNotEmpty() }
?.let { leavesToMoveAfterCurly ->
node.treeParent.addChildren(
leavesToMoveAfterCurly.first(),
leavesToMoveAfterCurly.last(),
node.treeNext,
)
}
}
(prevLeaf as LeafPsiElement).rawReplaceWithText(" ")
}
Expand Down Expand Up @@ -131,6 +162,11 @@ public class SpacingAroundCurlyRule : StandardRule("curly-spacing") {
}
}

private fun ASTNode.isPrecededByEolComment() =
prevLeaf()
?.isPartOfComment()
?: false

private fun shouldNotToBeSeparatedBySpace(leaf: ASTNode?): Boolean {
val nextElementType = leaf?.elementType
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,4 +464,102 @@ class SpacingAroundCurlyRuleTest {
LintViolation(5, 1, "Unexpected newline before \"{\""),
).isFormattedAs(formattedCode)
}

@Test
fun `Given a block comment before opening curly brace of function`() {
val code =
"""
class Foo1()/* a comment (originally) not preceded by a space */
{
}
class Foo2() /* a comment (originally) preceded by a space */
{
}
""".trimIndent()
val formattedCode =
"""
class Foo1() { /* a comment (originally) not preceded by a space */
}
class Foo2() { /* a comment (originally) preceded by a space */
}
""".trimIndent()
spacingAroundCurlyRuleAssertThat(code)
.hasLintViolations(
LintViolation(2, 1, "Unexpected newline before \"{\""),
LintViolation(5, 1, "Unexpected newline before \"{\""),
).isFormattedAs(formattedCode)
}

@Nested
inner class `Issue 2355 - Given a function signature with multiple comments at an unexpected location` {
@Test
fun `Multiple EOL comments at an unexpected location`() {
val code =
"""
fun foo(): Float
// comment1
// comment2
{
return 0.0f
}
""".trimIndent()
val formattedCode =
"""
fun foo(): Float {
// comment1
// comment2
return 0.0f
}
""".trimIndent()
spacingAroundCurlyRuleAssertThat(code)
.hasLintViolation(4, 1, "Unexpected newline before \"{\"")
.isFormattedAs(formattedCode)
}

@Test
fun `Multiple block comments at an unexpected location`() {
val code =
"""
fun foo(): Float
/* comment1 */
/* comment2 */
{
return 0.0f
}
""".trimIndent()
val formattedCode =
"""
fun foo(): Float {
/* comment1 */
/* comment2 */
return 0.0f
}
""".trimIndent()
spacingAroundCurlyRuleAssertThat(code)
.hasLintViolation(4, 1, "Unexpected newline before \"{\"")
.isFormattedAs(formattedCode)
}

@Test
fun `Multiple block and EOL comments at an unexpected location`() {
val code =
"""
fun foo(): Float // comment1
/* comment2 */
{
return 0.0f
}
""".trimIndent()
val formattedCode =
"""
fun foo(): Float { // comment1
/* comment2 */
return 0.0f
}
""".trimIndent()
spacingAroundCurlyRuleAssertThat(code)
.hasLintViolation(3, 1, "Unexpected newline before \"{\"")
.isFormattedAs(formattedCode)
}
}
}

0 comments on commit e5d944b

Please sign in to comment.