Repository: Kotlin/dokka (kotlin)

Issue:
NPE in javadoc parsing with K2 dokka
In trying to switch dependencies from `org.jetbrains.dokka:analysis-kotlin-descriptors` to `org.jetbrains.dokka:analysis-kotlin-symbols`, I'm seeing an error in javadoc parsing for the following code:
```
public class Foo {
    /**
     * {@link List<I>}
     */
    public <I> List<I> foo() {
        return null;
    }
}
```

The error is:
```
Cannot invoke "com.intellij.psi.javadoc.JavadocManager.getTagInfo(String)" because the return value of "com.intellij.psi.javadoc.JavadocManager.getInstance(com.intellij.openapi.project.Project)" is null
java.lang.NullPointerException: Cannot invoke "com.intellij.psi.javadoc.JavadocManager.getTagInfo(String)" because the return value of "com.intellij.psi.javadoc.JavadocManager.getInstance(com.intellij.openapi.project.Project)" is null
	at com.intellij.psi.impl.source.javadoc.PsiDocTagValueImpl.getReference(PsiDocTagValueImpl.java:49)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter$WithContext.toDocumentationLinkString(PsiElementToHtmlConverter.kt:149)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter$WithContext.toHtml(PsiElementToHtmlConverter.kt:112)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter$WithContext.parseHtmlOfSimpleElement(PsiElementToHtmlConverter.kt:85)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter$WithContext.parseHtml(PsiElementToHtmlConverter.kt:74)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter$WithContext.convert(PsiElementToHtmlConverter.kt:64)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiElementToHtmlConverter.convert(PsiElementToHtmlConverter.kt:54)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiDocTagParser.parse(PsiDocTagParser.kt:36)
	at org.jetbrains.dokka.analysis.java.parsers.doctag.PsiDocTagParser.parseAsParagraph(PsiDocTagParser.kt:26)
	at org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser.getDescription(JavaDocCommentParser.kt:49)
	at org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser.parsePsiDocComment$analysis_java_psi(JavaDocCommentParser.kt:41)
	at org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser.parse(JavaDocCommentParser.kt:37)
	at org.jetbrains.dokka.analysis.java.parsers.JavadocParser.parseDocumentation(JavadocParser.kt:26)
	at org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser.getDocumentation(DokkaPsiParser.kt:486)
	at org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser.parseFunction(DokkaPsiParser.kt:426)
	at org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser.parseFunction$default(DokkaPsiParser.kt:417)
	at org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser$parseClasslike$2$1$allFunctions$1$invokeSuspend$$inlined$parallelMapNotNull$1$1.invokeSuspend(parallelCollectionOperations.kt:21)
```

This javadoc didn't really get parsed with K1 either (it just became `&lt;`) either, but there wasn't an exception.

**Dokka configuration**
Dokka version 2.0.20-dev-388
Kotlin version 2.1.10

Pull request:
In K1, we register IDE's service `JavadocManager`, we could do the same in K2:
`registerProjectService(JavadocManager::class.java, JavadocManagerImpl(project))`
However, it seems unnecessary, since the link is unresolved in K1 as well.

Patch:
diff --git a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt
index 11c56fb3a7..eadbab6429 100644
--- a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt
+++ b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt
@@ -7,7 +7,6 @@ package org.jetbrains.dokka.analysis.java.util
 import com.intellij.psi.JavaDocTokenType
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiJavaCodeReferenceElement
-import com.intellij.psi.PsiWhiteSpace
 import com.intellij.psi.impl.source.tree.JavaDocElementType
 import com.intellij.psi.javadoc.PsiDocComment
 import com.intellij.psi.javadoc.PsiDocTag
@@ -44,7 +43,14 @@ internal fun PsiElement.referenceElementOrSelf(): PsiElement? =
     } else this
 
 internal fun PsiDocTag.linkElement(): PsiElement? =
-    valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace }
+    dataElements.firstOrNull {
+        /**
+         * * According to the [com.intellij.java.syntax.parser.JavaDocParser],
+         * no other node is expected here for the reference (link)
+         */
+        it.node.elementType == JavaDocElementType.DOC_REFERENCE_HOLDER ||
+                it.node.elementType == JavaDocElementType.DOC_METHOD_OR_FIELD_REF
+    }
 
 internal fun PsiElement.defaultLabel() = children.firstOrNull {
     it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken()


Test patch:
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/translators/JavadocParserTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/translators/JavadocParserTest.kt
index 4d8de49a00..8ca0083ed5 100644
--- a/dokka-subprojects/plugin-base/src/test/kotlin/translators/JavadocParserTest.kt
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/translators/JavadocParserTest.kt
@@ -8,15 +8,68 @@ import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
 import org.jetbrains.dokka.links.DRI
 import org.jetbrains.dokka.model.DModule
 import org.jetbrains.dokka.model.childrenOfType
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.model.DFunction
 import org.jetbrains.dokka.model.doc.*
 import org.jetbrains.dokka.model.firstChildOfType
 import org.jetbrains.dokka.model.firstMemberOfType
 import utils.text
 import kotlin.test.Test
 import kotlin.test.assertEquals
+import kotlin.test.assertNull
 
 class JavadocParserTest : BaseAbstractTest() {
 
+    @Test
+    fun `should not fail for javadoc links with type parameters #4117`() {
+        val configuration = dokkaConfiguration {
+            sourceSets {
+                sourceSet {
+                    sourceRoots = listOf("src/main/java")
+                }
+            }
+        }
+
+        testInline(
+            """
+            /src/main/java/sample/Foo.java
+            import java.util.List;
+
+            public class Foo {
+                /**
+                 * {@link List<I>}
+                 * @see List<I>
+                 */
+                public <I> List<I> foo() {
+                    return null;
+                }
+            }
+            """.trimIndent(),
+            configuration
+        ) {
+            documentablesMergingStage = {
+                val fn = it.dfs {
+                    it.name == "foo"
+                } as DFunction
+                val  description = fn.documentation.values.single().children[0] as Description
+                val  see = fn.documentation.values.single().children[1] as See
+
+                with(description.dfs { it is DocumentationLink } as DocumentationLink)  {
+                    assertEquals("List", dri.classNames)
+                    assertEquals("java.util", dri.packageName)
+                    assertNull(dri.callable)
+                    assertEquals("List", this.text())
+                }
+                with(see)  {
+                    assertEquals("List", address?.classNames)
+                    assertEquals("java.util", address?.packageName)
+                    assertNull(address?.callable)
+                    assertEquals("java.util.List", name)
+                }
+            }
+        }
+    }
+
     private fun performJavadocTest(testOperation: (DModule) -> Unit) {
         val configuration = dokkaConfiguration {
             sourceSets {