aboutsummaryrefslogtreecommitdiff
path: root/plugins/base/src
diff options
context:
space:
mode:
authorIgnat Beresnev <ignat.beresnev@jetbrains.com>2022-06-30 16:02:27 +0200
committerGitHub <noreply@github.com>2022-06-30 16:02:27 +0200
commit76334ec6e8d66b377fc9c37187725f8d267d5ba2 (patch)
treed3423cd6b47dc1e50edf378c8d33b131c606e19b /plugins/base/src
parent623b0e729c805e2ec019b1f56f67c5a2cf7eb327 (diff)
downloaddokka-76334ec6e8d66b377fc9c37187725f8d267d5ba2.tar.gz
dokka-76334ec6e8d66b377fc9c37187725f8d267d5ba2.tar.bz2
dokka-76334ec6e8d66b377fc9c37187725f8d267d5ba2.zip
Do not generate source links for synthetic elements (#2547)
Fixes #2544
Diffstat (limited to 'plugins/base/src')
-rw-r--r--plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt52
-rw-r--r--plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt60
-rw-r--r--plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt421
3 files changed, 514 insertions, 19 deletions
diff --git a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
index 93305055..50add451 100644
--- a/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
+++ b/plugins/base/src/main/kotlin/transformers/pages/sourcelinks/SourceLinksTransformer.kt
@@ -24,36 +24,47 @@ import java.io.File
class SourceLinksTransformer(val context: DokkaContext) : PageTransformer {
- private val builder : PageContentBuilder = PageContentBuilder(
+ private val builder : PageContentBuilder = PageContentBuilder(
context.plugin<DokkaBase>().querySingle { commentsToContentConverter },
context.plugin<DokkaBase>().querySingle { signatureProvider },
context.logger
)
- override fun invoke(input: RootPageNode) =
- input.transformContentPagesTree { node ->
+ override fun invoke(input: RootPageNode): RootPageNode {
+ val sourceLinks = getSourceLinksFromConfiguration()
+ if (sourceLinks.isEmpty()) {
+ return input
+ }
+ return input.transformContentPagesTree { node ->
when (node) {
is WithDocumentables ->
- node.documentables.filterIsInstance<WithSources>().flatMap { resolveSources(it) }
- .takeIf { it.isNotEmpty() }
- ?.let { node.addSourcesContent(it) }
- ?: node
+ node.documentables
+ .filterIsInstance<WithSources>()
+ .flatMap { resolveSources(sourceLinks, it) }
+ .takeIf { it.isNotEmpty() }
+ ?.let { node.addSourcesContent(it) }
+ ?: node
else -> node
}
}
+ }
- private fun getSourceLinks() = context.configuration.sourceSets
- .flatMap { it.sourceLinks.map { sl -> SourceLink(sl, it) } }
+ private fun getSourceLinksFromConfiguration(): List<SourceLink> {
+ return context.configuration.sourceSets
+ .flatMap { it.sourceLinks.map { sl -> SourceLink(sl, it) } }
+ }
- private fun resolveSources(documentable: WithSources) = documentable.sources
- .mapNotNull { entry ->
- getSourceLinks().find { File(entry.value.path).startsWith(it.path) && it.sourceSetData == entry.key }?.let {
- Pair(
- entry.key,
- entry.value.toLink(it)
- )
- }
+ private fun resolveSources(
+ sourceLinks: List<SourceLink>, documentable: WithSources
+ ): List<Pair<DokkaSourceSet, String>> {
+ return documentable.sources.mapNotNull { (sourceSet, documentableSource) ->
+ val sourceLink = sourceLinks.find { sourceLink ->
+ File(documentableSource.path).startsWith(sourceLink.path) && sourceLink.sourceSetData == sourceSet
+ } ?: return@mapNotNull null
+
+ sourceSet to documentableSource.toLink(sourceLink)
}
+ }
private fun ContentPage.addSourcesContent(sources: List<Pair<DokkaSourceSet, String>>) = builder
.buildSourcesContent(this, sources)
@@ -89,8 +100,8 @@ class SourceLinksTransformer(val context: DokkaContext) : PageTransformer {
}
private fun DocumentableSource.toLink(sourceLink: SourceLink): String {
- val sourcePath = File(this.path).canonicalPath.replace("\\", "/")
- val sourceLinkPath = File(sourceLink.path).canonicalPath.replace("\\", "/")
+ val sourcePath = File(this.path).invariantSeparatorsPath
+ val sourceLinkPath = File(sourceLink.path).invariantSeparatorsPath
val lineNumber = when (this) {
is DescriptorDocumentableSource -> this.descriptor
@@ -134,6 +145,9 @@ class SourceLinksTransformer(val context: DokkaContext) : PageTransformer {
}
private fun PsiElement.lineNumber(): Int? {
+ // synthetic and some light methods might return null
+ val textRange = textRange ?: return null
+
val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile)
// IJ uses 0-based line-numbers; external source browsers use 1-based
return doc?.getLineNumber(textRange.startOffset)?.plus(1)
diff --git a/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt
new file mode 100644
index 00000000..6a59a57e
--- /dev/null
+++ b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt
@@ -0,0 +1,60 @@
+package enums
+
+import org.jetbrains.dokka.SourceLinkDefinitionImpl
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.junit.jupiter.api.Test
+import signatures.renderedContent
+import utils.TestOutputWriterPlugin
+import java.net.URL
+import kotlin.test.assertEquals
+
+class JavaEnumsTest : BaseAbstractTest() {
+
+ // Shouldn't try to give source links to synthetic methods (values, valueOf) if any are present
+ // https://github.com/Kotlin/dokka/issues/2544
+ @Test
+ fun `java enum with configured source links should not fail build due to any synthetic methods`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ sourceLinks = listOf(
+ SourceLinkDefinitionImpl(
+ localDirectory = "src/main/java",
+ remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/java"),
+ remoteLineSuffix = "#L"
+ )
+ )
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/java/basic/JavaEnum.java
+ |package testpackage
+ |
+ |public enum JavaEnum {
+ | ONE, TWO, THREE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val enumPage = writerPlugin.writer.renderedContent("root/testpackage/-java-enum/index.html")
+ val sourceLink = enumPage.select("div[data-togglable=Sources]")
+ .select("a[href]")
+ .attr("href")
+
+
+ assertEquals(
+ "https://github.com/user/repo/tree/master/src/main/java/basic/JavaEnum.java#L3",
+ sourceLink
+ )
+ }
+ }
+ }
+}
diff --git a/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt
new file mode 100644
index 00000000..f527b87c
--- /dev/null
+++ b/plugins/base/src/test/kotlin/enums/KotlinEnumsTest.kt
@@ -0,0 +1,421 @@
+package enums
+
+import matchers.content.*
+import org.jetbrains.dokka.SourceLinkDefinitionImpl
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.*
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import signatures.renderedContent
+import utils.TestOutputWriter
+import utils.TestOutputWriterPlugin
+import java.io.File
+import java.net.URL
+
+class KotlinEnumsTest : BaseAbstractTest() {
+
+ @Test
+ fun `should preserve enum source ordering for documentables`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | ZERO,
+ | ONE,
+ | TWO,
+ | THREE,
+ | FOUR,
+ | FIVE,
+ | SIX,
+ | SEVEN,
+ | EIGHT,
+ | NINE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ documentablesTransformationStage = { module ->
+ val testPackage = module.packages[0]
+ assertEquals("testpackage", testPackage.name)
+
+ val testEnum = testPackage.classlikes[0] as DEnum
+ assertEquals("TestEnum", testEnum.name)
+
+ val enumEntries = testEnum.entries
+ assertEquals(10, enumEntries.count())
+
+ assertEquals("ZERO", enumEntries[0].name)
+ assertEquals("ONE", enumEntries[1].name)
+ assertEquals("TWO", enumEntries[2].name)
+ assertEquals("THREE", enumEntries[3].name)
+ assertEquals("FOUR", enumEntries[4].name)
+ assertEquals("FIVE", enumEntries[5].name)
+ assertEquals("SIX", enumEntries[6].name)
+ assertEquals("SEVEN", enumEntries[7].name)
+ assertEquals("EIGHT", enumEntries[8].name)
+ assertEquals("NINE", enumEntries[9].name)
+ }
+ }
+ }
+
+ @Test
+ fun `should preserve enum source ordering for generated pages`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | ZERO,
+ | ONE,
+ | TWO,
+ | THREE,
+ | FOUR,
+ | FIVE,
+ | SIX,
+ | SEVEN,
+ | EIGHT,
+ | NINE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ pagesGenerationStage = { rootPage ->
+ val packagePage = rootPage.children[0]
+ assertEquals("testpackage", packagePage.name)
+
+ val testEnumNode = packagePage.children[0]
+ assertEquals("TestEnum", testEnumNode.name)
+
+ val enumEntries = testEnumNode.children
+ assertEquals(10, enumEntries.size)
+
+ assertEquals("ZERO", enumEntries[0].name)
+ assertEquals("ONE", enumEntries[1].name)
+ assertEquals("TWO", enumEntries[2].name)
+ assertEquals("THREE", enumEntries[3].name)
+ assertEquals("FOUR", enumEntries[4].name)
+ assertEquals("FIVE", enumEntries[5].name)
+ assertEquals("SIX", enumEntries[6].name)
+ assertEquals("SEVEN", enumEntries[7].name)
+ assertEquals("EIGHT", enumEntries[8].name)
+ assertEquals("NINE", enumEntries[9].name)
+ }
+ }
+ }
+
+ @Test
+ fun `should preserve enum source ordering for rendered entries`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | ZERO,
+ | ONE,
+ | TWO,
+ | THREE,
+ | FOUR,
+ | FIVE,
+ | SIX,
+ | SEVEN,
+ | EIGHT,
+ | NINE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val enumEntriesOnPage = writerPlugin.writer.renderedContent("root/testpackage/-test-enum/index.html")
+ .select("div.table[data-togglable=Entries]")
+ .select("div.table-row")
+ .select("div.keyValue")
+ .select("div.title")
+ .select("a")
+
+ val enumEntries = enumEntriesOnPage.map { it.text() }
+ assertEquals(10, enumEntries.size)
+
+ assertEquals("ZERO", enumEntries[0])
+ assertEquals("ONE", enumEntries[1])
+ assertEquals("TWO", enumEntries[2])
+ assertEquals("THREE", enumEntries[3])
+ assertEquals("FOUR", enumEntries[4])
+ assertEquals("FIVE", enumEntries[5])
+ assertEquals("SIX", enumEntries[6])
+ assertEquals("SEVEN", enumEntries[7])
+ assertEquals("EIGHT", enumEntries[8])
+ assertEquals("NINE", enumEntries[9])
+ }
+ }
+ }
+
+ @Test
+ fun `should preserve enum source ordering for navigation menu`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | ZERO,
+ | ONE,
+ | TWO,
+ | THREE,
+ | FOUR,
+ | FIVE,
+ | SIX,
+ | SEVEN,
+ | EIGHT,
+ | NINE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val sideMenu = writerPlugin.writer.navigationHtml().select("div.sideMenuPart")
+
+ assertEquals("ZERO", sideMenu.select("#root-nav-submenu-0-0-0").text())
+ assertEquals("ONE", sideMenu.select("#root-nav-submenu-0-0-1").text())
+ assertEquals("TWO", sideMenu.select("#root-nav-submenu-0-0-2").text())
+ assertEquals("THREE", sideMenu.select("#root-nav-submenu-0-0-3").text())
+ assertEquals("FOUR", sideMenu.select("#root-nav-submenu-0-0-4").text())
+ assertEquals("FIVE", sideMenu.select("#root-nav-submenu-0-0-5").text())
+ assertEquals("SIX", sideMenu.select("#root-nav-submenu-0-0-6").text())
+ assertEquals("SEVEN", sideMenu.select("#root-nav-submenu-0-0-7").text())
+ assertEquals("EIGHT", sideMenu.select("#root-nav-submenu-0-0-8").text())
+ assertEquals("NINE", sideMenu.select("#root-nav-submenu-0-0-9").text())
+ }
+ }
+ }
+
+ fun TestOutputWriter.navigationHtml(): Element = contents.getValue("navigation.html").let { Jsoup.parse(it) }
+
+ @Test
+ fun `should handle companion object within enum`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/Test.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | E1,
+ | E2;
+ | companion object {}
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ documentablesTransformationStage = { m ->
+ m.packages.let { p ->
+ assertTrue(p.isNotEmpty(), "Package list cannot be empty")
+ p.first().classlikes.let { c ->
+ assertTrue(c.isNotEmpty(), "Classlikes list cannot be empty")
+
+ val enum = c.first() as DEnum
+ assertNotNull(enum.companion)
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun enumWithMethods() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/TestEnum.kt
+ |package testpackage
+ |
+ |
+ |interface Sample {
+ | fun toBeImplemented(): String
+ |}
+ |
+ |enum class TestEnum: Sample {
+ | E1 {
+ | override fun toBeImplemented(): String = "e1"
+ | }
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ documentablesTransformationStage = { m ->
+ m.packages.let { p ->
+ p.first().classlikes.let { c ->
+ val enum = c.first { it is DEnum } as DEnum
+ val first = enum.entries.first()
+
+ assertNotNull(first.functions.find { it.name == "toBeImplemented" })
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun enumWithAnnotationsOnEntries() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ }
+ }
+ }
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/TestEnum.kt
+ |package testpackage
+ |
+ |enum class TestEnum {
+ | /**
+ | Sample docs for E1
+ | **/
+ | @SinceKotlin("1.3") // This annotation is transparent due to lack of @MustBeDocumented annotation
+ | E1
+ |}
+ """.trimMargin(),
+ configuration
+ ) {
+ pagesTransformationStage = { m ->
+ val entryNode = m.children.first { it.name == "testpackage" }.children.first { it.name == "TestEnum" }.children.firstIsInstance<ClasslikePageNode>()
+ val signature = (entryNode.content as ContentGroup).dfs { it is ContentGroup && it.dci.toString() == "[testpackage/TestEnum.E1///PointingToDeclaration/{\"org.jetbrains.dokka.links.EnumEntryDRIExtra\":{\"key\":\"org.jetbrains.dokka.links.EnumEntryDRIExtra\"}}][Cover]" } as ContentGroup
+
+ signature.assertNode {
+ header(1) { +"E1" }
+ platformHinted {
+ group {
+ group {
+ link { +"E1" }
+ }
+ }
+ group {
+ group {
+ group {
+ +"Sample docs for E1"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Shouldn't try to give source links to synthetic methods (values, valueOf) if any are present
+ // Initially reported for Java, making sure it doesn't fail for Kotlin either
+ // https://github.com/Kotlin/dokka/issues/2544
+ @Test
+ fun `kotlin enum with configured source links should not fail the build due to synthetic methods`() {
+ val configuration = dokkaConfiguration {
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ sourceLinks = listOf(
+ SourceLinkDefinitionImpl(
+ localDirectory = "src/main/kotlin",
+ remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/kotlin"),
+ remoteLineSuffix = "#L"
+ )
+ )
+ }
+ }
+ }
+
+ val writerPlugin = TestOutputWriterPlugin()
+
+ testInline(
+ """
+ |/src/main/kotlin/basic/KotlinEnum.kt
+ |package testpackage
+ |
+ |enum class KotlinEnum {
+ | ONE, TWO, THREE
+ |}
+ """.trimMargin(),
+ configuration,
+ pluginOverrides = listOf(writerPlugin)
+ ) {
+ renderingStage = { _, _ ->
+ val sourceLink = writerPlugin.writer.renderedContent("root/testpackage/-kotlin-enum/index.html")
+ .select("div[data-togglable=Sources]")
+ .select("a[href]")
+ .attr("href")
+
+ assertEquals(
+ "https://github.com/user/repo/tree/master/src/main/kotlin/basic/KotlinEnum.kt#L3",
+ sourceLink
+ )
+ }
+ }
+ }
+}