pinterest / ktlint

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

NullPointerException when Ktlint runs the rule `multiline-expression-wrapping` during formatting #2297

Closed Nenrikido closed 1 year ago

Nenrikido commented 1 year ago

Expected Behavior

The rule should at least pass without errors and tell me if there are linting issues or not

Observed Behavior

When running the command mvn antrun:run@ktlint-format with this configuration in the pom.xml :

<configuration>
    <target name="ktlint">
        <java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
              classpathref="maven.plugin.classpath" classname="com.pinterest.ktlint.Main">
            <arg value="-F"/>
            <arg value="src/**/*.kt"/>
            <arg value="!src/**/test/**"/>
            <arg value="--disabled_rules=enum-entry-name-case,function-naming,no-consecutive-comments,comment-wrapping,package-name,no-empty-file,property-naming,filename"/>
        </java>
    </target>
</configuration>

I'm getting the error :

Internal Error (rule 'standard:multiline-expression-wrapping') in DataFrameImpl.kt at position '0:0. Please create a ticket at https://github.com/pinterest/ktlint/issues and provide the source code that triggered an error.        
[INFO]    [ktlint] com.pinterest.ktlint.rule.engine.api.KtLintRuleException: Rule 'standard:multiline-expression-wrapping' throws exception in file 'DataFrameImpl.kt' at position (0:0)
...
Caused by: java.lang.NullPointerException
[INFO]    [ktlint]      at com.pinterest.ktlint.ruleset.standard.rules.MultilineExpressionWrappingRule.isElvisOperator(MultilineExpressionWrappingRule.kt:155)
...

(I copy-pasted the interesting parts of the stack trace but if you want the full stack-trace, please tell me)

Steps to Reproduce

I'm linking to this issue 2 files where the exception arises at position 0:0, since i don't know where the bug comes from exactly in the files, I can't reduce the size of the code sample. Sorry for that KtFilesWithError.zip

My Environment

paul-dingemans commented 1 year ago

The problematic statement is following:

        return columns.size == otherColumns.size
                && columns.all { col ->
            val otherCol = otherColumns.firstOrNull { c -> c.name() == col.name() } ?: return@all false
            val result = col == otherCol
            if (!result) {
                println("column this[${col.name()}] != other[${otherCol.name()}]")
                println("this =\n${col}")
                println("other =\n${otherCol}")
            }
            result
        }

and specifically, the line starting with &&. The problem is caused by the chain-wrapping rule merges the && with the previous line, but malforms the AST while doing so. Because of the malformed AST, the MultilineExpressionWrappingRule throws a nullpointer exception.

Change your code as follows to work around this problem for now:

    override fun equals(other: Any?): Boolean {
        val df = other as? DataFrame ?: return false
        val otherColumns = df.columns()
        return columns.size ==
                otherColumns.size &&
                columns.all { col ->
            val otherCol = otherColumns.firstOrNull { c -> c.name() == col.name() } ?: return@all false
            val result = col == otherCol
            if (!result) {
                println("column this[${col.name()}] != other[${otherCol.name()}]")
                println("this =\n${col}")
                println("other =\n${otherCol}")
            }
            result
        }
    }

The problem above can be reproduced with simplified example:

            fun foo(): Boolean {
                return true
                        &&
                    columns.all { col ->
                        false
                    }
            }
Nenrikido commented 1 year ago

Thanks for the quick answer, the insights, and the workaround. I will apply it in waiting for the fix.