pinterest / ktlint

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

Exception in `standard:argument-list-wrapping` when last line has tralling space and next line contains function call #2682

Closed LemonNekoGH closed 3 weeks ago

LemonNekoGH commented 3 weeks ago

Expected Behavior

I use · stands for space. Before I use ktlint -F:

fun a() {
··var·a·=·10····
··println(a)
}

After:

fun a() {
··var·a·=·10
··println(a)
}

Observed Behavior

I got an Exception:

Code.kt:3:12: Internal Error (rule 'standard:argument-list-wrapping') in Code.kt at position '3:12. Please create a ticket at https://github.com/pinterest/ktlint/issues and provide the source code that triggered an error.
com.pinterest.ktlint.rule.engine.api.KtLintRuleException: Rule 'standard:argument-list-wrapping' throws exception in file 'Code.kt' at position (3:12)
   Rule maintainer: KtLint
   Issue tracker  : https://github.com/pinterest/ktlint/issues
   Repository     : https://github.com/pinterest/ktlint
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:65)
        at com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$lint$3.invoke(KtLintRuleEngine.kt:100)
        at com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$lint$3.invoke(KtLintRuleEngine.kt:99)
        at com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:46)
        at com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:44)
        at com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.lint(KtLintRuleEngine.kt:99)
        at com.pinterest.ktlint.cli.internal.KtlintCommandLine.lint(KtlintCommandLine.kt:554)
        at com.pinterest.ktlint.cli.internal.KtlintCommandLine.process(KtlintCommandLine.kt:464)
        at com.pinterest.ktlint.cli.internal.KtlintCommandLine.access$process(KtlintCommandLine.kt:68)
        at com.pinterest.ktlint.cli.internal.KtlintCommandLine$lintFiles$3.invoke$lambda$0(KtlintCommandLine.kt:402)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.IllegalArgumentException: First node in non-empty sequence must be a whitespace containing a newline
        at com.pinterest.ktlint.rule.engine.core.api.ASTNodeExtensionKt.lineLengthWithoutNewlinePrefix(ASTNodeExtension.kt:505)
        at com.pinterest.ktlint.rule.engine.core.api.ASTNodeExtensionKt.lineLength(ASTNodeExtension.kt:497)
        at com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule.exceedsMaxLineLength(ArgumentListWrappingRule.kt:132)
        at com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule.needToWrapArgumentList(ArgumentListWrappingRule.kt:127)
        at com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule.beforeVisitChildNodes(ArgumentListWrappingRule.kt:107)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:125)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:124)
        at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle(SuppressHandler.kt:28)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:124)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:93)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:30)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:132)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle(SuppressHandler.kt:28)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:93)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:30)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:132)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle(SuppressHandler.kt:28)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:93)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:30)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:132)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle(SuppressHandler.kt:28)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:93)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:30)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:132)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle(SuppressHandler.kt:28)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:131)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:93)
        at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:62)
        ... 13 more
 ()

If next line does not contains a function call, the exception gone, examples:

fun a() {
··var·a·=·10····
··a·=·20
}

fun b() {
··var·a·=·10····
··if·(a·==·10)·a·=·20
}

Your Environment

[*.{kt,kts}] trim_trailing_whitespace = true ktlint_code_style = android_studio


* I use command line interface, installed by `brew install ktlint`
* Operating System and version: macOS 14.5 (23F79)
paul-dingemans commented 3 weeks ago

I cannot reproduce the problem with given code sample and .editorconfig. I am familiar with this specific exception and have made some adjustments in the upcoming release. Did you actually try to reproduce with this code sample, or is it an abstraction of the real code in which you found the problem?

LemonNekoGH commented 3 weeks ago

Oh, sorry, I will check my reproduce code again, and I found that other authors of issue used ktlint's API to verify the issue, I will learn to use it. By the way, my .editorconfig file contains trim_trailing_whitespace = true, and VSCode will trim trailing whitespace on file save when this line exists.

LemonNekoGH commented 3 weeks ago

Oh, sorry again, I found a mistake in my description, the command ktlint with option -F won't produce the exception, but produced by command ktlint without any options. I will still learn to how to verify this issue in testing code.

And expected behavior when run ktlint without any options:

Code.kt:2:15: Trailing space(s) (standard:no-trailing-spaces)
Code.kt:2:16: Unnecessary long whitespace (standard:no-multi-spaces)
LemonNekoGH commented 3 weeks ago

I have tried to add these tests to ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt:

    // This test failed with com.pinterest.ktlint.rule.engine.api.KtLintRuleException
    @Test
    fun `It will throws an exception`() {
        val code = """
        fun a() {
            var a = 10    
            println(a)
        }
        """.trimIndent()

        argumentListWrappingRuleAssertThat(code)
            .hasNoLintViolations()
    }

    // This test passed.
    @Test
    fun `It won't throw an exception`() {
        val code = """
        fun a() {
            var a = 10    
            a = 20
        }
        """.trimIndent()

        argumentListWrappingRuleAssertThat(code)
            .hasNoLintViolations()
    }
paul-dingemans commented 3 weeks ago

To avoid that trailing spaces are removed, you should use $SPACE variable to retain them. For example:

    @Test
    fun `It will throws an exception`() {
        val code =
            """
            fun a() {
                var a = 10$SPACE$SPACE$SPACE$SPACE
                println(a)
            }
            """.trimIndent()

        argumentListWrappingRuleAssertThat(code)
            .hasNoLintViolations()
    }

This test however still succeeds. So, I don't get how you get an exception.

LemonNekoGH commented 3 weeks ago

Thank you for your notice, I have replaced space to variable, but exception still appeared.

I have done these steps to add and run test code:

  1. Check out ktlint repository.
  2. Check out tag 1.2.1
  3. Run ./gradlew to configure the project.
  4. Add test code.
  5. Run ./gradlew :ktlint-ruleset-standard:test

I don't know which step is wrong...Or, maybe I lost some steps?

LemonNekoGH commented 3 weeks ago

I will try to reproduce on other machine later.

paul-dingemans commented 3 weeks ago

Check out tag 1.2.1

Why checkout the 1.2.1 tag? Just create a new branch from master as otherwise you will miss all changes that have been added since the 1.2.1 release.

Other things that you can do:

LemonNekoGH commented 3 weeks ago

I use cli version 1.2.1 so I checkout tag 1.2.1 to keep environment identical.

I know my mistake now, if the bug fixed in new commit, this bug report will be meaningless.

Thank you, I will try to reproduce in latest commit.

LemonNekoGH commented 3 weeks ago

I didn't reproduce this problem in the latest commit. Thank you for your help. I'm sorry for wasting your time.

paul-dingemans commented 3 weeks ago

I didn't reproduce this problem in the latest commit. Thank you for your help. I'm sorry for wasting your time.

No problem. We both learned something ;-)