From 9559158bfeeb274e9ccf1b4563f1b23b42afc493 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Wed, 5 Jul 2023 10:04:55 +0200 Subject: Decompose Kotlin/Java analysis (#3034) * Extract analysis into separate modules --- subprojects/analysis-java-psi/README.md | 5 + .../analysis-java-psi/api/analysis-java-psi.api | 148 ++++ subprojects/analysis-java-psi/build.gradle.kts | 18 + .../java/DefaultPsiToDocumentableTranslator.kt | 83 +++ .../dokka/analysis/java/JavaAnalysisPlugin.kt | 106 +++ .../jetbrains/dokka/analysis/java/JavadocTag.kt | 48 ++ .../java/SynheticElementDocumentationProvider.kt | 42 ++ .../dokka/analysis/java/doccomment/DocComment.kt | 14 + .../analysis/java/doccomment/DocCommentCreator.kt | 9 + .../analysis/java/doccomment/DocCommentFactory.kt | 20 + .../analysis/java/doccomment/DocCommentFinder.kt | 64 ++ .../java/doccomment/DocumentationContent.kt | 11 + .../analysis/java/doccomment/JavaDocComment.kt | 84 +++ .../java/doccomment/JavaDocCommentCreator.kt | 11 + .../java/doccomment/PsiDocumentationContent.kt | 22 + .../java/parsers/CommentResolutionContext.kt | 9 + .../analysis/java/parsers/DocCommentParser.kt | 12 + .../dokka/analysis/java/parsers/DokkaPsiParser.kt | 797 +++++++++++++++++++++ .../analysis/java/parsers/JavaDocCommentParser.kt | 228 ++++++ .../dokka/analysis/java/parsers/JavadocParser.kt | 24 + .../java/parsers/doctag/DocTagParserContext.kt | 47 ++ .../java/parsers/doctag/HtmlToDocTagConverter.kt | 114 +++ .../parsers/doctag/InheritDocTagContentProvider.kt | 10 + .../java/parsers/doctag/InheritDocTagResolver.kt | 114 +++ .../java/parsers/doctag/PsiDocTagParser.kt | 39 + .../parsers/doctag/PsiElementToHtmlConverter.kt | 214 ++++++ .../dokka/analysis/java/util/CoreCopyPaste.kt | 20 + .../dokka/analysis/java/util/NoopIntellijLogger.kt | 43 ++ .../analysis/java/util/PropertiesConventionUtil.kt | 101 +++ .../java/util/PsiAccessorConventionUtil.kt | 98 +++ .../dokka/analysis/java/util/PsiCommentsUtils.kt | 49 ++ .../jetbrains/dokka/analysis/java/util/PsiUtil.kt | 119 +++ .../dokka/analysis/java/util/StdlibUtil.kt | 33 + .../dokka/analysis/java/util/resolveToGetDri.kt | 7 + .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + 35 files changed, 2764 insertions(+) create mode 100644 subprojects/analysis-java-psi/README.md create mode 100644 subprojects/analysis-java-psi/api/analysis-java-psi.api create mode 100644 subprojects/analysis-java-psi/build.gradle.kts create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavaDocCommentParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/JavadocParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/HtmlToDocTagConverter.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagResolver.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiDocTagParser.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/doctag/PsiElementToHtmlConverter.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/CoreCopyPaste.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/NoopIntellijLogger.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PropertiesConventionUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiAccessorConventionUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiCommentsUtils.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/PsiUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/StdlibUtil.kt create mode 100644 subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/util/resolveToGetDri.kt create mode 100644 subprojects/analysis-java-psi/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin (limited to 'subprojects/analysis-java-psi') diff --git a/subprojects/analysis-java-psi/README.md b/subprojects/analysis-java-psi/README.md new file mode 100644 index 00000000..d2bbd080 --- /dev/null +++ b/subprojects/analysis-java-psi/README.md @@ -0,0 +1,5 @@ +# Analysis: Java PSI + +An internal module for parsing Java sources. Defines no stable public API and is not published anywhere. + +Used by the Kotlin analysis artifacts to provide support for mixed-language (Kotlin+Java) projects. diff --git a/subprojects/analysis-java-psi/api/analysis-java-psi.api b/subprojects/analysis-java-psi/api/analysis-java-psi.api new file mode 100644 index 00000000..404249f8 --- /dev/null +++ b/subprojects/analysis-java-psi/api/analysis-java-psi.api @@ -0,0 +1,148 @@ +public final class org/jetbrains/dokka/analysis/java/AuthorJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/AuthorJavadocTag; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/BreakingAbstractionKotlinLightMethodChecker { + public abstract fun isLightAnnotation (Lcom/intellij/psi/PsiAnnotation;)Z + public abstract fun isLightAnnotationAttribute (Lcom/intellij/lang/jvm/annotation/JvmAnnotationAttribute;)Z +} + +public final class org/jetbrains/dokka/analysis/java/DeprecatedJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/DeprecatedJavadocTag; +} + +public final class org/jetbrains/dokka/analysis/java/DescriptionJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/DescriptionJavadocTag; +} + +public final class org/jetbrains/dokka/analysis/java/ExceptionJavadocTag : org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag { + public static final field Companion Lorg/jetbrains/dokka/analysis/java/ExceptionJavadocTag$Companion; + public static final field name Ljava/lang/String; + public fun (Ljava/lang/String;)V +} + +public final class org/jetbrains/dokka/analysis/java/ExceptionJavadocTag$Companion { +} + +public final class org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { + public fun ()V + public final fun getDocCommentCreators ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getDocCommentFinder ()Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder; + public final fun getDocCommentParsers ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getInheritDocTagContentProviders ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getKotlinLightMethodChecker ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getProjectProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; + public final fun getSourceRootsExtractor ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; +} + +public abstract class org/jetbrains/dokka/analysis/java/JavadocTag { + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getName ()Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/java/ParamJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field Companion Lorg/jetbrains/dokka/analysis/java/ParamJavadocTag$Companion; + public static final field name Ljava/lang/String; + public fun (Lcom/intellij/psi/PsiMethod;Ljava/lang/String;I)V + public final fun getMethod ()Lcom/intellij/psi/PsiMethod; + public final fun getParamIndex ()I + public final fun getParamName ()Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/java/ParamJavadocTag$Companion { +} + +public abstract interface class org/jetbrains/dokka/analysis/java/ProjectProvider { + public abstract fun getProject (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;)Lcom/intellij/openapi/project/Project; +} + +public final class org/jetbrains/dokka/analysis/java/ReturnJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/ReturnJavadocTag; +} + +public final class org/jetbrains/dokka/analysis/java/SeeJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field Companion Lorg/jetbrains/dokka/analysis/java/SeeJavadocTag$Companion; + public static final field name Ljava/lang/String; + public fun (Ljava/lang/String;)V + public final fun getQualifiedReference ()Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/java/SeeJavadocTag$Companion { +} + +public final class org/jetbrains/dokka/analysis/java/SinceJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public static final field INSTANCE Lorg/jetbrains/dokka/analysis/java/SinceJavadocTag; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/SourceRootsExtractor { + public abstract fun extract (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;Lorg/jetbrains/dokka/plugability/DokkaContext;)Ljava/util/List; +} + +public abstract class org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag : org/jetbrains/dokka/analysis/java/JavadocTag { + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getExceptionQualifiedName ()Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/java/ThrowsJavadocTag : org/jetbrains/dokka/analysis/java/ThrowingExceptionJavadocTag { + public static final field Companion Lorg/jetbrains/dokka/analysis/java/ThrowsJavadocTag$Companion; + public static final field name Ljava/lang/String; + public fun (Ljava/lang/String;)V +} + +public final class org/jetbrains/dokka/analysis/java/ThrowsJavadocTag$Companion { +} + +public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocComment { + public abstract fun hasTag (Lorg/jetbrains/dokka/analysis/java/JavadocTag;)Z + public abstract fun resolveTag (Lorg/jetbrains/dokka/analysis/java/JavadocTag;)Ljava/util/List; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator { + public abstract fun create (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment; +} + +public final class org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory { + public fun (Ljava/util/List;)V + public final fun fromElement (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment; +} + +public final class org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder { + public fun (Lorg/jetbrains/dokka/utilities/DokkaLogger;Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory;)V + public final fun findClosestToElement (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent { + public abstract fun getTag ()Lorg/jetbrains/dokka/analysis/java/JavadocTag; + public abstract fun resolveSiblings ()Ljava/util/List; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/parsers/DocCommentParser { + public abstract fun canParse (Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;)Z + public abstract fun parse (Lorg/jetbrains/dokka/analysis/java/doccomment/DocComment;Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/model/doc/DocumentationNode; +} + +public final class org/jetbrains/dokka/analysis/java/parsers/JavadocParser : org/jetbrains/dokka/analysis/java/parsers/JavaDocumentationParser { + public fun (Ljava/util/List;Lorg/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder;)V + public fun parseDocumentation (Lcom/intellij/psi/PsiNamedElement;)Lorg/jetbrains/dokka/model/doc/DocumentationNode; +} + +public final class org/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext { + public fun ()V + public final fun getDocumentationNode (Ljava/lang/String;)Lorg/jetbrains/dokka/model/doc/DocumentationNode; + public final fun getDri (Ljava/lang/String;)Lorg/jetbrains/dokka/links/DRI; + public final fun store (Lorg/jetbrains/dokka/links/DRI;)Ljava/lang/String; + public final fun store (Lorg/jetbrains/dokka/model/doc/DocumentationNode;)Ljava/lang/String; +} + +public abstract interface class org/jetbrains/dokka/analysis/java/parsers/doctag/InheritDocTagContentProvider { + public abstract fun canConvert (Lorg/jetbrains/dokka/analysis/java/doccomment/DocumentationContent;)Z + public abstract fun convertToHtml (Lorg/jetbrains/dokka/analysis/java/doccomment/DocumentationContent;Lorg/jetbrains/dokka/analysis/java/parsers/doctag/DocTagParserContext;)Ljava/lang/String; +} + +public final class org/jetbrains/dokka/analysis/java/util/PsiDocumentableSource : org/jetbrains/dokka/model/DocumentableSource { + public fun (Lcom/intellij/psi/PsiNamedElement;)V + public fun computeLineNumber ()Ljava/lang/Integer; + public fun getPath ()Ljava/lang/String; + public final fun getPsi ()Lcom/intellij/psi/PsiNamedElement; +} + diff --git a/subprojects/analysis-java-psi/build.gradle.kts b/subprojects/analysis-java-psi/build.gradle.kts new file mode 100644 index 00000000..88d043ee --- /dev/null +++ b/subprojects/analysis-java-psi/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("org.jetbrains.conventions.kotlin-jvm") +} + +dependencies { + compileOnly(projects.core) + + api(libs.intellij.java.psi.api) + + implementation(projects.subprojects.analysisMarkdownJb) + + implementation(libs.intellij.java.psi.impl) + implementation(libs.intellij.platform.util.api) + implementation(libs.intellij.platform.util.rt) + + implementation(libs.kotlinx.coroutines.core) + implementation(libs.jsoup) +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt new file mode 100644 index 00000000..72cf45d9 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/DefaultPsiToDocumentableTranslator.kt @@ -0,0 +1,83 @@ +package org.jetbrains.dokka.analysis.java + +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiKeyword +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiModifierListOwner +import kotlinx.coroutines.coroutineScope +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.analysis.java.parsers.DokkaPsiParser +import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser +import org.jetbrains.dokka.analysis.java.parsers.JavadocParser +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.JavaVisibility +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.query +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.transformers.sources.AsyncSourceToDocumentableTranslator +import org.jetbrains.dokka.utilities.parallelMap +import org.jetbrains.dokka.utilities.parallelMapNotNull + +internal class DefaultPsiToDocumentableTranslator : AsyncSourceToDocumentableTranslator { + + override suspend fun invokeSuspending(sourceSet: DokkaSourceSet, context: DokkaContext): DModule { + return coroutineScope { + val projectProvider = context.plugin().querySingle { projectProvider } + val project = projectProvider.getProject(sourceSet, context) + + val sourceRootsExtractor = context.plugin().querySingle { sourceRootsExtractor } + val sourceRoots = sourceRootsExtractor.extract(sourceSet, context) + + val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file") + + val psiFiles = sourceRoots.parallelMap { sourceRoot -> + sourceRoot.absoluteFile.walkTopDown().mapNotNull { + localFileSystem.findFileByPath(it.path)?.let { vFile -> + PsiManager.getInstance(project).findFile(vFile) as? PsiJavaFile + } + }.toList() + }.flatten() + + val docParser = createPsiParser(sourceSet, context) + + DModule( + name = context.configuration.moduleName, + packages = psiFiles.parallelMapNotNull { it }.groupBy { it.packageName }.toList() + .parallelMap { (packageName: String, psiFiles: List) -> + docParser.parsePackage(packageName, psiFiles) + }, + documentation = emptyMap(), + expectPresentInSet = null, + sourceSets = setOf(sourceSet) + ) + } + } + + private fun createPsiParser(sourceSet: DokkaSourceSet, context: DokkaContext): DokkaPsiParser { + val projectProvider = context.plugin().querySingle { projectProvider } + val docCommentParsers = context.plugin().query { docCommentParsers } + return DokkaPsiParser( + sourceSetData = sourceSet, + project = projectProvider.getProject(sourceSet, context), + logger = context.logger, + javadocParser = JavadocParser( + docCommentParsers = docCommentParsers, + docCommentFinder = context.plugin().docCommentFinder + ), + javaPsiDocCommentParser = docCommentParsers.single { it is JavaPsiDocCommentParser } as JavaPsiDocCommentParser, + lightMethodChecker = context.plugin().querySingle { kotlinLightMethodChecker } + ) + } +} + +internal fun PsiModifierListOwner.getVisibility() = modifierList?.let { + val ml = it.children.toList() + when { + ml.any { it.text == PsiKeyword.PUBLIC } || it.hasModifierProperty("public") -> JavaVisibility.Public + ml.any { it.text == PsiKeyword.PROTECTED } || it.hasModifierProperty("protected") -> JavaVisibility.Protected + ml.any { it.text == PsiKeyword.PRIVATE } || it.hasModifierProperty("private") -> JavaVisibility.Private + else -> JavaVisibility.Default + } +} ?: JavaVisibility.Default diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt new file mode 100644 index 00000000..8884d444 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavaAnalysisPlugin.kt @@ -0,0 +1,106 @@ +package org.jetbrains.dokka.analysis.java + +import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiAnnotation +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.doccomment.DocCommentCreator +import org.jetbrains.dokka.analysis.java.doccomment.DocCommentFactory +import org.jetbrains.dokka.analysis.java.doccomment.DocCommentFinder +import org.jetbrains.dokka.analysis.java.doccomment.JavaDocCommentCreator +import org.jetbrains.dokka.analysis.java.parsers.DocCommentParser +import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagContentProvider +import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser +import org.jetbrains.dokka.analysis.java.parsers.doctag.InheritDocTagResolver +import org.jetbrains.dokka.analysis.java.parsers.doctag.PsiDocTagParser +import org.jetbrains.dokka.analysis.java.util.NoopIntellijLoggerFactory +import org.jetbrains.dokka.plugability.* +import java.io.File + + +@InternalDokkaApi +interface ProjectProvider { + fun getProject(sourceSet: DokkaSourceSet, context: DokkaContext): Project +} + +@InternalDokkaApi +interface SourceRootsExtractor { + fun extract(sourceSet: DokkaSourceSet, context: DokkaContext): List +} + +@InternalDokkaApi +interface BreakingAbstractionKotlinLightMethodChecker { + // TODO [beresnev] not even sure it's needed, but left for compatibility and to preserve behaviour + fun isLightAnnotation(annotation: PsiAnnotation): Boolean + fun isLightAnnotationAttribute(attribute: JvmAnnotationAttribute): Boolean +} + +@InternalDokkaApi +class JavaAnalysisPlugin : DokkaPlugin() { + + // single + val projectProvider by extensionPoint() + + // single + val sourceRootsExtractor by extensionPoint() + + // multiple + val docCommentCreators by extensionPoint() + + // multiple + val docCommentParsers by extensionPoint() + + // none or more + val inheritDocTagContentProviders by extensionPoint() + + // TODO [beresnev] figure out a better way depending on what it's used for + val kotlinLightMethodChecker by extensionPoint() + + private val docCommentFactory by lazy { + DocCommentFactory(query { docCommentCreators }.reversed()) + } + + val docCommentFinder by lazy { + DocCommentFinder(logger, docCommentFactory) + } + + internal val javaDocCommentCreator by extending { + docCommentCreators providing { JavaDocCommentCreator() } + } + + private val psiDocTagParser by lazy { + PsiDocTagParser( + inheritDocTagResolver = InheritDocTagResolver( + docCommentFactory = docCommentFactory, + docCommentFinder = docCommentFinder, + contentProviders = query { inheritDocTagContentProviders } + ) + ) + } + + internal val javaDocCommentParser by extending { + docCommentParsers providing { + JavaPsiDocCommentParser( + psiDocTagParser + ) + } + } + + internal val psiToDocumentableTranslator by extending { + CoreExtensions.sourceToDocumentableTranslator providing { DefaultPsiToDocumentableTranslator() } + } + + @OptIn(DokkaPluginApiPreview::class) + override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement + + private companion object { + init { + // Suppress messages emitted by the IntelliJ logger since + // there's not much the end user can do about it + Logger.setFactory(NoopIntellijLoggerFactory()) + } + } +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt new file mode 100644 index 00000000..f5cd550f --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/JavadocTag.kt @@ -0,0 +1,48 @@ +package org.jetbrains.dokka.analysis.java + +import com.intellij.psi.PsiMethod +import org.jetbrains.dokka.InternalDokkaApi + +@InternalDokkaApi +sealed class JavadocTag(val name: String) + +object AuthorJavadocTag : JavadocTag("author") +object DeprecatedJavadocTag : JavadocTag("deprecated") +object DescriptionJavadocTag : JavadocTag("description") +object ReturnJavadocTag : JavadocTag("return") +object SinceJavadocTag : JavadocTag("since") + +class ParamJavadocTag( + val method: PsiMethod, + val paramName: String, + val paramIndex: Int +) : JavadocTag(name) { + companion object { + const val name: String = "param" + } +} + +class SeeJavadocTag( + val qualifiedReference: String +) : JavadocTag(name) { + companion object { + const val name: String = "see" + } +} + +sealed class ThrowingExceptionJavadocTag( + name: String, + val exceptionQualifiedName: String? +) : JavadocTag(name) + +class ThrowsJavadocTag(exceptionQualifiedName: String?) : ThrowingExceptionJavadocTag(name, exceptionQualifiedName) { + companion object { + const val name: String = "throws" + } +} + +class ExceptionJavadocTag(exceptionQualifiedName: String?) : ThrowingExceptionJavadocTag(name, exceptionQualifiedName) { + companion object { + const val name: String = "exception" + } +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt new file mode 100644 index 00000000..d780bb40 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/SynheticElementDocumentationProvider.kt @@ -0,0 +1,42 @@ +package org.jetbrains.dokka.analysis.java + +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.SyntheticElement +import com.intellij.psi.javadoc.PsiDocComment +import org.jetbrains.dokka.analysis.java.parsers.JavaPsiDocCommentParser +import org.jetbrains.dokka.model.doc.DocumentationNode + +private const val ENUM_VALUEOF_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValueOf.java.template" +private const val ENUM_VALUES_TEMPLATE_PATH = "/dokka/docs/javadoc/EnumValues.java.template" + +internal class SyntheticElementDocumentationProvider( + private val javadocParser: JavaPsiDocCommentParser, + private val project: Project +) { + fun isDocumented(psiElement: PsiElement): Boolean = psiElement is PsiMethod + && (psiElement.isSyntheticEnumValuesMethod() || psiElement.isSyntheticEnumValueOfMethod()) + + fun getDocumentation(psiElement: PsiElement): DocumentationNode? { + val psiMethod = psiElement as? PsiMethod ?: return null + val templatePath = when { + psiMethod.isSyntheticEnumValuesMethod() -> ENUM_VALUES_TEMPLATE_PATH + psiMethod.isSyntheticEnumValueOfMethod() -> ENUM_VALUEOF_TEMPLATE_PATH + else -> return null + } + val docComment = loadSyntheticDoc(templatePath) ?: return null + return javadocParser.parsePsiDocComment(docComment, psiElement) + } + + private fun loadSyntheticDoc(path: String): PsiDocComment? { + val text = javaClass.getResource(path)?.readText() ?: return null + return JavaPsiFacade.getElementFactory(project).createDocCommentFromText(text) + } +} + +private fun PsiMethod.isSyntheticEnumValuesMethod() = this.isSyntheticEnumFunction() && this.name == "values" +private fun PsiMethod.isSyntheticEnumValueOfMethod() = this.isSyntheticEnumFunction() && this.name == "valueOf" +private fun PsiMethod.isSyntheticEnumFunction() = this is SyntheticElement && this.containingClass?.isEnum == true + diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt new file mode 100644 index 00000000..6cc32233 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocComment.kt @@ -0,0 +1,14 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.JavadocTag + +/** + * MUST override equals and hashcode + */ +@InternalDokkaApi +interface DocComment { + fun hasTag(tag: JavadocTag): Boolean + + fun resolveTag(tag: JavadocTag): List +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt new file mode 100644 index 00000000..3d7d4247 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentCreator.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.InternalDokkaApi + +@InternalDokkaApi +interface DocCommentCreator { + fun create(element: PsiNamedElement): DocComment? +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt new file mode 100644 index 00000000..96245ac2 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFactory.kt @@ -0,0 +1,20 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.InternalDokkaApi + +@InternalDokkaApi +class DocCommentFactory( + private val docCommentCreators: List +) { + fun fromElement(element: PsiNamedElement): DocComment? { + docCommentCreators.forEach { creator -> + val comment = creator.create(element) + if (comment != null) { + return comment + } + } + return null + } +} + diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt new file mode 100644 index 00000000..32c8dc65 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocCommentFinder.kt @@ -0,0 +1,64 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.javadoc.PsiDocComment +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.util.from +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.utilities.DokkaLogger + +@InternalDokkaApi +class DocCommentFinder( + private val logger: DokkaLogger, + private val docCommentFactory: DocCommentFactory, +) { + fun findClosestToElement(element: PsiNamedElement): DocComment? { + val docComment = docCommentFactory.fromElement(element) + if (docComment != null) { + return docComment + } + + return if (element is PsiMethod) { + findClosestToMethod(element) + } else { + element.children + .filterIsInstance() + .firstOrNull() + ?.let { JavaDocComment(it) } + } + } + + private fun findClosestToMethod(method: PsiMethod): DocComment? { + val superMethods = method.findSuperMethods() + if (superMethods.isEmpty()) return null + + if (superMethods.size == 1) { + return findClosestToElement(superMethods.single()) + } + + val superMethodDocumentation = superMethods.map { superMethod -> findClosestToElement(superMethod) }.distinct() + if (superMethodDocumentation.size == 1) { + return superMethodDocumentation.single() + } + + logger.debug( + "Conflicting documentation for ${DRI.from(method)}" + + "${superMethods.map { DRI.from(it) }}" + ) + + /* Prioritize super class over interface */ + val indexOfSuperClass = superMethods.indexOfFirst { superMethod -> + val parent = superMethod.parent + if (parent is PsiClass) !parent.isInterface + else false + } + + return if (indexOfSuperClass >= 0) { + superMethodDocumentation[indexOfSuperClass] + } else { + superMethodDocumentation.first() + } + } +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt new file mode 100644 index 00000000..c06ed496 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/DocumentationContent.kt @@ -0,0 +1,11 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.JavadocTag + +@InternalDokkaApi +interface DocumentationContent { + val tag: JavadocTag + + fun resolveSiblings(): List +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt new file mode 100644 index 00000000..5c9be887 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocComment.kt @@ -0,0 +1,84 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiElement +import com.intellij.psi.javadoc.PsiDocComment +import com.intellij.psi.javadoc.PsiDocTag +import org.jetbrains.dokka.analysis.java.* +import org.jetbrains.dokka.analysis.java.util.contentElementsWithSiblingIfNeeded +import org.jetbrains.dokka.analysis.java.util.getKotlinFqName +import org.jetbrains.dokka.analysis.java.util.hasTag +import org.jetbrains.dokka.analysis.java.util.resolveToElement +import org.jetbrains.dokka.utilities.firstIsInstanceOrNull + +internal class JavaDocComment(val comment: PsiDocComment) : DocComment { + override fun hasTag(tag: JavadocTag): Boolean { + return when (tag) { + is ThrowingExceptionJavadocTag -> hasTag(tag) + else -> comment.hasTag(tag) + } + } + + private fun hasTag(tag: ThrowingExceptionJavadocTag): Boolean = + comment.hasTag(tag) && comment.resolveTag(tag).firstIsInstanceOrNull() + ?.resolveToElement() + ?.getKotlinFqName() == tag.exceptionQualifiedName + + override fun resolveTag(tag: JavadocTag): List { + return when (tag) { + is ParamJavadocTag -> resolveParamTag(tag) + is ThrowingExceptionJavadocTag -> resolveThrowingTag(tag) + else -> comment.resolveTag(tag).map { PsiDocumentationContent(it, tag) } + } + } + + private fun resolveParamTag(tag: ParamJavadocTag): List { + val resolvedParamElements = comment.resolveTag(tag) + .filterIsInstance() + .map { it.contentElementsWithSiblingIfNeeded() } + .firstOrNull { + it.firstOrNull()?.text == tag.method.parameterList.parameters[tag.paramIndex].name + }.orEmpty() + + return resolvedParamElements + .withoutReferenceLink() + .map { PsiDocumentationContent(it, tag) } + } + + private fun resolveThrowingTag(tag: ThrowingExceptionJavadocTag): List { + val resolvedElements = comment.resolveTag(tag) + .flatMap { + when (it) { + is PsiDocTag -> it.contentElementsWithSiblingIfNeeded() + else -> listOf(it) + } + } + + return resolvedElements + .withoutReferenceLink() + .map { PsiDocumentationContent(it, tag) } + } + + private fun PsiDocComment.resolveTag(tag: JavadocTag): List { + return when (tag) { + DescriptionJavadocTag -> this.descriptionElements.toList() + else -> this.findTagsByName(tag.name).toList() + } + } + + private fun List.withoutReferenceLink(): List = drop(1) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JavaDocComment + + if (comment != other.comment) return false + + return true + } + + override fun hashCode(): Int { + return comment.hashCode() + } +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt new file mode 100644 index 00000000..00efeb0a --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/JavaDocCommentCreator.kt @@ -0,0 +1,11 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiDocCommentOwner +import com.intellij.psi.PsiNamedElement + +internal class JavaDocCommentCreator : DocCommentCreator { + override fun create(element: PsiNamedElement): DocComment? { + val psiDocComment = (element as? PsiDocCommentOwner)?.docComment ?: return null + return JavaDocComment(psiDocComment) + } +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt new file mode 100644 index 00000000..c36ce50d --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/doccomment/PsiDocumentationContent.kt @@ -0,0 +1,22 @@ +package org.jetbrains.dokka.analysis.java.doccomment + +import com.intellij.psi.PsiElement +import com.intellij.psi.javadoc.PsiDocTag +import org.jetbrains.dokka.analysis.java.JavadocTag +import org.jetbrains.dokka.analysis.java.util.contentElementsWithSiblingIfNeeded + +internal data class PsiDocumentationContent( + val psiElement: PsiElement, + override val tag: JavadocTag +) : DocumentationContent { + + override fun resolveSiblings(): List { + return if (psiElement is PsiDocTag) { + psiElement.contentElementsWithSiblingIfNeeded() + .map { content -> PsiDocumentationContent(content, tag) } + } else { + listOf(this) + } + } + +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt new file mode 100644 index 00000000..1e193648 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/CommentResolutionContext.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dokka.analysis.java.parsers + +import com.intellij.psi.javadoc.PsiDocComment +import org.jetbrains.dokka.analysis.java.JavadocTag + +internal data class CommentResolutionContext( + val comment: PsiDocComment, + val tag: JavadocTag? +) diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt new file mode 100644 index 00000000..3f691799 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DocCommentParser.kt @@ -0,0 +1,12 @@ +package org.jetbrains.dokka.analysis.java.parsers + +import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.java.doccomment.DocComment +import org.jetbrains.dokka.model.doc.DocumentationNode + +@InternalDokkaApi +interface DocCommentParser { + fun canParse(docComment: DocComment): Boolean + fun parse(docComment: DocComment, context: PsiNamedElement): DocumentationNode +} diff --git a/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt new file mode 100644 index 00000000..45f44338 --- /dev/null +++ b/subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt @@ -0,0 +1,797 @@ +package org.jetbrains.dokka.analysis.java.parsers + +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute +import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue +import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue +import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue +import com.intellij.lang.jvm.types.JvmReferenceType +import com.intellij.openapi.project.Project +import com.intellij.psi.* +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.java.BreakingAbstractionKotlinLightMethodChecker +import org.jetbrains.dokka.analysis.java.SyntheticElementDocumentationProvider +import org.jetbrains.dokka.analysis.java.getVisibility +import org.jetbrains.dokka.analysis.java.util.* +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.nextTarget +import org.jetbrains.dokka.links.withClass +import org.jetbrains.dokka.links.withEnumEntryExtra +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.AnnotationTarget +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.doc.Param +import org.jetbrains.dokka.model.properties.PropertyContainer +import org.jetbrains.dokka.utilities.DokkaLogger +import org.jetbrains.dokka.utilities.parallelForEach +import org.jetbrains.dokka.utilities.parallelMap +import org.jetbrains.dokka.utilities.parallelMapNotNull + +internal class DokkaPsiParser( + private val sourceSetData: DokkaConfiguration.DokkaSourceSet, + private val project: Project, + private val logger: DokkaLogger, + private val javadocParser: JavadocParser, + private val javaPsiDocCommentParser: JavaPsiDocCommentParser, + private val lightMethodChecker: BreakingAbstractionKotlinLightMethodChecker, +) { + private val syntheticDocProvider = SyntheticElementDocumentationProvider(javaPsiDocCommentParser, project) + + private val cachedBounds = hashMapOf() + + private val PsiMethod.hash: Int + get() = "$returnType $name$parameterList".hashCode() + + private val PsiField.hash: Int + get() = "$type $name".hashCode() + + private val PsiClassType.shouldBeIgnored: Boolean + get() = isClass("java.lang.Enum") || isClass("java.lang.Object") + + private fun PsiClassType.isClass(qName: String): Boolean { + val shortName = qName.substringAfterLast('.') + if (className == shortName) { + val psiClass = resolve() + return psiClass?.qualifiedName == qName + } + return false + } + + private fun T.toSourceSetDependent() = mapOf(sourceSetData to this) + + suspend fun parsePackage(packageName: String, psiFiles: List): DPackage = coroutineScope { + val dri = DRI(packageName = packageName) + val packageInfo = psiFiles.singleOrNull { it.name == "package-info.java" } + val documentation = packageInfo?.let { + javadocParser.parseDocumentation(it).toSourceSetDependent() + }.orEmpty() + val annotations = packageInfo?.packageStatement?.annotationList?.annotations + + DPackage( + dri = dri, + functions = emptyList(), + properties = emptyList(), + classlikes = psiFiles.parallelMap { psiFile -> + coroutineScope { + psiFile.classes.asIterable().parallelMap { parseClasslike(it, dri) } + } + }.flatten(), + typealiases = emptyList(), + documentation = documentation, + expectPresentInSet = null, + sourceSets = setOf(sourceSetData), + extra = PropertyContainer.withAll( + annotations?.toList().orEmpty().toListOfAnnotations().toSourceSetDependent().toAnnotations() + ) + ) + } + + private suspend fun parseClasslike(psi: PsiClass, parent: DRI): DClasslike = coroutineScope { + with(psi) { + val dri = parent.withClass(name.toString()) + val superMethodsKeys = hashSetOf() + val superMethods = mutableListOf>() + val superFieldsKeys = hashSetOf() + val superFields = mutableListOf>() + methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) } + + /** + * Caution! This method mutates + * - superMethodsKeys + * - superMethods + * - superFieldsKeys + * - superKeys + */ + /** + * Caution! This method mutates + * - superMethodsKeys + * - superMethods + * - superFieldsKeys + * - superKeys + */ + fun Array.getSuperTypesPsiClasses(): List> { + forEach { type -> + (type as? PsiClassType)?.resolve()?.let { + val definedAt = DRI.from(it) + it.methods.forEach { method -> + val hash = method.hash + if (!method.isConstructor && !superMethodsKeys.contains(hash) && + method.getVisibility() != JavaVisibility.Private + ) { + superMethodsKeys.add(hash) + superMethods.add(Pair(method, definedAt)) + } + } + it.fields.forEach { field -> + val hash = field.hash + if (!superFieldsKeys.contains(hash)) { + superFieldsKeys.add(hash) + superFields.add(Pair(field, definedAt)) + } + } + } + } + return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi -> + supertypePsi.resolve()?.let { supertypePsiClass -> + val javaClassKind = when { + supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE + else -> JavaClassKindTypes.CLASS + } + supertypePsiClass to javaClassKind + } + } + } + + fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode { + val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses() + .partition { it.second == JavaClassKindTypes.CLASS } + + return AncestryNode( + typeConstructor = GenericTypeConstructor( + DRI.from(psiClass), + psiClass.typeParameters.map { typeParameter -> + TypeParameter( + dri = DRI.from(typeParameter), + name = typeParameter.name.orEmpty(), + extra = typeParameter.annotations() + ) + } + ), + superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers), + interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) } + ) + } + + val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this) + + val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods) + val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors( + fields = superFields.map { it.first }.toTypedArray(), + methods = superMethods.map { it.first }.toTypedArray() + ) + + val regularSuperFunctionsKeys = regularSuperFunctions.map { it.hash }.toSet() + val regularSuperFunctionsWithDRI = superMethods.filter { it.first.hash in regularSuperFunctionsKeys } + + val superAccessorsWithDRI = superAccessors.mapValues { (field, methods) -> + val containsJvmField = field.annotations.mapNotNull { it.toAnnotation() }.any { it.isJvmField() } + if (containsJvmField) { + emptyList() + } else { + methods.mapNotNull { method -> superMethods.find { it.first.hash == method.hash } } + } + } + + val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() } + val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent() + val allFunctions = async { + val parsedRegularFunctions = regularFunctions.parallelMapNotNull { + if (!it.isConstructor) parseFunction( + it, + parentDRI = dri + ) else null + } + val parsedSuperFunctions = regularSuperFunctionsWithDRI + .filter { it.first !in overridden } + .parallelMap { parseFunction(it.first, inheritedFrom = it.second) } + + parsedRegularFunctions + parsedSuperFunctions + } + val allFields = async { + val parsedFields = fields.toList().parallelMapNotNull { + parseField(it, accessors[it].orEmpty()) + } + val parsedSuperFields = superFields.parallelMapNotNull { (field, dri) -> + parseFieldWithInheritingAccessors( + field, + superAccessorsWithDRI[field].orEmpty(), + inheritedFrom = dri + ) + } + parsedFields + parsedSuperFields + } + val source = parseSources() + val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } } + val visibility = getVisibility().toSourceSetDependent() + val ancestors = (listOfNotNull(ancestry.superclass?.let { + it.typeConstructor.let { typeConstructor -> + TypeConstructorWithKind( + typeConstructor, + JavaClassKindTypes.CLASS + ) + } + }) + ancestry.interfaces.map { + TypeConstructorWithKind( + it.typeConstructor, + JavaClassKindTypes.INTERFACE + ) + }).toSourceSetDependent() + val modifiers = getModifier().toSourceSetDependent() + val implementedInterfacesExtra = + ImplementedInterfaces(ancestry.allImplementedInterfaces().toSourceSetDependent()) + + when { + isAnnotationType -> + DAnnotation( + name = name.orEmpty(), + dri = dri, + documentation = documentation, + expectPresentInSet = null, + sources = source, + functions = allFunctions.await(), + properties = allFields.await(), + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + constructors = parseConstructors(dri), + generics = mapTypeParameters(dri), + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( + implementedInterfacesExtra, + annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations() + ) + ) + + isEnum -> DEnum( + dri = dri, + name = name.orEmpty(), + entries = fields.filterIsInstance().map { entry -> + DEnumEntry( + dri = dri.withClass(entry.name).withEnumEntryExtra(), + name = entry.name, + documentation = javadocParser.parseDocumentation(entry).toSourceSetDependent(), + expectPresentInSet = null, + functions = emptyList(), + properties = emptyList(), + classlikes = emptyList(), + sourceSets = setOf(sourceSetData), + extra = PropertyContainer.withAll( + implementedInterfacesExtra, + annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations() + ) + ) + }, + documentation = documentation, + expectPresentInSet = null, + sources = source, + functions = allFunctions.await(), + properties = fields.filter { it !is PsiEnumConstant } + .map { parseField(it, accessors[it].orEmpty()) }, + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + constructors = parseConstructors(dri), + supertypes = ancestors, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( + implementedInterfacesExtra, + annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations() + ) + ) + + isInterface -> DInterface( + dri = dri, + name = name.orEmpty(), + documentation = documentation, + expectPresentInSet = null, + sources = source, + functions = allFunctions.await(), + properties = allFields.await(), + classlikes = classlikes.await(), + visibility = visibility, + companion = null, + generics = mapTypeParameters(dri), + supertypes = ancestors, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( + implementedInterfacesExtra, + annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations() + ) + ) + + else -> DClass( + dri = dri, + name = name.orEmpty(), + constructors = parseConstructors(dri), + functions = allFunctions.await(), + properties = allFields.await(), + classlikes = classlikes.await(), + sources = source, + visibility = visibility, + companion = null, + generics = mapTypeParameters(dri), + supertypes = ancestors, + documentation = documentation, + expectPresentInSet = null, + modifier = modifiers, + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = PropertyContainer.withAll( + implementedInterfacesExtra, + annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations(), + ancestry.exceptionInSupertypesOrNull() + ) + ) + } + } + } + + /* + * Parameter `parentDRI` required for substitute package name: + * in the case of synthetic constructor, it will return empty from [DRI.Companion.from]. + */ + private fun PsiClass.parseConstructors(parentDRI: DRI): List { + val constructors = when { + isAnnotationType || isInterface -> emptyArray() + isEnum -> this.constructors + else -> this.constructors.takeIf { it.isNotEmpty() } ?: arrayOf(createDefaultConstructor()) + } + return constructors.map { parseFunction(psi = it, isConstructor = true, parentDRI = parentDRI) } + } + + /** + * PSI doesn't return a default constructor if class doesn't contain an explicit one. + * This method create synthetic constructor + * Visibility modifier is preserved from the class. + */ + private fun PsiClass.createDefaultConstructor(): PsiMethod { + val psiElementFactory = JavaPsiFacade.getElementFactory(project) + val signature = when (val classVisibility = getVisibility()) { + JavaVisibility.Default -> name.orEmpty() + else -> "${classVisibility.name} $name" + } + return psiElementFactory.createConstructor(signature, this) + } + + private fun AncestryNode.exceptionInSupertypesOrNull(): ExceptionInSupertypes? = + typeConstructorsBeingExceptions().takeIf { it.isNotEmpty() } + ?.let { ExceptionInSupertypes(it.toSourceSetDependent()) } + + private fun parseFunction( + psi: PsiMethod, + isConstructor: Boolean = false, + inheritedFrom: DRI? = null, + parentDRI: DRI? = null, + ): DFunction { + val dri = parentDRI?.let { dri -> + DRI.from(psi).copy(packageName = dri.packageName, classNames = dri.classNames) + } ?: DRI.from(psi) + val docs = psi.getDocumentation() + return DFunction( + dri = dri, + name = psi.name, + isConstructor = isConstructor, + parameters = psi.parameterList.parameters.map { psiParameter -> + DParameter( + dri = dri.copy(target = dri.target.nextTarget()), + name = psiParameter.name, + documentation = DocumentationNode( + listOfNotNull(docs.firstChildOfTypeOrNull { + it.name == psiParameter.name + }) + ).toSourceSetDependent(), + expectPresentInSet = null, + type = getBound(psiParameter.type), + sourceSets = setOf(sourceSetData), + extra = PropertyContainer.withAll( + psiParameter.annotations.toList().toListOfAnnotations().toSourceSetDependent() + .toAnnotations() + ) + ) + }, + documentation = docs.toSourceSetDependent(), + expectPresentInSet = null, + sources = psi.parseSources(), + visibility = psi.getVisibility().toSourceSetDependent(), + type = psi.returnType?.let { getBound(type = it) } ?: Void, + generics = psi.mapTypeParameters(dri), + receiver = null, + modifier = psi.getModifier().toSourceSetDependent(), + sourceSets = setOf(sourceSetData), + isExpectActual = false, + extra = psi.additionalExtras().let { + PropertyContainer.withAll( + inheritedFrom?.let { InheritedMember(it.toSourceSetDependent()) }, + it.toSourceSetDependent().toAdditionalModifiers(), + (psi.annotations.toList() + .toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent() + .toAnnotations(), + ObviousMember.takeIf { psi.isObvious(inheritedFrom) }, + psi.throwsList.toDriList().takeIf { it.isNotEmpty() } + ?.let { CheckedExceptions(it.toSourceSetDependent()) } + ) + } + ) + } + + private fun PsiNamedElement.parseSources(): SourceSetDependent { + return when { + // `isPhysical` detects the virtual declarations without real sources. + // Otherwise, `PsiDocumentableSource` initialization will fail: non-physical declarations doesn't have `virtualFile`. + // This check protects from accidentally requesting sources for synthetic / virtual declarations. + isPhysical -> PsiDocumentableSource(this).toSourceSetDependent() + else -> emptyMap() + } + } + + private fun PsiMethod.getDocumentation(): DocumentationNode = + this.takeIf { it is SyntheticElement }?.let { syntheticDocProvider.getDocumentation(it) } + ?: javadocParser.parseDocumentation(this) + + private fun PsiMethod.isObvious(inheritedFrom: DRI? = null): Boolean { + return (this is SyntheticElement && !syntheticDocProvider.isDocumented(this)) + || inheritedFrom?.isObvious(