From 6b729257b89ff5a19a52045efa386349feec0bc6 Mon Sep 17 00:00:00 2001 From: Andrzej Ratajczak <32793002+BarkingBad@users.noreply.github.com> Date: Wed, 12 Jan 2022 15:04:59 +0100 Subject: [PATCH] Add global settings to JSON dokka cli input (#2292) * Add global settings to JSON dokka cli input * Apply requested changes * Move initialization of global arguments to extension function in core module --- core/api/core.api | 17 +++ core/src/main/kotlin/configuration.kt | 29 +++++ core/src/main/kotlin/utilities/json.kt | 1 - .../dokka/it/cli/CliIntegrationTest.kt | 111 +++++++++++++++++- .../org/jetbrains/dokka/it/cli/jsonBuilder.kt | 52 ++++++++ .../integrationTest/resources/my-file.json | 0 plugins/base/frontend/package-lock.json | 2 +- runners/cli/api/cli.api | 1 + runners/cli/build.gradle.kts | 1 + runners/cli/src/main/kotlin/cli/main.kt | 27 +++-- runners/cli/src/test/kotlin/cli/CliTest.kt | 30 +++++ runners/cli/src/test/resources/my-file.json | 51 ++++++++ 12 files changed, 309 insertions(+), 13 deletions(-) create mode 100644 integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/jsonBuilder.kt create mode 100644 integration-tests/cli/src/integrationTest/resources/my-file.json create mode 100644 runners/cli/src/test/kotlin/cli/CliTest.kt create mode 100644 runners/cli/src/test/resources/my-file.json diff --git a/core/api/core.api b/core/api/core.api index 471f63142a..8b9e5c7b02 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -4,6 +4,8 @@ public final class org/jetbrains/dokka/ConfigurationKt { public static final fun ExternalDocumentationLink (Ljava/net/URL;Ljava/net/URL;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl; public static synthetic fun ExternalDocumentationLink$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl; public static synthetic fun ExternalDocumentationLink$default (Ljava/net/URL;Ljava/net/URL;ILjava/lang/Object;)Lorg/jetbrains/dokka/ExternalDocumentationLinkImpl; + public static final fun GlobalDokkaConfiguration (Ljava/lang/String;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration; + public static final fun apply (Lorg/jetbrains/dokka/DokkaConfiguration;Lorg/jetbrains/dokka/GlobalDokkaConfiguration;)Lorg/jetbrains/dokka/DokkaConfiguration; public static final fun build (Ljava/lang/Iterable;)Ljava/util/List; public static final fun toJsonString (Lorg/jetbrains/dokka/DokkaConfiguration;)Ljava/lang/String; public static final fun toJsonString (Lorg/jetbrains/dokka/plugability/ConfigurableBlock;)Ljava/lang/String; @@ -343,6 +345,21 @@ public final class org/jetbrains/dokka/ExternalDocumentationLinkImpl : org/jetbr public fun toString ()Ljava/lang/String; } +public final class org/jetbrains/dokka/GlobalDokkaConfiguration { + public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration; + public static synthetic fun copy$default (Lorg/jetbrains/dokka/GlobalDokkaConfiguration;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/GlobalDokkaConfiguration; + public fun equals (Ljava/lang/Object;)Z + public final fun getExternalDocumentationLinks ()Ljava/util/List; + public final fun getPerPackageOptions ()Ljava/util/List; + public final fun getSourceLinks ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class org/jetbrains/dokka/PackageOptionsImpl : org/jetbrains/dokka/DokkaConfiguration$PackageOptions { public fun (Ljava/lang/String;ZLjava/lang/Boolean;ZZLjava/util/Set;)V public final fun component1 ()Ljava/lang/String; diff --git a/core/src/main/kotlin/configuration.kt b/core/src/main/kotlin/configuration.kt index 46b338d241..038a5bb777 100644 --- a/core/src/main/kotlin/configuration.kt +++ b/core/src/main/kotlin/configuration.kt @@ -3,6 +3,7 @@ package org.jetbrains.dokka import org.jetbrains.dokka.plugability.ConfigurableBlock +import org.jetbrains.dokka.utilities.cast import org.jetbrains.dokka.utilities.parseJson import org.jetbrains.dokka.utilities.toJsonString import java.io.File @@ -86,6 +87,34 @@ data class DokkaSourceSetID( fun DokkaConfigurationImpl(json: String): DokkaConfigurationImpl = parseJson(json) +/** + * Global options are applied to all packages and modules and overwrite package configuration. + * + * These are handy if we have multiple sourcesets sharing the same global options as it reduces the size of the boilerplate. + * Otherwise, the user would be enforced to repeat all these options per each sourceset. + */ +data class GlobalDokkaConfiguration( + val perPackageOptions: List?, + val externalDocumentationLinks: List?, + val sourceLinks: List? +) + +fun GlobalDokkaConfiguration(json: String): GlobalDokkaConfiguration = parseJson(json) + +fun DokkaConfiguration.apply(globals: GlobalDokkaConfiguration): DokkaConfiguration = this.apply { + sourceSets.forEach { + it.perPackageOptions.cast>().addAll(globals.perPackageOptions ?: emptyList()) + } + + sourceSets.forEach { + it.externalDocumentationLinks.cast>().addAll(globals.externalDocumentationLinks ?: emptyList()) + } + + sourceSets.forEach { + it.sourceLinks.cast>().addAll(globals.sourceLinks ?: emptyList()) + } +} + fun DokkaConfiguration.toJsonString(): String = toJsonString(this) fun T.toJsonString(): String = toJsonString(this) diff --git a/core/src/main/kotlin/utilities/json.kt b/core/src/main/kotlin/utilities/json.kt index 211037d6c3..d3762f6d74 100644 --- a/core/src/main/kotlin/utilities/json.kt +++ b/core/src/main/kotlin/utilities/json.kt @@ -34,7 +34,6 @@ internal fun toJsonString(value: Any): String = objectMapper.writeValueAsString( @PublishedApi internal inline fun parseJson(json: String): T = parseJson(json, TypeReference()) - @PublishedApi internal fun parseJson(json: String, typeReference: TypeReference): T = objectMapper.readValue(json, typeReference.jackson) diff --git a/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/CliIntegrationTest.kt b/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/CliIntegrationTest.kt index b87badd71c..b94df32a31 100644 --- a/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/CliIntegrationTest.kt +++ b/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/CliIntegrationTest.kt @@ -2,6 +2,8 @@ package org.jetbrains.dokka.it.cli import org.jetbrains.dokka.it.awaitProcessResult import java.io.File +import java.io.PrintWriter +import java.lang.IllegalStateException import kotlin.test.* class CliIntegrationTest : AbstractCliIntegrationTest() { @@ -193,7 +195,7 @@ class CliIntegrationTest : AbstractCliIntegrationTest() { } @Test - fun `logging level should be respected`(){ + fun `logging level should be respected`() { val dokkaOutputDir = File(projectDir, "output") assertTrue(dokkaOutputDir.mkdirs()) val process = ProcessBuilder( @@ -259,4 +261,111 @@ class CliIntegrationTest : AbstractCliIntegrationTest() { ) ) } + + + @Test + fun `should accept json as input configuration`() { + val dokkaOutputDir = File(projectDir, "output") + assertTrue(dokkaOutputDir.mkdirs()) + val jsonPath = javaClass.getResource("/my-file.json")?.path ?: throw IllegalStateException("No JSON found!") + PrintWriter(jsonPath).run { + write(jsonBuilder(dokkaOutputDir.path, basePluginJarFile.path, File(projectDir, "src").path, reportUndocumented = true)) + close() + } + + val process = ProcessBuilder( + "java", "-jar", cliJarFile.path, jsonPath + ).redirectErrorStream(true).start() + + val result = process.awaitProcessResult() + assertEquals(0, result.exitCode, "Expected exitCode 0 (Success)") + + val extensionLoadedRegex = Regex("""Extension: org\.jetbrains\.dokka\.base\.DokkaBase""") + val amountOfExtensionsLoaded = extensionLoadedRegex.findAll(result.output).count() + + assertTrue( + amountOfExtensionsLoaded > 10, + "Expected more than 10 extensions being present (found $amountOfExtensionsLoaded)" + ) + + val undocumentedReportRegex = Regex("""Undocumented:""") + val amountOfUndocumentedReports = undocumentedReportRegex.findAll(result.output).count() + assertTrue( + amountOfUndocumentedReports > 0, + "Expected at least one report of undocumented code (found $amountOfUndocumentedReports)" + ) + + assertTrue(dokkaOutputDir.isDirectory, "Missing dokka output directory") + } + + /** + * This test disables global `reportUndocumneted` property and set `reportUndocumented` via perPackageOptions to + * make sure that global settings apply to dokka context. + */ + @Test + fun `global settings should overwrite package options in configuration`() { + val dokkaOutputDir = File(projectDir, "output") + assertTrue(dokkaOutputDir.mkdirs()) + val jsonPath = javaClass.getResource("/my-file.json")?.path ?: throw IllegalStateException("No JSON found!") + PrintWriter(jsonPath).run { + write( + jsonBuilder( + outputPath = dokkaOutputDir.path, + pluginsClasspath = basePluginJarFile.path, + projectPath = File(projectDir, "src").path, + globalSourceLinks = """ + { + "localDirectory": "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin", + "remoteUrl": "https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-gradle-example/src/main/kotlin", + "remoteLineSuffix": "#L" + } + """.trimIndent(), + globalExternalDocumentationLinks = """ + { + "url": "https://docs.oracle.com/javase/8/docs/api/", + "packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list" + }, + { + "url": "https://kotlinlang.org/api/latest/jvm/stdlib/", + "packageListUrl": "https://kotlinlang.org/api/latest/jvm/stdlib/package-list" + } + """.trimIndent(), + globalPerPackageOptions = """ + { + "matchingRegex": ".*", + "skipDeprecated": "true", + "reportUndocumented": "true", + "documentedVisibilities": ["PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "PACKAGE"] + } + """.trimIndent(), + reportUndocumented = false + ), + ) + close() + } + + val process = ProcessBuilder( + "java", "-jar", cliJarFile.path, jsonPath + ).redirectErrorStream(true).start() + + val result = process.awaitProcessResult() + assertEquals(0, result.exitCode, "Expected exitCode 0 (Success)") + + val extensionLoadedRegex = Regex("""Extension: org\.jetbrains\.dokka\.base\.DokkaBase""") + val amountOfExtensionsLoaded = extensionLoadedRegex.findAll(result.output).count() + + assertTrue( + amountOfExtensionsLoaded > 10, + "Expected more than 10 extensions being present (found $amountOfExtensionsLoaded)" + ) + + val undocumentedReportRegex = Regex("""Undocumented:""") + val amountOfUndocumentedReports = undocumentedReportRegex.findAll(result.output).count() + assertTrue( + amountOfUndocumentedReports > 0, + "Expected at least one report of undocumented code (found $amountOfUndocumentedReports)" + ) + + assertTrue(dokkaOutputDir.isDirectory, "Missing dokka output directory") + } } diff --git a/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/jsonBuilder.kt b/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/jsonBuilder.kt new file mode 100644 index 0000000000..d5d1df82e6 --- /dev/null +++ b/integration-tests/cli/src/integrationTest/kotlin/org/jetbrains/dokka/it/cli/jsonBuilder.kt @@ -0,0 +1,52 @@ +package org.jetbrains.dokka.it.cli + +fun jsonBuilder( + outputPath: String, + pluginsClasspath: String, + projectPath: String, + globalSourceLinks: String = "", + globalExternalDocumentationLinks: String = "", + globalPerPackageOptions: String = "", + reportUndocumented: Boolean = false + +): String { + return """{ + "moduleName": "Dokka Example", + "moduleVersion": null, + "outputDir": "$outputPath", + "pluginsClasspath": ["$pluginsClasspath"], + "cacheRoot": null, + "offlineMode": false, + "sourceLinks": [$globalSourceLinks], + "externalDocumentationLinks": [$globalExternalDocumentationLinks], + "perPackageOptions": [$globalPerPackageOptions], + "sourceSets": [ + { + "displayName": "jvm", + "sourceSetID": { + "scopeId": ":dokkaHtml", + "sourceSetName": "main" + }, + "sourceRoots": [ + "$projectPath" + ], + "dependentSourceSets": [], + "samples": [], + "includes": [], + "includeNonPublic": false, + "reportUndocumented": $reportUndocumented, + "skipEmptyPackages": true, + "skipDeprecated": false, + "jdkVersion": 8, + "sourceLinks": [], + "perPackageOptions": [], + "externalDocumentationLinks": [], + "noStdlibLink": false, + "noJdkLink": false, + "suppressedFiles": [], + "analysisPlatform": "jvm" + } + ] +} +""" +} diff --git a/integration-tests/cli/src/integrationTest/resources/my-file.json b/integration-tests/cli/src/integrationTest/resources/my-file.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/base/frontend/package-lock.json b/plugins/base/frontend/package-lock.json index 997f5c0355..a28d810e70 100644 --- a/plugins/base/frontend/package-lock.json +++ b/plugins/base/frontend/package-lock.json @@ -41,7 +41,7 @@ "terser-webpack-plugin": "^5.2.5" }, "engines": { - "node": ">=17.0.0" + "node": ">=16.0.0" } }, "node_modules/@babel/code-frame": { diff --git a/runners/cli/api/cli.api b/runners/cli/api/cli.api index e1e52c0572..208235aef5 100644 --- a/runners/cli/api/cli.api +++ b/runners/cli/api/cli.api @@ -89,6 +89,7 @@ public final class org/jetbrains/dokka/GlobalArguments : org/jetbrains/dokka/Dok public final class org/jetbrains/dokka/MainKt { public static final fun defaultLinks (Lorg/jetbrains/dokka/DokkaConfiguration$DokkaSourceSet;)Ljava/util/List; + public static final fun initializeConfiguration (Lorg/jetbrains/dokka/GlobalArguments;)Lorg/jetbrains/dokka/DokkaConfiguration; public static final fun main ([Ljava/lang/String;)V public static final fun parseLinks (Ljava/util/List;)Ljava/util/List; } diff --git a/runners/cli/build.gradle.kts b/runners/cli/build.gradle.kts index 08fadec9e3..2785e92fdd 100644 --- a/runners/cli/build.gradle.kts +++ b/runners/cli/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3") implementation(project(":core")) implementation(kotlin("stdlib")) + testImplementation(kotlin("test-junit")) } tasks { diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index 739539a997..1dc32a45a5 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -2,10 +2,7 @@ package org.jetbrains.dokka import kotlinx.cli.* import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink -import org.jetbrains.dokka.utilities.DokkaConsoleLogger -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.utilities.LoggingLevel -import org.jetbrains.dokka.utilities.cast +import org.jetbrains.dokka.utilities.* import java.io.* import java.net.MalformedURLException import java.net.URL @@ -80,6 +77,7 @@ class GlobalArguments(args: Array) : DokkaConfiguration { private val _includes by parser.option( ArgTypeFile, + fullName = "includes", description = "Markdown files that would be displayed in multi-module page separated by the semicolon `;`)" ).delimiter(";") @@ -406,14 +404,23 @@ fun parseLinks(links: List): List { } } +fun initializeConfiguration(globalArguments: GlobalArguments): DokkaConfiguration = if (globalArguments.json != null) { + val jsonContent = Paths.get(checkNotNull(globalArguments.json)).toFile().readText() + val globals = GlobalDokkaConfiguration(jsonContent) + val dokkaConfigurationImpl = DokkaConfigurationImpl(jsonContent) + + dokkaConfigurationImpl.apply(globals).apply { + sourceSets.forEach { + it.externalDocumentationLinks.cast>().addAll(defaultLinks(it)) + } + } + } else { + globalArguments + } + fun main(args: Array) { val globalArguments = GlobalArguments(args) - val configuration = if (globalArguments.json != null) - DokkaConfigurationImpl( - Paths.get(checkNotNull(globalArguments.json)).toFile().readText() - ) - else - globalArguments + val configuration = initializeConfiguration(globalArguments) DokkaGenerator(configuration, globalArguments.logger).generate() } diff --git a/runners/cli/src/test/kotlin/cli/CliTest.kt b/runners/cli/src/test/kotlin/cli/CliTest.kt new file mode 100644 index 0000000000..5910e93871 --- /dev/null +++ b/runners/cli/src/test/kotlin/cli/CliTest.kt @@ -0,0 +1,30 @@ +package org.jetbrains.dokka + +import junit.framework.Assert.assertTrue +import org.junit.Test +import java.lang.IllegalStateException +import java.nio.file.Paths +import kotlin.test.assertEquals + +class CliIntegrationTest { + + @Test + fun `should apply global settings to all source sets`() { + val jsonPath = Paths.get(javaClass.getResource("/my-file.json")?.toURI() ?: throw IllegalStateException("No JSON found!")).toFile().toString() + val globalArguments = GlobalArguments(arrayOf(jsonPath)) + + val configuration = initializeConfiguration(globalArguments) + + configuration.sourceSets.forEach { + assertTrue(it.perPackageOptions.isNotEmpty()) + assertTrue(it.sourceLinks.isNotEmpty()) + assertTrue(it.externalDocumentationLinks.isNotEmpty()) + + assertTrue(it.externalDocumentationLinks.any { it.url.toString() == "https://docs.oracle.com/javase/8/docs/api/" }) + assertEquals(it.sourceLinks.single().localDirectory, "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin") + assertEquals(it.perPackageOptions.single().matchingRegex, "my-custom-regex") + } + + } + +} diff --git a/runners/cli/src/test/resources/my-file.json b/runners/cli/src/test/resources/my-file.json new file mode 100644 index 0000000000..49dda8149d --- /dev/null +++ b/runners/cli/src/test/resources/my-file.json @@ -0,0 +1,51 @@ +{ + "moduleName": "Dokka Example", + "moduleVersion": null, + "outputDir": "$outputPath", + "pluginsClasspath": ["$pluginsClasspath"], + "cacheRoot": null, + "offlineMode": false, + "sourceLinks": [{ + "localDirectory": "/home/Vadim.Mishenev/dokka/examples/cli/src/main/kotlin", + "remoteUrl": "https://github.com/Kotlin/dokka/tree/master/examples/gradle/dokka-gradle-example/src/main/kotlin", + "remoteLineSuffix": "#L" + }], + "externalDocumentationLinks": [{ + "url": "https://docs.oracle.com/javase/8/docs/api/", + "packageListUrl": "https://docs.oracle.com/javase/8/docs/api/package-list" + }], + "perPackageOptions": [{ + "matchingRegex": "my-custom-regex", + "skipDeprecated": "true", + "reportUndocumented": "true", + "includeNonPublic": "true", + "documentedVisibilities": ["PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "PACKAGE"] + }], + "sourceSets": [ + { + "displayName": "jvm", + "sourceSetID": { + "scopeId": ":dokkaHtml", + "sourceSetName": "main" + }, + "sourceRoots": [ + "$projectPath" + ], + "dependentSourceSets": [], + "samples": [], + "includes": [], + "includeNonPublic": false, + "reportUndocumented": false, + "skipEmptyPackages": true, + "skipDeprecated": false, + "jdkVersion": 8, + "sourceLinks": [], + "perPackageOptions": [], + "externalDocumentationLinks": [], + "noStdlibLink": false, + "noJdkLink": false, + "suppressedFiles": [], + "analysisPlatform": "jvm" + } + ] +}