<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