pinterest / ktlint

An anti-bikeshedding Kotlin linter with built-in formatter
https://pinterest.github.io/ktlint/
MIT License
6.26k stars 506 forks source link

Single-line chain method calls with operator #2712

Closed hanggrian closed 5 months ago

hanggrian commented 5 months ago

I have two strings, both are single-line chain method calls that do not violate the max lines rule.

val left = result.value.drop(1).dropLast(1).lowercase()
val right = result.value.last()

When joined together with + operator, chain method continuation rule treats them as one statement. It demanded that the first chain be wrapped, but not the rest.

Expected Behavior

When there are mathematical and comparison operators, chained calls of each operand should be inspected individually. Alternatively, force multi-lining on the rest of the operands.

val s =
    result.value.drop(1).dropLast(1).lowercase() +
        result.value.last()

// or

val s =
    result.value
        .drop(1)
        .dropLast(1)
        .lowercase() +
        result.value
                .last()

Current Behavior

val s =
    result.value
        .drop(1)
        .dropLast(1)
        .lowercase() +
        result.value.last()

Additional information

paul-dingemans commented 5 months ago

When there are mathematical and comparison operators, chained calls of each operand should be inspected individually. Alternatively, force multi-lining on the rest of the operands.

This is exactly how the rule works. The outmost expression (parenthesis added for clarification) is (result.value.drop(1).dropLast(1).lowercase()) + (result.value.last()).

The first part of this expression, result.value.drop(1).dropLast(1).lowercase(), contains 4 chain operators, which as documented result in writing this expression as a multiline expression. The second subexpression has less than 4 chain operators, and as of that may be written as a single line.

When the first subexpression is shortened by remove one chain operation, you will see that the example is accepted by ktlint unchanged:

val s =
    result.value.drop(1).dropLast(1) +
        result.value.last()
hanggrian commented 5 months ago

Hi Paul, thanks for clarifying. I missed the documented configuration of 4 chain method calls. However, explicitly setting ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than to unset seems to have no effect in my test. It will revert back to 4 while the document states that it should disable the setting.

Using the original code, I can only pass the check when ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than is set to 5 or more.

val s =
    result.value.drop(1).dropLast(1).lowercase() +
        result.value.last()
paul-dingemans commented 5 months ago

However, explicitly setting ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than to unset seems to have no effect in my test. It will revert back to 4 while the document states that it should disable the setting

That is a good find. The code indeed sets the value to 4 whenever the property is unset. It should have set the value in that case to Int.MAX_VALUE.