saveourtool / diktat

Strict coding standard for Kotlin and a custom set of rules for detecting code smells, code style issues and bugs
https://diktat.saveourtool.com
MIT License
544 stars 39 forks source link

`WRONG_INDENTATION` rule behaving incorrectly with superclass constructor call arguments #1404

Open 0x6675636b796f75676974687562 opened 2 years ago

0x6675636b796f75676974687562 commented 2 years ago

Both this

class Klass : Base("foo")

and this superclass constructor calls are indented correctly, in full accordance with 3.3. Indentation:

class Klass :
    Base("foo")

Now consider we want to split the superclass constructor call arguments into multiple lines:

class Klass : Base(
    "foo"
)

Even this is correct, too. Now, let's put a newline before the superclass call:

class Klass :
    Base(
        "foo"
    )

DiKTat will immediately complain:

Klass.kt:3:1: [WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 4 but was 8 (diktat-ruleset:indentation)
Klass.kt:4:1: [WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 0 but was 4 (diktat-ruleset:indentation)

So apparently DiKTat expects the following formatting (outright wrong):

class Klass :
    Base(
    "foo"
)

Steps to Reproduce

As it's obvious from the above example, two conditions must be met for the issue to manifest itself:

  1. The supertype constructor call should be put on a separate line.
  2. The supertype constructor call arguments should be put on individual lines.

There're more real-life examples which trigger this issue, e. g.:

class Klass :
    Base(
        "foo",
        ::Klass,
        listOf(
            "foo",
            "bar",
            "baz"
        )
    ),
    Cloneable,
    CharSequence {
    // ...
}

or even

class IndentationRuleFixTest :
    FixTestBase(
        "test/paragraph3/indentation",
        ::IndentationRule,
        listOf(
            RulesConfig(WRONG_INDENTATION.name, true,
                mapOf(
                    "newlineAtEnd" to "true",
                    "extendedIndentOfParameters" to "true",
                    "alignedParameters" to "true",
                    "extendedIndentAfterOperators" to "true",
                    "extendedIndentBeforeDot" to "true",
                )
            )
        )
    ),
    IndentationRuleTestMixin,
    IndentationRuleTestResources {
    // ...
}

Environment information

0x6675636b796f75676974687562 commented 2 years ago

If we look at this particular example:

class Klass :
    Base(
        "foo",
        ::Klass,
        listOf(
            "foo",
            "bar",
            "baz"
        )
    ),
    Cloneable,
    CharSequence {
    // ...
}

— it can be reformatted like this, triggering only a WRONG_NEWLINES warning and no WRONG_INDENTATION warnings (which is the expected behaviour):

class Klass : Base(
    "foo",
    ::Klass,
    listOf(
        "foo",
        "bar",
        "baz"
    )
), Cloneable, CharSequence {
    // ...
}

— or like this, triggering false positive WRONG_INDENTATION warnings on lines 10-11:

class Klass : Base(
    "foo",
    ::Klass,
    listOf(
        "foo",
        "bar",
        "baz"
    )
),
    Cloneable,
    CharSequence {
    // ...
}
Klass.kt:10:1: [WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 0 but was 4 (diktat-ruleset:indentation)
Klass.kt:11:1: [WRONG_INDENTATION] only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed): expected 0 but was 4 (diktat-ruleset:indentation)
orchestr7 commented 2 years ago

Important, let's do it! But not break anything else :)

0x6675636b796f75676974687562 commented 2 years ago

More examples:

class C(private val property: (rulesConfigList: List<RulesConfig>) -> Rule,
        arg1: List<RulesConfig>?) :
    Base(
        id = DIKTAT_RULE_SET_ID,
        about = NO_ABOUT,
    ) {
}

and

class C
@JvmOverloads
constructor(private val property: DiktatRuleSetFactory = DiktatRuleSetFactory()) :
    Base(
        id = DIKTAT_RULE_SET_ID,
        about = About(
            maintainer = "Diktat",
            description = "Strict coding standard for Kotlin and a custom set of rules for detecting code smells, code style issues, and bugs",
            license = "https://github.com/saveourtool/diktat/blob/master/LICENSE",
            repositoryUrl = "https://github.com/saveourtool/diktat",
            issueTrackerUrl = "https://github.com/saveourtool/diktat/issues",
        ),
    ) {
}