In nbadal/ktlint-intellij-plugin#425 it was reported that the compose rules are not working in the new ktlint-intellij-plugin while they do work in Ktlint CLI.
Post.kt:
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun Post() {
Text("foo")
}
.editorconfig:
root = true
[*.{kt,kts}]
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ktlint_function_naming_ignore_when_annotated_with=Composable
Output with ktlint CLI 1.1.0
:
/opt/homebrew/bin/ktlint -F -R <path>/scripts/ktlint-compose-0.3.8-all.jar .../Post.kt
<path>/Post.kt:11:5: This @Composable function emits content but doesn't have a modifier parameter.
See https://mrmans0n.github.io/compose-rules/rules/#when-should-i-expose-modifier-parameters for more information. (compose:modifier-missing-check)
Summary error count (descending) by rule:
compose:modifier-missing-check: 1
Process finished with exit code 1
The lint violation is however not reported by the ktlint-intellij-plugin
.
After investigation, I have found that the usages of KtStubElementTypes
in KtlintRule
is the most likely cause of the problem.
when (node.elementType) {
KtStubElementTypes.FILE -> {
psi.attach(config)
visitFile(psi as KtFile, autoCorrect, emit.toEmitter())
}
KtStubElementTypes.CLASS -> visitClass(psi as KtClass, autoCorrect, emit.toEmitter())
KtStubElementTypes.FUNCTION -> {
val function = psi as KtFunction
val emitter = emit.toEmitter()
visitFunction(function, autoCorrect, emitter)
if (function.isComposable) {
visitComposable(function, autoCorrect, emitter)
}
}
}
Here the node.elementType
is checked against KtStubElementTypes
instead of using ElementType
. This might also be used in other classes.
To proof that above assumption is the cause of the problem, I have build a new simple rule in a new custom ruleset in ktlint:
class NoFooFun :
Rule(
ruleId = RuleId("$CUSTOM_RULE_SET_ID:no-foo-fun"),
about =
About(
maintainer = "Your name",
repositoryUrl = "https://github.com/your/project/",
issueTrackerUrl = "https://github.com/your/project/issues",
),
) {
override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
// Compose ruleset uses KtStubElementTypes in KtlintRule class to match element types. This works fine when rules are used with
// ktlint CLI as the embeddable kotlin compiler is used.
if (node.elementType == KtStubElementTypes.FUNCTION) {
node
.findChildByType(ElementType.IDENTIFIER)
?.takeIf { it.text == "foo" }
?.let {
emit(node.startOffset, "Unexpected fun foo (via KtStubElementTypes.FUNCTION)", false)
}
}
// However, when the ruleset is used in combination with the ktlint-intelli-plugin, the KtStubElementTypes are no longer recognized.
// Most likely this is caused by interoperability problems between the embedded kotlin compiler in the KtLintRuleEngine versus the
// Kotlin compiler in IntelliJ IDEA.
if (node.elementType == ElementType.FUN) {
node
.findChildByType(ElementType.IDENTIFIER)
?.takeIf { it.text == "foo" }
?.let {
emit(node.startOffset, "Unexpected fun foo (via elementType.FUN)", false)
}
}
}
}
Given code sample below:
fun foo() {
// do something
}
and ktlint CLI 1.1.0
this produces:
$ ktlint-1.1.0 --relative -R ~/Downloads/ktlint-custom-ruleset-example-1.1.0.jar
15:21:12.813 [main] INFO com.pinterest.ktlint.cli.internal.KtlintCommandLine -- Enable default patterns [**/*.kt, **/*.kts]
src/main/kotlin/Foo.kt:1:1: Unexpected fun foo (via KtStubElementTypes.FUNCTION) (custom-rule-set-id:no-foo-fun)
src/main/kotlin/Foo.kt:1:1: Unexpected fun foo (via elementType.FUN) (custom-rule-set-id:no-foo-fun)
Summary error count (descending) by rule:
custom-rule-set-id:no-foo-fun: 2
The problem is reported twice, as was to be expected. The embeddable kotlin compiler in Ktlint CLI can match the element type of the node both to 'ElementType.FUNand
KtStubElementTypes.FUNCTION`.
When using the same ruleset jar in ktlint-intellij-plugin
, following problems are reported:
As you can see, the problem is now only reported as it matches with ElementType.FUN
. I am not 100% sure about the reason, but I believe that the KtStubElementTypes
are not compatible with the IDEA kotlin compiler.
Can you provide a snapshot version of the compose ruleset that uses ElementType
instead of KtStubElementTypes
so that we can verify whether this resolves the problem?