diff options
author | Ignat Beresnev <ignat.beresnev@jetbrains.com> | 2023-11-22 18:39:22 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-22 18:39:22 +0100 |
commit | e63dad0875c8da8c2c04ac8a4285ad2507e74257 (patch) | |
tree | e70a6dac67794f5133f99a0c2a7e1530f2001697 | |
parent | 610b5520b02ce8275462793e176406d1fb37861c (diff) | |
download | dokka-e63dad0875c8da8c2c04ac8a4285ad2507e74257.tar.gz dokka-e63dad0875c8da8c2c04ac8a4285ad2507e74257.tar.bz2 dokka-e63dad0875c8da8c2c04ac8a4285ad2507e74257.zip |
Stabilize ExternalDocumentableProvider (#3312)
17 files changed, 455 insertions, 190 deletions
diff --git a/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api b/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api index 3b546932..d15924b0 100644 --- a/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api +++ b/dokka-subprojects/analysis-kotlin-api/api/analysis-kotlin-api.api @@ -1,8 +1,13 @@ public final class org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { public fun <init> ()V + public final fun getExternalDocumentableProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getSampleAnalysisEnvironmentCreator ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; } +public abstract interface class org/jetbrains/dokka/analysis/kotlin/documentable/ExternalDocumentableProvider { + public abstract fun getClasslike (Lorg/jetbrains/dokka/links/DRI;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike; +} + public final class org/jetbrains/dokka/analysis/kotlin/internal/DocumentableLanguage : java/lang/Enum { public static final field JAVA Lorg/jetbrains/dokka/analysis/kotlin/internal/DocumentableLanguage; public static final field KOTLIN Lorg/jetbrains/dokka/analysis/kotlin/internal/DocumentableLanguage; @@ -14,10 +19,6 @@ public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/Doc public abstract fun getLanguage (Lorg/jetbrains/dokka/model/Documentable;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/analysis/kotlin/internal/DocumentableLanguage; } -public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider { - public abstract fun findClasslike (Lorg/jetbrains/dokka/links/DRI;Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Lorg/jetbrains/dokka/model/DClasslike; -} - public abstract interface class org/jetbrains/dokka/analysis/kotlin/internal/FullClassHierarchyBuilder { public abstract fun build (Lorg/jetbrains/dokka/model/DModule;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -47,7 +48,6 @@ public final class org/jetbrains/dokka/analysis/kotlin/internal/InheritanceNode public final class org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { public fun <init> ()V public final fun getDocumentableSourceLanguageParser ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; - public final fun getExternalDocumentablesProvider ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getFullClassHierarchyBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getInheritanceBuilder ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; public final fun getKotlinToJavaService ()Lorg/jetbrains/dokka/plugability/ExtensionPoint; diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt index 1df1dfe6..5a70b3f2 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/KotlinAnalysisPlugin.kt @@ -4,6 +4,7 @@ package org.jetbrains.dokka.analysis.kotlin +import org.jetbrains.dokka.analysis.kotlin.documentable.ExternalDocumentableProvider import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironment import org.jetbrains.dokka.plugability.DokkaPlugin @@ -20,6 +21,14 @@ public class KotlinAnalysisPlugin : DokkaPlugin() { */ public val sampleAnalysisEnvironmentCreator: ExtensionPoint<SampleAnalysisEnvironmentCreator> by extensionPoint() + /** + * An extension that helps to find external documentables that are not provided by Dokka by default, + * such as documentables that come from external dependencies. + * + * @see ExternalDocumentableProvider for more details + */ + public val externalDocumentableProvider: ExtensionPoint<ExternalDocumentableProvider> by extensionPoint() + @OptIn(DokkaPluginApiPreview::class) override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement } diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/documentable/ExternalDocumentableProvider.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/documentable/ExternalDocumentableProvider.kt new file mode 100644 index 00000000..6e2c7b33 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/documentable/ExternalDocumentableProvider.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.documentable + +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DClasslike +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator + +/** + * Helps find external documentables that are not provided by Dokka by default. + * + * By default, Dokka parses and makes available only documentables of the declarations found + * in the user project itself. Meaning, if the project's source code contains a `fun foo()`, + * it must be returned by [SourceToDocumentableTranslator]. However, if the user project + * depends on an external library which has a `fun bar()`, it will __not__ be available and + * documented out of the box. + * + * This provider helps you find documentables for the declarations that are present in + * [DokkaSourceSet.classpath] during runtime, but are not necessarily from the user project itself. + * + * For example, it can help you find a class that comes from a dependency, which can be useful + * if you want to get more information about a supertype of the project's class, + * because [DClass.supertypes] only provides the supertype's DRI, but not its full documentable. + * + * It is expected to work with all languages supported by the analysis implementation, + * meaning it should return Java classes if Java as an input language is supported. + * + * If you query DRIs of local project declarations (not external), it should generally work, although + * it's not guaranteed that the returned value will be 100% equal to that provided by Dokka by default. + * + * Note: because classpath entries consist of compiled code, the returned documentables may have some + * properties set to null or empty, because some information cannot be extracted from it in any way. + * One such example is KDocs, they are present in source code, but not in compiled classes. + */ +public interface ExternalDocumentableProvider { + + /** + * Returns a valid and fully initialized [DClasslike] if the [dri] points to a class-like + * declaration (annotation, class, enum, interface, object) that can be found among + * [DokkaSourceSet.classpath] entries. + * + * If the [dri] points to a non-class-like declaration (like a function), + * or the declaration cannot be found, it returns `null`. + * + * Note: the implementation is not expected to cache results or return pre-computed values, so + * it may need to analyze parts of the project and instantiate new documentables on every invocation. + * Use this function sparingly, and cache results on your side if you need to. + */ + public fun getClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt deleted file mode 100644 index 7c564880..00000000 --- a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/ExternalDocumentablesProvider.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package org.jetbrains.dokka.analysis.kotlin.internal - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.InternalDokkaApi -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DClasslike - -/** - * Service that can be queried with [DRI] and source set to obtain a documentable for classlike. - * - * There are some cases when there is a need to process documentables of classlikes that were not defined - * in the project itself but are somehow related to the symbols defined in the documented project (e.g. are supertypes - * of classes defined in project). - */ -@InternalDokkaApi -public fun interface ExternalDocumentablesProvider { - - /** - * Returns [DClasslike] matching provided [DRI] in specified source set. - * - * Result is null if compiler haven't generated matching class descriptor. - */ - public fun findClasslike(dri: DRI, sourceSet: DokkaConfiguration.DokkaSourceSet): DClasslike? -} diff --git a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt index d032d490..dbf33c8e 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/internal/InternalKotlinAnalysisPlugin.kt @@ -27,8 +27,6 @@ public class InternalKotlinAnalysisPlugin : DokkaPlugin() { public val inheritanceBuilder: ExtensionPoint<InheritanceBuilder> by extensionPoint() - public val externalDocumentablesProvider: ExtensionPoint<ExternalDocumentablesProvider> by extensionPoint() - public val documentableSourceLanguageParser: ExtensionPoint<DocumentableSourceLanguageParser> by extensionPoint() @OptIn(DokkaPluginApiPreview::class) diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/documentable/ExternalDocumentableProviderTest.kt b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/documentable/ExternalDocumentableProviderTest.kt new file mode 100644 index 00000000..2ef9e2f0 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/kotlin/org/jetbrains/dokka/analysis/test/documentable/ExternalDocumentableProviderTest.kt @@ -0,0 +1,342 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.test.documentable + +import org.jetbrains.dokka.analysis.test.api.javaTestProject +import org.jetbrains.dokka.analysis.test.api.kotlinJvmTestProject +import org.jetbrains.dokka.analysis.test.api.useServices +import org.jetbrains.dokka.analysis.test.api.util.getResourceAbsolutePath +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import kotlin.test.* + +class ExternalDocumentableProviderTest { + + @Test + fun `should find a valid external class from java stdlib`() { + val project = javaTestProject { + javaFile("org/jetbrains/dokka/test/MyProjectJavaClass.java") { + +"public class MyProjectJavaClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyProjectJavaClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val arrayListDRI = DRI("java.util", "ArrayList") + + val arrayListClasslike = externalDocumentableProvider.getClasslike(arrayListDRI, sourceSet) + assertNotNull(arrayListClasslike) + + assertEquals("ArrayList", arrayListClasslike.name) + assertEquals(arrayListDRI, arrayListClasslike.dri) + assertTrue(arrayListClasslike is DClass) + assertTrue(arrayListClasslike.functions.size > 10, "java.util.ArrayList is expected to have >10 functions") + + val superTypes = arrayListClasslike.supertypes.entries.single().value + val abstractListSuperType = superTypes.firstOrNull { + val dri = it.typeConstructor.dri + dri.packageName == "java.util" && dri.classNames == "AbstractList" + } + assertNotNull(abstractListSuperType, "java.util.ArrayList is expected to extend java.util.AbstractList") + + } + } + + @Test + fun `should find a valid external annotation from kotlin jvm stdlib`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val jvmFieldDRI = DRI("kotlin.jvm", "JvmField") + + val jvmFieldAnnotation = externalDocumentableProvider.getClasslike(jvmFieldDRI, sourceSet) + assertNotNull(jvmFieldAnnotation) + + assertEquals("JvmField", jvmFieldAnnotation.name) + assertEquals(jvmFieldDRI, jvmFieldAnnotation.dri) + assertTrue(jvmFieldAnnotation is DAnnotation) + } + } + + @Test + fun `should find a valid external enum from kotlin stdlib`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val deprecationLevelDRI = DRI("kotlin", "DeprecationLevel") + + val deprecationLevelEnum = externalDocumentableProvider.getClasslike(deprecationLevelDRI, sourceSet) + assertNotNull(deprecationLevelEnum) + + assertEquals("DeprecationLevel", deprecationLevelEnum.name) + assertEquals(deprecationLevelDRI, deprecationLevelEnum.dri) + assertTrue(deprecationLevelEnum is DEnum) + assertEquals(3, deprecationLevelEnum.entries.size) + + val warningLevel = deprecationLevelEnum.entries[0] + assertEquals("WARNING", warningLevel.name) + } + } + + @Test + fun `should find a valid external interface from kotlin stdlib`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val sequenceDRI = DRI("kotlin.sequences", "Sequence") + + val sequenceInterface = externalDocumentableProvider.getClasslike(sequenceDRI, sourceSet) + assertNotNull(sequenceInterface) + + assertEquals("Sequence", sequenceInterface.name) + assertEquals(sequenceDRI, sequenceInterface.dri) + assertTrue(sequenceInterface is DInterface) + + val iteratorFunction = sequenceInterface.functions.firstOrNull { it.name == "iterator" } + assertNotNull(iteratorFunction) + } + } + + @Test + fun `should find a valid external object from kotlin stdlib`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val emptyCoroutineContextDRI = DRI("kotlin.coroutines", "EmptyCoroutineContext") + + val emptyCoroutineContext = externalDocumentableProvider.getClasslike(emptyCoroutineContextDRI, sourceSet) + assertNotNull(emptyCoroutineContext) + + assertEquals("EmptyCoroutineContext", emptyCoroutineContext.name) + assertEquals(emptyCoroutineContextDRI, emptyCoroutineContext.dri) + assertTrue(emptyCoroutineContext is DObject) + } + } + + @Test + fun `should find a valid external class from a third party library`() { + val project = kotlinJvmTestProject { + dokkaConfiguration { + kotlinSourceSet { + additionalClasspath = setOf( + getResourceAbsolutePath("jars/kotlinx-cli-jvm-0.3.6.jar") + ) + } + } + + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val argTypeDRI = DRI("kotlinx.cli", "ArgType") + + val argTypeClass = externalDocumentableProvider.getClasslike(argTypeDRI, sourceSet) + assertNotNull(argTypeClass) + + assertEquals("ArgType", argTypeClass.name) + assertEquals(argTypeDRI, argTypeClass.dri) + assertTrue(argTypeClass is DClass) + assertEquals(KotlinModifier.Abstract, argTypeClass.modifier.values.single()) + } + } + + @Test + fun `should find a nested interface from java stdlib`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + val mapEntryDRI = DRI("java.util", "Map.Entry") + + val mapEntryInterface = externalDocumentableProvider.getClasslike(mapEntryDRI, sourceSet) + assertNotNull(mapEntryInterface) + + assertEquals("Entry", mapEntryInterface.name) + assertEquals(mapEntryDRI, mapEntryInterface.dri) + assertTrue(mapEntryInterface is DInterface) + } + } + + @Test + fun `should return null for querying non existing dri`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + + val nonExistingDRI = DRI("com.example.pckg", "NonExistingClassname") + val nonExistingClasslike = externalDocumentableProvider.getClasslike(nonExistingDRI, sourceSet) + assertNull(nonExistingClasslike) + } + } + + @Test + fun `should return null for querying a classlike with a function dri`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + // first check that Dokka only returns documentables declared in the project by default + // to make sure later that the external documentable is indeed external and not local + val projectPackages = context.module.packages + assertEquals(1, projectPackages.size, "Expected only a single package to be returned") + + val projectPackageChildren = projectPackages.single().children + assertEquals(1, projectPackageChildren.size, "Expected the project to contain only 1 child") + assertEquals("MyKotlinClass", projectPackageChildren.single().name) + + // query for an external documentable that is not part of the project itself + val sourceSet = context.configuration.sourceSets.single() + + val functionDRI = DRI("kotlin.collections", "listOf") + val queriedClasslike = externalDocumentableProvider.getClasslike(functionDRI, sourceSet) + assertNull(queriedClasslike) + } + } + + @Test + fun `should return a class defined in the user project itself`() { + val project = kotlinJvmTestProject { + ktFile("org/jetbrains/test/dokka/MyKotlinFile.kt") { + +"class MyKotlinClass {}" + } + } + + project.useServices { context -> + val sourceSet = context.configuration.sourceSets.single() + + val userProjectClassDRI = DRI("org.jetbrains.test.dokka", "MyKotlinClass") + val userProjectClass = externalDocumentableProvider.getClasslike(userProjectClassDRI, sourceSet) + assertNotNull(userProjectClass) + + assertEquals("MyKotlinClass", userProjectClass.name) + assertEquals(userProjectClassDRI, userProjectClass.dri) + assertTrue(userProjectClass is DClass) + } + } + +} diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/README.md b/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/README.md new file mode 100644 index 00000000..21f27d40 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/README.md @@ -0,0 +1 @@ +The JARs in this directory can be used as external dependencies for testing purposes. diff --git a/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/kotlinx-cli-jvm-0.3.6.jar b/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/kotlinx-cli-jvm-0.3.6.jar Binary files differnew file mode 100644 index 00000000..b4f1e5a4 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-api/src/test/resources/jars/kotlinx-cli-jvm-0.3.6.jar diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt index f729838d..714ee16a 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestAnalysisServices.kt @@ -5,6 +5,7 @@ package org.jetbrains.dokka.analysis.test.api.analysis import org.jetbrains.dokka.analysis.kotlin.KotlinAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.documentable.ExternalDocumentableProvider import org.jetbrains.dokka.analysis.kotlin.internal.ModuleAndPackageDocumentationReader import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreator @@ -16,5 +17,6 @@ import org.jetbrains.dokka.analysis.kotlin.sample.SampleAnalysisEnvironmentCreat */ class TestAnalysisServices( val sampleAnalysisEnvironmentCreator: SampleAnalysisEnvironmentCreator, + val externalDocumentableProvider: ExternalDocumentableProvider, val moduleAndPackageDocumentationReader: ModuleAndPackageDocumentationReader ) diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt index 674c6d47..7a410d7d 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/analysis/TestProjectAnalyzer.kt @@ -228,6 +228,7 @@ internal object TestProjectAnalyzer { val internalAnalysisPlugin = context.plugin<InternalKotlinAnalysisPlugin>() return TestAnalysisServices( sampleAnalysisEnvironmentCreator = publicAnalysisPlugin.querySingle { sampleAnalysisEnvironmentCreator }, + externalDocumentableProvider = publicAnalysisPlugin.querySingle { externalDocumentableProvider }, moduleAndPackageDocumentationReader = internalAnalysisPlugin.querySingle { moduleAndPackageDocumentationReader } diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt index b6563fb7..fc3499ea 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/configuration/TestDokkaConfigurationBuilder.kt @@ -5,6 +5,7 @@ package org.jetbrains.dokka.analysis.test.api.configuration import org.jetbrains.dokka.analysis.test.api.TestProject +import org.jetbrains.dokka.analysis.test.api.util.getResourceAbsolutePath import org.jetbrains.dokka.analysis.test.api.util.AnalysisTestDslMarker /** @@ -66,6 +67,8 @@ abstract class BaseTestDokkaSourceSetBuilder { /** * JARs **additional** to the default classpath. * + * You can put test JARs inside `src/resources`, and then get it via [getResourceAbsolutePath]. + * * @see TestDokkaSourceSet.classpath */ open var additionalClasspath: Set<String> = emptySet() diff --git a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt index 779add8d..afa3e84a 100644 --- a/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt +++ b/dokka-subprojects/analysis-kotlin-api/src/testFixtures/kotlin/org/jetbrains/dokka/analysis/test/api/util/FileUtils.kt @@ -39,3 +39,30 @@ internal fun <T> withTempDirectory(logger: DokkaLogger? = null, block: (tempDire logger?.debug("Deleted temporary directory $tempDir") } } + +/** + * Finds a resource by [resourcePath], and returns its absolute path. + * + * A resource is usually a file found in the `resources` directory of the project. + * + * For example, if you have a file `project/src/main/resources/jars/kotlinx-cli-jvm-0.3.6.jar`, + * you should be able to get it by calling this function as + * `getResourceAbsolutePath("jars/kotlinx-cli-jvm-0.3.6.jar")`. + * + * @throws IllegalArgumentException if the resource cannot be found or does not exist + * @return an absolute path to the resource, such as `/home/user/projects/dokka/../MyFile.md` + */ +fun getResourceAbsolutePath(resourcePath: String): String { + val resourceFile = getResourceFile(resourcePath) + require(resourceFile.exists()) { + "Resource file does not exist: $resourcePath" + } + return resourceFile.absolutePath +} + +private fun getResourceFile(resourcePath: String): File { + val resource = object {}.javaClass.classLoader.getResource(resourcePath)?.file + ?: throw IllegalArgumentException("Resource not found: $resourcePath") + + return File(resource) +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt index e8ebceb0..5bffaa48 100644 --- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt +++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/CompilerDescriptorAnalysisPlugin.kt @@ -18,7 +18,7 @@ import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.* import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.impl.moduledocs.ModuleAndPackageDocumentationReader import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.java.* import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultDescriptorToDocumentableTranslator -import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DefaultExternalDocumentablesProvider +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.translator.DescriptorExternalDocumentablesProvider import org.jetbrains.dokka.renderers.PostAction import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin import org.jetbrains.dokka.plugability.* @@ -95,8 +95,8 @@ public class CompilerDescriptorAnalysisPlugin : DokkaPlugin() { plugin<InternalKotlinAnalysisPlugin>().inheritanceBuilder providing { DescriptorInheritanceBuilder() } } - internal val defaultExternalDocumentablesProvider by extending { - plugin<InternalKotlinAnalysisPlugin>().externalDocumentablesProvider providing ::DefaultExternalDocumentablesProvider + internal val descriptorExternalDocumentableProvider by extending { + plugin<KotlinAnalysisPlugin>().externalDocumentableProvider providing ::DescriptorExternalDocumentablesProvider } private val javaAnalysisPlugin by lazy { plugin<JavaAnalysisPlugin>() } diff --git a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorExternalDocumentablesProvider.kt index 006a990b..750c1945 100644 --- a/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DefaultExternalDocumentablesProvider.kt +++ b/dokka-subprojects/analysis-kotlin-descriptors-compiler/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/compiler/translator/DescriptorExternalDocumentablesProvider.kt @@ -11,7 +11,7 @@ import org.jetbrains.dokka.model.DClasslike import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.analysis.kotlin.internal.ExternalDocumentablesProvider +import org.jetbrains.dokka.analysis.kotlin.documentable.ExternalDocumentableProvider import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PackageViewDescriptor @@ -19,12 +19,12 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered -internal class DefaultExternalDocumentablesProvider(context: DokkaContext) : ExternalDocumentablesProvider { +internal class DescriptorExternalDocumentablesProvider(context: DokkaContext) : ExternalDocumentableProvider { private val analysis = context.plugin<CompilerDescriptorAnalysisPlugin>().querySingle { kotlinAnalysis } private val translator: ExternalClasslikesTranslator = DefaultDescriptorToDocumentableTranslator(context) - override fun findClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? { + override fun getClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? { val pkg = dri.packageName?.let { FqName(it) } ?: FqName.ROOT val names = dri.classNames?.split('.') ?: return null diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt index 23bb0dc5..d8b171b9 100644 --- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/plugin/SymbolsAnalysisPlugin.kt @@ -114,8 +114,8 @@ public class SymbolsAnalysisPlugin : DokkaPlugin() { plugin<InternalKotlinAnalysisPlugin>().inheritanceBuilder providing ::SymbolInheritanceBuilder } - internal val symbolExternalDocumentablesProvider by extending { - plugin<InternalKotlinAnalysisPlugin>().externalDocumentablesProvider providing ::SymbolExternalDocumentablesProvider + internal val symbolExternalDocumentableProvider by extending { + plugin<KotlinAnalysisPlugin>().externalDocumentableProvider providing ::SymbolExternalDocumentablesProvider } internal val symbolSampleAnalysisEnvironmentCreator by extending { diff --git a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt index f68de715..4bcee7e8 100644 --- a/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt +++ b/dokka-subprojects/analysis-kotlin-symbols/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/symbols/services/SymbolExternalDocumentablesProvider.kt @@ -19,13 +19,13 @@ import org.jetbrains.dokka.plugability.plugin import org.jetbrains.dokka.plugability.querySingle import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol -import org.jetbrains.dokka.analysis.kotlin.internal.ExternalDocumentablesProvider import org.jetbrains.dokka.plugability.query +import org.jetbrains.dokka.analysis.kotlin.documentable.ExternalDocumentableProvider -internal class SymbolExternalDocumentablesProvider(val context: DokkaContext) : ExternalDocumentablesProvider { +internal class SymbolExternalDocumentablesProvider(val context: DokkaContext) : ExternalDocumentableProvider { private val kotlinAnalysis = context.plugin<SymbolsAnalysisPlugin>().querySingle { kotlinAnalysis } - override fun findClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? { + override fun getClasslike(dri: DRI, sourceSet: DokkaSourceSet): DClasslike? { val classId = getClassIdFromDRI(dri) return analyze(kotlinAnalysis.getModule(sourceSet)) { diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/translators/ExternalDocumentablesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/translators/ExternalDocumentablesTest.kt deleted file mode 100644 index 1879c538..00000000 --- a/dokka-subprojects/plugin-base/src/test/kotlin/translators/ExternalDocumentablesTest.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package translators - -import org.jetbrains.dokka.analysis.kotlin.internal.ExternalDocumentablesProvider -import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.model.DClass -import org.jetbrains.dokka.model.DInterface -import org.jetbrains.dokka.plugability.plugin -import org.jetbrains.dokka.plugability.querySingle -import org.jetbrains.dokka.utilities.cast -import kotlin.test.Test -import kotlin.test.assertEquals - -class ExternalDocumentablesTest : BaseAbstractTest() { - @Test - fun `external documentable from java stdlib`() { - val configuration = dokkaConfiguration { - sourceSets { - sourceSet { - sourceRoots = listOf("src") - analysisPlatform = "jvm" - classpath += jvmStdlibPath!! - } - } - } - - testInline( - """ - /src/com/sample/MyList.kt - package com.sample - class MyList: ArrayList<Int>() - """.trimIndent(), - configuration - ) { - lateinit var provider: ExternalDocumentablesProvider - pluginsSetupStage = { - provider = it.plugin<InternalKotlinAnalysisPlugin>().querySingle { externalDocumentablesProvider } - } - documentablesTransformationStage = { mod -> - val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single() - val res = provider.findClasslike( - entry.value.single().typeConstructor.dri, - entry.key) - assertEquals("ArrayList", res?.name) - assertEquals("java.util/ArrayList///PointingToDeclaration/", res?.dri?.toString()) - - val supertypes = res?.cast<DClass>()?.supertypes?.values?.single() - ?.map { it.typeConstructor.dri.classNames } - assertEquals( - listOf("AbstractList", "RandomAccess", "Cloneable", "Serializable", "MutableList"), - supertypes - ) - } - } - } - - @Test - fun `external documentable from dependency`() { - val coroutinesPath = - ClassLoader.getSystemResource("kotlinx/coroutines/Job.class") - ?.file - ?.replace("file:", "") - ?.replaceAfter(".jar", "") - - val configuration = dokkaConfiguration { - sourceSets { - sourceSet { - sourceRoots = listOf("src") - analysisPlatform = "jvm" - classpath += listOf(jvmStdlibPath!!, coroutinesPath!!) - } - } - } - - testInline( - """ - /src/com/sample/MyJob.kt - package com.sample - import kotlinx.coroutines.Job - abstract class MyJob: Job - """.trimIndent(), - configuration - ) { - lateinit var provider: ExternalDocumentablesProvider - pluginsSetupStage = { - provider = it.plugin<InternalKotlinAnalysisPlugin>().querySingle { externalDocumentablesProvider } - } - documentablesTransformationStage = { mod -> - val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single() - val res = provider.findClasslike( - entry.value.single().typeConstructor.dri, - entry.key) - assertEquals("Job", res?.name) - assertEquals("kotlinx.coroutines/Job///PointingToDeclaration/", res?.dri?.toString()) - - val supertypes = res?.cast<DInterface>()?.supertypes?.values?.single() - ?.map { it.typeConstructor.dri.classNames } - assertEquals( - listOf("CoroutineContext.Element"), - supertypes - ) - } - } - } - - @Test - fun `external documentable for nested class`() { - val configuration = dokkaConfiguration { - sourceSets { - sourceSet { - sourceRoots = listOf("src") - analysisPlatform = "jvm" - classpath += jvmStdlibPath!! - } - } - } - - testInline( - """ - /src/com/sample/MyList.kt - package com.sample - abstract class MyEntry: Map.Entry<Int, String> - """.trimIndent(), - configuration - ) { - lateinit var provider: ExternalDocumentablesProvider - pluginsSetupStage = { - provider = it.plugin<InternalKotlinAnalysisPlugin>().querySingle { externalDocumentablesProvider } - } - documentablesTransformationStage = { mod -> - val entry = mod.packages.single().classlikes.single().cast<DClass>().supertypes.entries.single() - val res = provider.findClasslike( - entry.value.single().typeConstructor.dri, - entry.key) - assertEquals("Entry", res?.name) - assertEquals("kotlin.collections/Map.Entry///PointingToDeclaration/", res?.dri?.toString()) - } - } - } -} |