• 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() == "sdkextsince") {
146                 val sdkExtSince = DocumentationNode(it.getContent(), Content.Empty, NodeKind.SdkExtSince)
147                 append(sdkExtSince, RefKind.Detail)
148             } else if (name?.toLowerCase() == "deprecatedsince") {
149                 val deprecatedLevel = DocumentationNode(it.getContent(), Content.Empty, NodeKind.DeprecatedLevel)
150                 append(deprecatedLevel, RefKind.Detail)
151             } else if (name?.toLowerCase() == "artifactid") {
152                 val artifactId = DocumentationNode(it.getContent(), Content.Empty, NodeKind.ArtifactId)
153                 append(artifactId, RefKind.Detail)
154             }
155         }
156     }
157 
158     private fun DeclarationDescriptor.isSuppressWarning(): Boolean {
159         val suppressAnnotation = annotations.findAnnotation(FqName(Suppress::class.qualifiedName!!))
160         return if (suppressAnnotation != null) {
161             @Suppress("UNCHECKED_CAST")
162             (suppressAnnotation.argumentValue("names")?.value as List<StringValue>).any { it.value == "NOT_DOCUMENTED" }
163         } else containingDeclaration?.isSuppressWarning() ?: false
164     }
165 
166     /**
167      * Special case for generating stdlib documentation (the Any class to which the override chain will resolve
168      * is not the same one as the Any class included in the source scope).
169      */
170     fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? {
171         if (descriptor !is CallableMemberDescriptor) {
172             return null
173         }
174         val name = descriptor.name.asString()
175         if (name == "equals" || name == "hashCode" || name == "toString") {
176             var deepestDescriptor: CallableMemberDescriptor = descriptor
177             while (!deepestDescriptor.overriddenDescriptors.isEmpty()) {
178                 deepestDescriptor = deepestDescriptor.overriddenDescriptors.first()
179             }
180             if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") {
181                 val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassifierDescriptors(
182                         FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.FROM_IDE)
183                 anyClassDescriptors.forEach {
184                     val anyMethod = (it as ClassDescriptor).getMemberScope(listOf())
185                             .getDescriptorsFiltered(DescriptorKindFilter.FUNCTIONS, { it == descriptor.name })
186                             .single()
187                     val kdoc = anyMethod.findKDoc()
188                     if (kdoc != null) {
189                         return kdoc
190                     }
191                 }
192             }
193         }
194         return null
195     }
196 
197     fun parseJavadoc(descriptor: DeclarationDescriptor): Pair<Content, (DocumentationNode) -> Unit> {
198         val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi
199         if (psi is PsiDocCommentOwner) {
200             val parseResult = JavadocParser(
201                     refGraph,
202                     logger,
203                     signatureProvider,
204                     externalDocumentationLinkResolver
205             ).parseDocumentation(psi as PsiNamedElement)
206             return parseResult.content to { node ->
207                 parseResult.deprecatedContent?.let {
208                     val deprecationNode = DocumentationNode("", it, NodeKind.Modifier)
209                     node.append(deprecationNode, RefKind.Deprecation)
210                 }
211                 if (node.kind in NodeKind.classLike) {
212                     parseResult.attributeRefs.forEach {
213                         val signature = node.detailOrNull(NodeKind.Signature)
214                         val signatureName = signature?.name
215                         val classAttrSignature = "${signatureName}:$it"
216                         refGraph.register(classAttrSignature, DocumentationNode(node.name, Content.Empty, NodeKind.Attribute))
217                         refGraph.link(node, classAttrSignature, RefKind.Detail)
218                         refGraph.link(classAttrSignature, node, RefKind.Owner)
219                         refGraph.link(classAttrSignature, it, RefKind.AttributeRef)
220                     }
221                 } else if (node.kind in NodeKind.memberLike) {
222                     parseResult.attributeRefs.forEach {
223                         refGraph.link(node, it, RefKind.HiddenLink)
224                     }
225                 }
226                 parseResult.apiLevel?.let {
227                     node.append(it, RefKind.Detail)
228                 }
229                 parseResult.sdkExtSince?.let {
230                     node.append(it, RefKind.Detail)
231                 }
232                 parseResult.deprecatedLevel?.let {
233                     node.append(it, RefKind.Detail)
234                 }
235                 parseResult.artifactId?.let {
236                     node.append(it, RefKind.Detail)
237                 }
238                 parseResult.attribute?.let {
239                     val signature = node.detailOrNull(NodeKind.Signature)
240                     val signatureName = signature?.name
241                     val attrSignature = "AttrMain:$signatureName"
242                     refGraph.register(attrSignature, it)
243                     refGraph.link(attrSignature, node, RefKind.AttributeSource)
244                 }
245             }
246         }
247         return Content.Empty to { _ -> }
248     }
249 
250     fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, KDocTag::class.java)
251             ?: arrayOf()
252 
253     private fun MutableContent.addTagToSeeAlso(descriptor: DeclarationDescriptor, seeTag: KDocTag) {
254         addTagToSection(seeTag, descriptor, "See Also")
255     }
256 
257     private fun MutableContent.addTagToSection(seeTag: KDocTag, descriptor: DeclarationDescriptor, sectionName: String) {
258         val subjectName = seeTag.getSubjectName()
259         if (subjectName != null) {
260             val section = findSectionByTag(sectionName) ?: addSection(sectionName, null)
261             val link = linkResolver.resolveContentLink(descriptor, subjectName)
262             link.append(ContentText(subjectName))
263             val para = ContentParagraph()
264             para.append(link)
265             section.append(para)
266         }
267     }
268 
269     private fun KDocTag.getAttr(descriptor: DeclarationDescriptor): DocumentationNode? {
270         var attribute: DocumentationNode? = null
271         val matcher = TEXT.matcher(getContent())
272         if (matcher.matches()) {
273             val command = matcher.group(1)
274             val more = matcher.group(2)
275             attribute = when (command) {
276                 REF_COMMAND -> {
277                     val attrRef = more.trim()
278                     val qualified = attrRef.split('.', '#')
279                     val targetDescriptor = resolveKDocLink(resolutionFacade.resolveSession.bindingContext, resolutionFacade, descriptor, this, qualified)
280                     DocumentationNode(attrRef, Content.Empty, NodeKind.Attribute).also {
281                         if (targetDescriptor.isNotEmpty()) {
282                             refGraph.link(it, targetDescriptor.first().signature(), RefKind.Detail)
283                         }
284                     }
285                 }
286                 NAME_COMMAND -> {
287                     val nameMatcher = NAME_TEXT.matcher(more)
288                     if (nameMatcher.matches()) {
289                         val attrName = nameMatcher.group(1)
290                         DocumentationNode(attrName, Content.Empty, NodeKind.Attribute)
291                     } else {
292                         null
293                     }
294                 }
295                 DESCRIPTION_COMMAND -> {
296                     val attrDescription = more
297                     DocumentationNode(attrDescription, Content.Empty, NodeKind.Attribute)
298                 }
299                 else -> null
300             }
301         }
302         return attribute
303     }
304 
305 }
306 
307 /**
308  * Lazily executed wrapper node holding a [NodeKind.Parameter] node that will be used to add type
309  * and default value information to
310  * [org.jetbrains.dokka.Formats.DevsiteLayoutHtmlFormatOutputBuilder].
311  *
312  * We make this a [ContentBlock] instead of a [ContentNode] so we won't fallback to calling
313  * [toString] on this and trying to add it to documentation somewhere - returning an empty list
314  * should make this a no-op.
315  *
316  * @property wrappedNode lazily executable lambda that will return the matching documentation node
317  * for this parameter (if it exists)
318  */
319 class ParameterInfoNode(private val wrappedNode: () -> DocumentationNode?) : ContentBlock() {
320     private var computed = false
321 
322     val parameterContent: NodeRenderContent?
323         get() = lazyNode
324 
325     private var lazyNode: NodeRenderContent? = null
326         get() {
327             if (!computed) {
328                 computed = true
329 
330                 val node = wrappedNode()
331                 if (node != null) {
332                     field = NodeRenderContent(node, LanguageService.RenderMode.SUMMARY)
333                 }
334             }
335         return field
336     }
337 
338     override val children = arrayListOf<ContentNode>()
339 }
340