kotlin-style-guide icon indicating copy to clipboard operation
kotlin-style-guide copied to clipboard

Expression formatting

Open yole opened this issue 8 years ago • 17 comments

When wrapping chained calls, put the . character or the ?. operator on the next line, with a single indent:

val anchor = owner.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

Put the elvis operator on the line after the wrap:

val s = foo()
    ?: bar

Leave other operators before the wrap:

val s = "foo" + 
    "bar"

yole avatar May 31 '16 18:05 yole

It should be clarified whether the first call should always appear on the same line.

One edge case I've frequently run into: what should be done in the case where the first call (owner.firstChild!! in your example) has many arguments (relates to #8) or passes a long lambda?

// Option 1: Awkward
val anchor = owner.firstChild { child ->
  child is PsiElement
}.siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

// Option 2: Awkward
val anchor = owner.firstChild { child ->
  child is PsiElement
}
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

// Option 3: Less awkward, IMO
val anchor = owner
    .firstChild { child ->
      child is PsiElement
    }
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

I've been preferring the third option.

damianw avatar Jun 08 '16 17:06 damianw

I prefer option 3

cypressious avatar Jun 08 '16 17:06 cypressious

One other thought related to https://github.com/yole/kotlin-style-guide/issues/9#issuecomment-224664633:

If the first call is to a this-scoped or globally-scoped function, it should be placed on the next line:

val anchor =
    firstChild { child ->
      child is PsiElement
    }
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

damianw avatar Aug 02 '16 17:08 damianw

I'd prefer not to have any special rules with the respect to the first call in chain. It really depends on the context. It should be a requirement to always but "." on a new line, but if you do wrap the expression, then dot should be at the beginning of the line.

elizarov avatar Nov 08 '16 13:11 elizarov

It is not that clear about wrapping other operators. For example, when you write long algebraic expression it is very useful to wrap "+" and "-" so that they are on the beginning of the line. It is much easier to follow the formula in this case and it is consistent with math tradition.

elizarov avatar Nov 08 '16 16:11 elizarov

https://youtrack.jetbrains.com/issue/KT-9220 I prefer "align by dot":

    val anchor = owner.firstChild { child -> 
                          child is PsiElement
                      }
                      .siblings(forward = true)
                      .dropWhile { 
                          it is PsiComment || it is PsiWhiteSpace 
                      }

it plays nice in java

mykola-dev avatar Mar 09 '17 13:03 mykola-dev

Hey guys! As I see from this discussion everybody preffer the option 3 from @damianw comment. But IDEA still autoformats such case like in the second option:

val fooBars = mapOf(
    foo1 to bar1,
    foo2 to bar2
)
    .map { (foo, bar) ->
        FooBar(foo, bar)
     }

Is it ok or I just don't know how to make it format right?

Jeevuz avatar Oct 05 '18 13:10 Jeevuz

@Jeevuz How do you expect this case to be formatted? Option 3 is about inserting a line break before the first . in the call chain. In this case there is no call chain.

The right way to format this example is as follows, and unless I'm mistaken IDEA does allow this formatting:

    val fooBars = mapOf(
        foo1 to bar1,
        foo2 to bar2
    ).map { (foo, bar) ->
        FooBar(foo, bar)
    }

yole avatar Oct 08 '18 15:10 yole

@yole Thanks for clarifying this! This exactly what I use now.

But I think this style hides parameters a little:

val secondExample = mapOf(
    longKey to firstValue,
    evenLongerKey to secondValue,
    keyThree to valueThreeWithSuffix
).map { (parameterName, value) -> // This line hides
    FooBar(parameterName, value)
}

Maybe it will be more readable with indented first call?

val secondExample = mapOf(
        longKey to firstValue,
        evenLongerKey to secondValue,
        keyThree to valueThreeWithSuffix
    )
    .map { (parameterName, value) ->
        FooBar(parameterName, value)
    }

Just curious, why the continuation indent (4 spaces) is not used for the method calls? I think it will be clearer that this method call is assigned to something.

Jeevuz avatar Oct 08 '18 18:10 Jeevuz

@yole @Jeevuz I think in that case I might even prefer this style, with the function call on a new line:

val fooBars =
    mapOf(
        foo1 to bar1,
        foo2 to bar2
    )
    .map { (foo, bar) ->
        FooBar(foo, bar)
    }

But I'd also probably put quite some effort into not having a function call that long in the first place if I need to chain it.

roschlau avatar Oct 08 '18 18:10 roschlau

Uh... I got the next level. AS formats as follows. How will you format this?

val fooBars = mapOf(
    foo1 to bar1,
    foo2 to bar2
).map { (foo, bar) ->
    FooBar(foo, bar)
}
    .filter { foo > 0 }

The only way I see is to define map creation as a separate local variable. But making a variable only for formatting purposes is little bit strange I think.

Jeevuz avatar Oct 09 '18 16:10 Jeevuz

@Jeevuz Like this?

val fooBars =
    mapOf(
        foo1 to bar1,
        foo2 to bar2
    )
    .map { (foo, bar) -> FooBar(foo, bar) }
    .filter { foo > 0 }

roschlau avatar Oct 10 '18 08:10 roschlau

@roschlau Exactly! But AS doen't allow this type of formatting. For this to work we need first call params or lambda block to be idented when assigned a variable. Maybe it's a topic for a new issue?

Jeevuz avatar Oct 10 '18 08:10 Jeevuz

Is there any justification for Leave other operators before the wrap:? I find it more readable to put boolean operators at the beginning of a long chain because it highlights how they are being combined

if (expression1
    && expr2
    && expr3
    || (expr4
        && expr5
        || expr6)
    && expr7
) {

}

guelo avatar Apr 27 '20 21:04 guelo

val s = "foo" + 
    "bar"

suggests when wrapping after a binary operator a single indent should be used. ktlint also does it like this. But IntelliJ uses a double-indent. In which project should a bug be reported? :-)

Vampire avatar Jun 17 '23 02:06 Vampire

It goes here: https://youtrack.jetbrains.com/issues/KTIJ

elizarov avatar Jul 24 '23 08:07 elizarov

Thanks, https://youtrack.jetbrains.com/issue/KTIJ-26380

Vampire avatar Jul 24 '23 08:07 Vampire