-
Notifications
You must be signed in to change notification settings - Fork 391
/
SamplesTransformer.kt
161 lines (145 loc) · 7.23 KB
/
SamplesTransformer.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package org.jetbrains.dokka.base.transformers.pages.samples
import com.intellij.psi.PsiElement
import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.Platform
import org.jetbrains.dokka.analysis.AnalysisEnvironment
import org.jetbrains.dokka.analysis.DokkaMessageCollector
import org.jetbrains.dokka.analysis.DokkaResolutionFacade
import org.jetbrains.dokka.analysis.EnvironmentAndFacade
import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.renderers.sourceSets
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.doc.Sample
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.plugin
import org.jetbrains.dokka.plugability.querySingle
import org.jetbrains.dokka.transformers.pages.PageTransformer
import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.utils.PathUtil
abstract class SamplesTransformer(val context: DokkaContext) : PageTransformer {
abstract fun processBody(psiElement: PsiElement): String
abstract fun processImports(psiElement: PsiElement): String
final override fun invoke(input: RootPageNode): RootPageNode {
val analysis = setUpAnalysis(context)
val kotlinPlaygroundScript =
"<script src=\"https://unpkg.com/kotlin-playground@1\"></script>"
return input.transformContentPagesTree { page ->
val samples = (page as? WithDocumentables)?.documentables?.flatMap {
it.documentation.entries.flatMap { entry ->
entry.value.children.filterIsInstance<Sample>().map { entry.key to it }
}
}
samples?.fold(page as ContentPage) { acc, (sampleSourceSet, sample) ->
acc.modified(
content = acc.content.addSample(page, sampleSourceSet, sample.name, analysis),
embeddedResources = acc.embeddedResources + kotlinPlaygroundScript
)
} ?: page
}
}
private fun setUpAnalysis(context: DokkaContext) = context.configuration.sourceSets.associateWith { sourceSet ->
if (sourceSet.samples.isEmpty()) context.plugin<DokkaBase>()
.querySingle { kotlinAnalysis }[sourceSet] // from sourceSet.sourceRoots
else AnalysisEnvironment(DokkaMessageCollector(context.logger), sourceSet.analysisPlatform).run {
if (analysisPlatform == Platform.jvm) {
configureJdkClasspathRoots()
}
sourceSet.classpath.forEach(::addClasspath)
addSources(sourceSet.samples.toList())
loadLanguageVersionSettings(sourceSet.languageVersion, sourceSet.apiVersion)
val environment = createCoreEnvironment()
val (facade, _) = createResolutionFacade(environment)
EnvironmentAndFacade(environment, facade)
}
}
private fun ContentNode.addSample(
contentPage: ContentPage,
sourceSet: DokkaSourceSet,
fqName: String,
analysis: Map<DokkaSourceSet, EnvironmentAndFacade>
): ContentNode {
val facade = analysis[sourceSet]?.facade
?: return this.also { context.logger.warn("Cannot resolve facade for platform ${sourceSet.sourceSetID}") }
val psiElement = fqNameToPsiElement(facade, fqName)
?: return this.also { context.logger.warn("Cannot find PsiElement corresponding to $fqName") }
val imports =
processImports(psiElement)
val body = processBody(psiElement)
val node = contentCode(contentPage.sourceSets(), contentPage.dri, createSampleBody(imports, body), "kotlin")
return dfs(fqName, node)
}
protected open fun createSampleBody(imports: String, body: String) =
""" |$imports
|fun main() {
| //sampleStart
| $body
| //sampleEnd
|}""".trimMargin()
private fun ContentNode.dfs(fqName: String, node: ContentCodeBlock): ContentNode {
return when (this) {
is ContentHeader -> copy(children.map { it.dfs(fqName, node) })
is ContentDivergentGroup -> @Suppress("UNCHECKED_CAST") copy(children.map {
it.dfs(fqName, node)
} as List<ContentDivergentInstance>)
is ContentDivergentInstance -> copy(
before.let { it?.dfs(fqName, node) },
divergent.dfs(fqName, node),
after.let { it?.dfs(fqName, node) })
is ContentCodeBlock -> copy(children.map { it.dfs(fqName, node) })
is ContentCodeInline -> copy(children.map { it.dfs(fqName, node) })
is ContentDRILink -> copy(children.map { it.dfs(fqName, node) })
is ContentResolvedLink -> copy(children.map { it.dfs(fqName, node) })
is ContentEmbeddedResource -> copy(children.map { it.dfs(fqName, node) })
is ContentTable -> copy(children = children.map { it.dfs(fqName, node) as ContentGroup })
is ContentList -> copy(children.map { it.dfs(fqName, node) })
is ContentGroup -> copy(children.map { it.dfs(fqName, node) })
is PlatformHintedContent -> copy(inner.dfs(fqName, node))
is ContentText -> if (text == fqName) node else this
is ContentBreakLine -> this
else -> this.also { context.logger.error("Could not recognize $this ContentNode in SamplesTransformer") }
}
}
private fun fqNameToPsiElement(resolutionFacade: DokkaResolutionFacade, functionName: String): PsiElement? {
val packageName = functionName.takeWhile { it != '.' }
val descriptor = resolutionFacade.resolveSession.getPackageFragment(FqName(packageName))
?: return null.also { context.logger.warn("Cannot find descriptor for package $packageName") }
val symbol = resolveKDocLink(
BindingContext.EMPTY,
resolutionFacade,
descriptor,
null,
functionName.split(".")
).firstOrNull() ?: return null.also { context.logger.warn("Unresolved function $functionName in @sample") }
return DescriptorToSourceUtils.descriptorToDeclaration(symbol)
}
private fun contentCode(
sourceSets: Set<DisplaySourceSet>,
dri: Set<DRI>,
content: String,
language: String,
styles: Set<Style> = emptySet(),
extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
) =
ContentCodeBlock(
children = listOf(
ContentText(
text = content,
dci = DCI(dri, ContentKind.Sample),
sourceSets = sourceSets,
style = emptySet(),
extra = PropertyContainer.empty()
)
),
language = language,
dci = DCI(dri, ContentKind.Sample),
sourceSets = sourceSets,
style = styles + ContentStyle.RunnableSample + TextStyle.Monospace,
extra = extra
)
}