From ae68d3614799854257a5a4165dc765f5dea0da7f Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Tue, 14 Jul 2026 11:49:33 +0200 Subject: [PATCH] Kotlin: read KDoc sections via Kotlin PSI, not com.intellij.psi navigation The previous two commits removed the `ProcessCanceledException` and `PsiTreeUtil` references from KDoc-section parsing, but the internal extractor build then failed on `unresolved reference 'PsiElement'`: the hand-written tree walk named `com.intellij.psi.PsiElement` (and its `firstChild`/`nextSibling`). The root cause is that the LighterAST/K2 comment extractor is compiled against a curated classpath that deliberately excludes `com.intellij.psi.*`. It exposes only the `com.intellij.lang` LighterAST API (`LighterASTNode`, `FlyweightCapableTreeStructure`) plus the Kotlin PSI classes (`org.jetbrains.kotlin.psi.*`, `...kdoc.psi.*`). That is why `KtPsiFactory`, `KtFile`, `KDoc` and `getAllSections` all resolve while any `com.intellij.psi` reference does not. The full standalone builds bundle the whole classpath, so they never caught this. Locate the parsed KDoc through the Kotlin PSI API instead: the KDoc attaches to the throwaway `val __codeql_kdoc__` as its `docComment`, so `ktFile.declarations.firstOrNull()?.docComment` retrieves it using only `org.jetbrains.kotlin.psi` types (`KtCommonFile.getDeclarations`, `KtDeclaration.getDocComment`). No `com.intellij.psi` symbol remains in the file's code. Behaviour is unchanged: the parsed input is a single KDoc comment followed by one declaration, so its `docComment` is exactly the KDoc the old descendant search returned. No expected-output changes. Verified the standalone extractor builds for 1.9.0-Beta and 2.4.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CommentExtractorLighterAST.kt | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_9_0-Beta/CommentExtractorLighterAST.kt b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_9_0-Beta/CommentExtractorLighterAST.kt index 9eb931cf930..2c36f2ce6b0 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_9_0-Beta/CommentExtractorLighterAST.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_1_9_0-Beta/CommentExtractorLighterAST.kt @@ -52,36 +52,22 @@ class CommentExtractorLighterAST( } } - // Depth-first search for the first KDoc descendant of `element`. We navigate - // the PSI tree by hand rather than using `com.intellij.psi.util.PsiTreeUtil`, - // because that utility class is not present on every supported compiler's - // embeddable classpath (the reduced classpath used by some builds strips it), - // whereas the core `PsiElement` navigation API is always available. - private fun findKDocDescendant( - element: com.intellij.psi.PsiElement - ): org.jetbrains.kotlin.kdoc.psi.api.KDoc? { - if (element is org.jetbrains.kotlin.kdoc.psi.api.KDoc) { - return element - } - var child = element.firstChild - while (child != null) { - findKDocDescendant(child)?.let { - return it - } - child = child.nextSibling - } - return null - } - private fun parseKDocSections( commentText: String ): List? { val factory = ktPsiFactory ?: return null return try { // A KDoc is only recognised as a doc comment when it precedes a - // declaration, so we append a throwaway declaration before parsing. + // declaration, so we append a throwaway declaration before parsing; the + // KDoc then attaches to that declaration as its `docComment`. We read it + // via the `org.jetbrains.kotlin.psi` API (`KtFile.declarations` and + // `KtDeclaration.docComment`) rather than a generic PSI-tree walk, because + // the LighterAST/K2 extractor is compiled against a curated classpath that + // exposes the Kotlin PSI classes but not `com.intellij.psi.*` (only the + // `com.intellij.lang` LighterAST API). Referencing `com.intellij.psi` + // types here breaks that build. val ktFile = factory.createFile("$commentText\nval __codeql_kdoc__ = 0") - findKDocDescendant(ktFile)?.getAllSections() + ktFile.declarations.firstOrNull()?.docComment?.getAllSections() } catch (e: Exception) { // Never swallow IntelliJ's ProcessCanceledException: it is a control-flow // exception that must propagate for cancellation to work. We match it by