diff --git a/core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt b/core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt index ecf490b5..1f56f333 100644 --- a/core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt +++ b/core/src/main/java/com/facebook/ktfmt/format/KotlinInputAstVisitor.kt @@ -1557,20 +1557,30 @@ class KotlinInputAstVisitor( override fun visitAnnotatedExpression(expression: KtAnnotatedExpression) { builder.sync(expression) builder.block(ZERO) { - loop@ for (child in expression.node.children()) { - when (val psi = child.psi) { - is PsiWhiteSpace -> continue@loop - is KtAnnotationEntry -> { - visit(psi) - if (expression.annotationEntries.size != 1) { - builder.forcedBreak() - } else { - builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) - } + val baseExpression = expression.baseExpression + + builder.block(ZERO) { + val annotationEntries = expression.annotationEntries + for (annotationEntry in annotationEntries) { + if (annotationEntry !== annotationEntries.first()) { + builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) } - else -> visit(psi) + visit(annotationEntry) } } + + // Binary expressions in a block have a different meaning according to their formatting. + // If there in the line above, they refer to the entire expression, if they're in the same + // line then only to the first operand of the operator. + // We force a break to avoid such semantic changes + when { + (baseExpression is KtBinaryExpression || baseExpression is KtBinaryExpressionWithTypeRHS) && + expression.parent is KtBlockExpression -> builder.forcedBreak() + baseExpression is KtLambdaExpression -> builder.space() + else -> builder.breakOp(Doc.FillMode.UNIFIED, " ", ZERO) + } + + visit(expression.baseExpression) } } diff --git a/core/src/test/java/com/facebook/ktfmt/format/FormatterTest.kt b/core/src/test/java/com/facebook/ktfmt/format/FormatterTest.kt index 33b5b195..ff83cb96 100644 --- a/core/src/test/java/com/facebook/ktfmt/format/FormatterTest.kt +++ b/core/src/test/java/com/facebook/ktfmt/format/FormatterTest.kt @@ -4034,16 +4034,38 @@ class FormatterTest { fun `annotated expressions`() = assertFormatted( """ + |------------------------------------------------ |fun f() { - | @Suppress("MagicNumber") add(10) + | @Suppress("MagicNumber") add(10) && add(20) + | + | @Suppress("MagicNumber") + | add(10) && add(20) + | + | @Anno1 @Anno2(param = Param1::class) + | add(10) && add(20) | | @Anno1 | @Anno2(param = Param1::class) | @Anno3 | @Anno4(param = Param2::class) - | add(10) + | add(10) && add(20) + | + | @Anno1 + | @Anno2(param = Param1::class) + | @Anno3 + | @Anno4(param = Param2::class) + | add(10) && add(20) + | + | @Suppress("MagicNumber") add(10) && + | add(20) && + | add(30) + | + | add(@Suppress("MagicNumber") 10) && + | add(20) && + | add(30) |} - |""".trimMargin()) + |""".trimMargin(), + deduceMaxWidth = true) @Test fun `annotated function declarations`() = @@ -4688,8 +4710,7 @@ class FormatterTest { | } | |fun foo() = - | Runnable @Px - | { + | Runnable @Px { | foo() | // | } @@ -5687,4 +5708,48 @@ class FormatterTest { | .z { it } |""".trimMargin(), deduceMaxWidth = true) + + @Test + fun `annotations for expressions`() = + assertFormatted( + """ + |fun f() { + | var b + | @Suppress("UNCHECKED_CAST") b = f(1) as Int + | @Suppress("UNCHECKED_CAST") + | b = f(1) as Int + | + | @Suppress("UNCHECKED_CAST") b = f(1) to 5 + | @Suppress("UNCHECKED_CAST") + | b = f(1) to 5 + | + | @Suppress("UNCHECKED_CAST") f(1) as Int + 5 + | @Suppress("UNCHECKED_CAST") + | f(1) as Int + 5 + | + | @Anno1 /* comment */ @Anno2 f(1) as Int + |} + |""".trimMargin()) + + @Test + fun `annotations for expressions 2`() { + val code = + """ + |fun f() { + | @Suppress("UNCHECKED_CAST") f(1 + f(1) as Int) + | @Suppress("UNCHECKED_CAST") + | f(1 + f(1) as Int) + |} + |""".trimMargin() + + val expected = + """ + |fun f() { + | @Suppress("UNCHECKED_CAST") f(1 + f(1) as Int) + | @Suppress("UNCHECKED_CAST") f(1 + f(1) as Int) + |} + |""".trimMargin() + + assertThatFormatting(code).isEqualTo(expected) + } }