• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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