<lambda>null1 package org.jetbrains.dokka.Kotlin
2
3 import com.google.inject.Inject
4 import com.intellij.psi.PsiDocCommentOwner
5 import com.intellij.psi.PsiNamedElement
6 import com.intellij.psi.util.PsiTreeUtil
7 import org.intellij.markdown.parser.LinkMap
8 import org.jetbrains.dokka.*
9 import org.jetbrains.dokka.Samples.SampleProcessingService
10 import org.jetbrains.kotlin.descriptors.*
11 import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor
12 import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
13 import org.jetbrains.kotlin.idea.kdoc.findKDoc
14 import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
15 import org.jetbrains.kotlin.incremental.components.NoLookupLocation
16 import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
17 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
18 import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
19 import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
20 import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
21 import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
22 import org.jetbrains.kotlin.name.FqName
23 import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
24 import org.jetbrains.kotlin.psi.KtDeclaration
25 import org.jetbrains.kotlin.psi.KtNamedFunction
26 import org.jetbrains.kotlin.resolve.DescriptorUtils
27 import org.jetbrains.kotlin.resolve.annotations.argumentValue
28 import org.jetbrains.kotlin.resolve.constants.StringValue
29 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
30 import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
31 import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
32 import org.jetbrains.kotlin.resolve.source.PsiSourceElement
33 import java.util.regex.Pattern
34
35 private val REF_COMMAND = "ref"
36 private val NAME_COMMAND = "name"
37 private val DESCRIPTION_COMMAND = "description"
38 private val TEXT = Pattern.compile("(\\S+)\\s*(.*)", Pattern.DOTALL)
39 private val NAME_TEXT = Pattern.compile("(\\S+)(.*)", Pattern.DOTALL)
40
41 class DescriptorDocumentationParser @Inject constructor(
42 val options: DocumentationOptions,
43 val logger: DokkaLogger,
44 val linkResolver: DeclarationLinkResolver,
45 val resolutionFacade: DokkaResolutionFacade,
46 val refGraph: NodeReferenceGraph,
47 val sampleService: SampleProcessingService,
48 val signatureProvider: KotlinElementSignatureProvider,
49 val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
50 ) {
51 fun parseDocumentation(descriptor: DeclarationDescriptor, inline: Boolean = false): Content =
52 parseDocumentationAndDetails(descriptor, inline).first
53
54 fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor, inline: Boolean = false): Pair<Content, (DocumentationNode) -> Unit> {
55 if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor ||
56 descriptor is EnumEntrySyntheticClassDescriptor) {
57 return parseJavadoc(descriptor)
58 }
59
60 val kdoc = descriptor.findKDoc() ?: findStdlibKDoc(descriptor)
61 if (kdoc == null) {
62 if (options.effectivePackageOptions(descriptor.fqNameSafe).reportUndocumented && !descriptor.isDeprecated() &&
63 descriptor !is ValueParameterDescriptor && descriptor !is TypeParameterDescriptor &&
64 descriptor !is PropertyAccessorDescriptor && !descriptor.isSuppressWarning()) {
65 logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}")
66 }
67 return Content.Empty to { node -> }
68 }
69
70 val contextDescriptor =
71 (PsiTreeUtil.getParentOfType(kdoc, KDoc::class.java)?.context as? KtDeclaration)
72 ?.takeIf { it != descriptor.original.sourcePsi() }
73 ?.resolveToDescriptorIfAny()
74 ?: descriptor
75
76 // This will build the initial node for all content above the tags, however we also sometimes have @Sample
77 // tags between content, so we handle that case below
78 var kdocText = kdoc.getContent()
79 // workaround for code fence parsing problem in IJ markdown parser
80 if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) {
81 kdocText += "\n"
82 }
83 val tree = parseMarkdown(kdocText)
84 val linkMap = LinkMap.buildLinkMap(tree.node, kdocText)
85 val content = buildContent(tree, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }), inline)
86 if (kdoc is KDocSection) {
87 val tags = kdoc.getTags()
88 tags.forEach {
89 when (it.knownTag) {
90 KDocKnownTag.SAMPLE -> {
91 content.append(sampleService.resolveSample(contextDescriptor, it.getSubjectName(), it))
92 // If the sample tag has text below it, it will be considered as the child of the tag, so add it
93 val tagSubContent = it.getContent()
94 if (tagSubContent.isNotBlank()) {
95 val markdownNode = parseMarkdown(tagSubContent)
96 buildInlineContentTo(markdownNode, content, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }))
97 }
98 }
99 KDocKnownTag.SEE ->
100 content.addTagToSeeAlso(contextDescriptor, it)
101 KDocKnownTag.PARAM -> {
102 val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
103 section.append(ParameterInfoNode {
104 val signature = signatureProvider.signature(descriptor)
105 refGraph.lookupOrWarn(signature, logger)?.details?.find { node ->
106 node.kind == NodeKind.Parameter && node.name == it.getSubjectName()
107 }
108 })
109 val sectionContent = it.getContent()
110 val markdownNode = parseMarkdown(sectionContent)
111 buildInlineContentTo(markdownNode, section, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }))
112 }
113 else -> {
114 val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName())
115 val sectionContent = it.getContent()
116 val markdownNode = parseMarkdown(sectionContent)
117 buildInlineContentTo(markdownNode, section, LinkResolver(linkMap, { href -> linkResolver.resolveContentLink(contextDescriptor, href) }))
118 }
119 }
120 }
121 }
122 return content to { node ->
123 if (kdoc is KDocSection) {
124 val tags = kdoc.getTags()
125 node.addExtraTags(tags, descriptor)
126 }
127 }
128 }
129
130 /**
131 * Adds @attr tag. There are 3 types of syntax for this:
132 * *@attr ref <android.>R.styleable.<attribute_name>
133 * *@attr name <attribute_name>
134 * *@attr description <attribute_description>
135 * This also adds the @since and @apiSince tags.
136 */
137 private fun DocumentationNode.addExtraTags(tags: Array<KDocTag>, descriptor: DeclarationDescriptor) {
138 tags.forEach {
139 val name = it.name
140 if (name?.toLowerCase() == "attr") {
141 it.getAttr(descriptor)?.let { append(it, RefKind.Detail) }
142 } else if (name?.toLowerCase() == "since" || name?.toLowerCase() == "apisince") {
143 val apiLevel = DocumentationNode(it.getContent(), Content.Empty, NodeKind.ApiLevel)
144 append(apiLevel, RefKind.Detail)
145 } else if (name?.toLowerCase() == "deprecatedsince") {
146 val deprecatedLevel = DocumentationNode(it.getContent(), Content.Empty, NodeKind.DeprecatedLevel)
147 append(deprecatedLevel, RefKind.Detail)
148 } else if (name?.toLowerCase() == "artifactid") {
149 val artifactId = DocumentationNode(it.getContent(), Content.Empty, NodeKind.ArtifactId)
150 append(artifactId, RefKind.Detail)
151 }
152 }
153 }
154
155 private fun DeclarationDescriptor.isSuppressWarning(): Boolean {
156 val suppressAnnotation = annotations.findAnnotation(FqName(Suppress::class.qualifiedName!!))
157 return if (suppressAnnotation != null) {
158 @Suppress("UNCHECKED_CAST")
159 (suppressAnnotation.argumentValue("names")?.value as List<StringValue>).any { it.value == "NOT_DOCUMENTED" }
160 } else containingDeclaration?.isSuppressWarning() ?: false
161 }
162
163 /**
164 * Special case for generating stdlib documentation (the Any class to which the override chain will resolve
165 * is not the same one as the Any class included in the source scope).
166 */
167 fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? {
168 if (descriptor !is CallableMemberDescriptor) {
169 return null
170 }
171 val name = descriptor.name.asString()
172 if (name == "equals" || name == "hashCode" || name == "toString") {
173 var deepestDescriptor: CallableMemberDescriptor = descriptor
174 while (!deepestDescriptor.overriddenDescriptors.isEmpty()) {
175 deepestDescriptor = deepestDescriptor.overriddenDescriptors.first()
176 }
177 if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") {
178 val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassifierDescriptors(
179 FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.FROM_IDE)
180 anyClassDescriptors.forEach {
181 val anyMethod = (it as ClassDescriptor).getMemberScope(listOf())
182 .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS, { it == descriptor.name })
183 .single()
184 val kdoc = anyMethod.findKDoc()
185 if (kdoc != null) {
186 return kdoc
187 }
188 }
189 }
190 }
191 return null
192 }
193
194 fun parseJavadoc(descriptor: DeclarationDescriptor): Pair<Content, (DocumentationNode) -> Unit> {
195 val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi
196 if (psi is PsiDocCommentOwner) {
197 val parseResult = JavadocParser(
198 refGraph,
199 logger,
200 signatureProvider,
201 externalDocumentationLinkResolver
202 ).parseDocumentation(psi as PsiNamedElement)
203 return parseResult.content to { node ->
204 parseResult.deprecatedContent?.let {
205 val deprecationNode = DocumentationNode("", it, NodeKind.Modifier)
206 node.append(deprecationNode, RefKind.Deprecation)
207 }
208 if (node.kind in NodeKind.classLike) {
209 parseResult.attributeRefs.forEach {
210 val signature = node.detailOrNull(NodeKind.Signature)
211 val signatureName = signature?.name
212 val classAttrSignature = "${signatureName}:$it"
213 refGraph.register(classAttrSignature, DocumentationNode(node.name, Content.Empty, NodeKind.Attribute))
214 refGraph.link(node, classAttrSignature, RefKind.Detail)
215 refGraph.link(classAttrSignature, node, RefKind.Owner)
216 refGraph.link(classAttrSignature, it, RefKind.AttributeRef)
217 }
218 } else if (node.kind in NodeKind.memberLike) {
219 parseResult.attributeRefs.forEach {
220 refGraph.link(node, it, RefKind.HiddenLink)
221 }
222 }
223 parseResult.apiLevel?.let {
224 node.append(it, RefKind.Detail)
225 }
226 parseResult.deprecatedLevel?.let {
227 node.append(it, RefKind.Detail)
228 }
229 parseResult.artifactId?.let {
230 node.append(it, RefKind.Detail)
231 }
232 parseResult.attribute?.let {
233 val signature = node.detailOrNull(NodeKind.Signature)
234 val signatureName = signature?.name
235 val attrSignature = "AttrMain:$signatureName"
236 refGraph.register(attrSignature, it)
237 refGraph.link(attrSignature, node, RefKind.AttributeSource)
238 }
239 }
240 }
241 return Content.Empty to { _ -> }
242 }
243
244 fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, KDocTag::class.java)
245 ?: arrayOf()
246
247 private fun MutableContent.addTagToSeeAlso(descriptor: DeclarationDescriptor, seeTag: KDocTag) {
248 addTagToSection(seeTag, descriptor, "See Also")
249 }
250
251 private fun MutableContent.addTagToSection(seeTag: KDocTag, descriptor: DeclarationDescriptor, sectionName: String) {
252 val subjectName = seeTag.getSubjectName()
253 if (subjectName != null) {
254 val section = findSectionByTag(sectionName) ?: addSection(sectionName, null)
255 val link = linkResolver.resolveContentLink(descriptor, subjectName)
256 link.append(ContentText(subjectName))
257 val para = ContentParagraph()
258 para.append(link)
259 section.append(para)
260 }
261 }
262
263 private fun KDocTag.getAttr(descriptor: DeclarationDescriptor): DocumentationNode? {
264 var attribute: DocumentationNode? = null
265 val matcher = TEXT.matcher(getContent())
266 if (matcher.matches()) {
267 val command = matcher.group(1)
268 val more = matcher.group(2)
269 attribute = when (command) {
270 REF_COMMAND -> {
271 val attrRef = more.trim()
272 val qualified = attrRef.split('.', '#')
273 val targetDescriptor = resolveKDocLink(resolutionFacade.resolveSession.bindingContext, resolutionFacade, descriptor, this, qualified)
274 DocumentationNode(attrRef, Content.Empty, NodeKind.Attribute).also {
275 if (targetDescriptor.isNotEmpty()) {
276 refGraph.link(it, targetDescriptor.first().signature(), RefKind.Detail)
277 }
278 }
279 }
280 NAME_COMMAND -> {
281 val nameMatcher = NAME_TEXT.matcher(more)
282 if (nameMatcher.matches()) {
283 val attrName = nameMatcher.group(1)
284 DocumentationNode(attrName, Content.Empty, NodeKind.Attribute)
285 } else {
286 null
287 }
288 }
289 DESCRIPTION_COMMAND -> {
290 val attrDescription = more
291 DocumentationNode(attrDescription, Content.Empty, NodeKind.Attribute)
292 }
293 else -> null
294 }
295 }
296 return attribute
297 }
298
299 }
300
301 /**
302 * Lazily executed wrapper node holding a [NodeKind.Parameter] node that will be used to add type
303 * and default value information to
304 * [org.jetbrains.dokka.Formats.DevsiteLayoutHtmlFormatOutputBuilder].
305 *
306 * We make this a [ContentBlock] instead of a [ContentNode] so we won't fallback to calling
307 * [toString] on this and trying to add it to documentation somewhere - returning an empty list
308 * should make this a no-op.
309 *
310 * @property wrappedNode lazily executable lambda that will return the matching documentation node
311 * for this parameter (if it exists)
312 */
313 class ParameterInfoNode(private val wrappedNode: () -> DocumentationNode?) : ContentBlock() {
314 private var computed = false
315
316 val parameterContent: NodeRenderContent?
317 get() = lazyNode
318
319 private var lazyNode: NodeRenderContent? = null
320 get() {
321 if (!computed) {
322 computed = true
323
324 val node = wrappedNode()
325 if (node != null) {
326 field = NodeRenderContent(node, LanguageService.RenderMode.SUMMARY)
327 }
328 }
329 return field
330 }
331
332 override val children = arrayListOf<ContentNode>()
333 }
334