<lambda>null1 package org.jetbrains.dokka
2 
3 import com.google.inject.Inject
4 import com.intellij.openapi.util.text.StringUtil
5 import com.intellij.psi.*
6 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
7 import com.intellij.psi.impl.source.PsiClassReferenceType
8 import com.intellij.psi.util.InheritanceUtil
9 import com.intellij.psi.util.PsiTreeUtil
10 import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration
11 import org.jetbrains.kotlin.asJava.elements.KtLightElement
12 import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
13 import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
14 import org.jetbrains.kotlin.lexer.KtTokens
15 import org.jetbrains.kotlin.psi.KtDeclaration
16 import org.jetbrains.kotlin.psi.KtModifierListOwner
17 import java.io.File
18 
19 fun getSignature(element: PsiElement?) = when(element) {
20     is PsiPackage -> element.qualifiedName
21     is PsiClass -> element.qualifiedName
22     is PsiField -> element.containingClass!!.qualifiedName + "$" + element.name
23     is PsiMethod ->
24         element.containingClass?.qualifiedName + "$" + element.name + "(" +
25                 element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")"
26     else -> null
27 }
28 
typeSignaturenull29 private fun PsiType.typeSignature(): String = when(this) {
30     is PsiArrayType -> "Array((${componentType.typeSignature()}))"
31     is PsiPrimitiveType -> "kotlin." + canonicalText.capitalize()
32     is PsiClassType -> resolve()?.qualifiedName ?: className
33     else -> mapTypeName(this)
34 }
35 
mapTypeNamenull36 private fun mapTypeName(psiType: PsiType): String = when (psiType) {
37     is PsiPrimitiveType -> psiType.canonicalText
38     is PsiClassType -> psiType.resolve()?.name ?: psiType.className
39     is PsiEllipsisType -> mapTypeName(psiType.componentType)
40     is PsiArrayType -> "kotlin.Array"
41     else -> psiType.canonicalText
42 }
43 
44 interface JavaDocumentationBuilder {
appendFilenull45     fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>)
46 }
47 
48 class JavaPsiDocumentationBuilder : JavaDocumentationBuilder {
49     private val options: DocumentationOptions
50     private val refGraph: NodeReferenceGraph
51     private val docParser: JavaDocumentationParser
52     private val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
53 
54     @Inject constructor(
55             options: DocumentationOptions,
56             refGraph: NodeReferenceGraph,
57             logger: DokkaLogger,
58             signatureProvider: ElementSignatureProvider,
59             externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
60     ) {
61         this.options = options
62         this.refGraph = refGraph
63         this.docParser = JavadocParser(refGraph, logger, signatureProvider, externalDocumentationLinkResolver)
64         this.externalDocumentationLinkResolver = externalDocumentationLinkResolver
65     }
66 
67     constructor(
68         options: DocumentationOptions,
69         refGraph: NodeReferenceGraph,
70         docParser: JavaDocumentationParser,
71         externalDocumentationLinkResolver: ExternalDocumentationLinkResolver
72     ) {
73         this.options = options
74         this.refGraph = refGraph
75         this.docParser = docParser
76         this.externalDocumentationLinkResolver = externalDocumentationLinkResolver
77     }
78 
79     override fun appendFile(file: PsiJavaFile, module: DocumentationModule, packageContent: Map<String, Content>) {
80         if (skipFile(file) || file.classes.all { skipElement(it) }) {
81             return
82         }
83         val packageNode = findOrCreatePackageNode(module, file.packageName, emptyMap(), refGraph)
84         appendClasses(packageNode, file.classes)
85     }
86 
87     fun appendClasses(packageNode: DocumentationNode, classes: Array<PsiClass>) {
88         packageNode.appendChildren(classes) { build() }
89     }
90 
91     fun register(element: PsiElement, node: DocumentationNode) {
92         val signature = getSignature(element)
93         if (signature != null) {
94             refGraph.register(signature, node)
95         }
96     }
97 
98     fun link(node: DocumentationNode, element: PsiElement?) {
99         val qualifiedName = getSignature(element)
100         if (qualifiedName != null) {
101             refGraph.link(node, qualifiedName, RefKind.Link)
102         }
103     }
104 
105     fun link(element: PsiElement?, node: DocumentationNode, kind: RefKind) {
106         val qualifiedName = getSignature(element)
107         if (qualifiedName != null) {
108             refGraph.link(qualifiedName, node, kind)
109         }
110     }
111 
112     fun nodeForElement(element: PsiNamedElement,
113                        kind: NodeKind,
114                        name: String = element.name ?: "<anonymous>"): DocumentationNode {
115         val (docComment, deprecatedContent, attrs, apiLevel, deprecatedLevel, artifactId, attribute) = docParser.parseDocumentation(element)
116         val node = DocumentationNode(name, docComment, kind)
117         if (element is PsiModifierListOwner) {
118             node.appendModifiers(element)
119             val modifierList = element.modifierList
120             if (modifierList != null) {
121                 modifierList.annotations.filter { !ignoreAnnotation(it) }.forEach {
122                     val annotation = it.build()
123                     if (it.qualifiedName == "java.lang.Deprecated" || it.qualifiedName == "kotlin.Deprecated") {
124                         node.append(annotation, RefKind.Deprecation)
125                         annotation.convertDeprecationDetailsToChildren()
126                     } else {
127                         node.append(annotation, RefKind.Annotation)
128                     }
129                 }
130             }
131         }
132         if (deprecatedContent != null) {
133             val deprecationNode = DocumentationNode("", deprecatedContent, NodeKind.Modifier)
134             node.append(deprecationNode, RefKind.Deprecation)
135         }
136         if (element is PsiDocCommentOwner && element.isDeprecated && node.deprecation == null) {
137             val deprecationNode = DocumentationNode("", Content.of(ContentText("Deprecated")), NodeKind.Modifier)
138             node.append(deprecationNode, RefKind.Deprecation)
139         }
140         apiLevel?.let {
141             node.append(it, RefKind.Detail)
142         }
143         deprecatedLevel?.let {
144             node.append(it, RefKind.Detail)
145         }
146         artifactId?.let {
147             node.append(it, RefKind.Detail)
148         }
149         attrs.forEach {
150             refGraph.link(node, it, RefKind.Detail)
151             refGraph.link(it, node, RefKind.Owner)
152         }
153         attribute?.let {
154             val attrName = node.qualifiedName()
155             refGraph.register("Attr:$attrName", attribute)
156         }
157         return node
158     }
159 
160     fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) {
161         "java.lang.SuppressWarnings" -> true
162         else -> false
163     }
164 
165     fun <T : Any> DocumentationNode.appendChildren(elements: Array<T>,
166                                                    kind: RefKind = RefKind.Member,
167                                                    buildFn: T.() -> DocumentationNode) {
168         elements.forEach {
169             if (!skipElement(it)) {
170                 append(it.buildFn(), kind)
171             }
172         }
173     }
174 
175     private fun skipFile(javaFile: PsiJavaFile): Boolean = options.effectivePackageOptions(javaFile.packageName).suppress
176 
177     private fun skipElement(element: Any) =
178             skipElementByVisibility(element) ||
179                     hasSuppressDocTag(element) ||
180                     hasHideAnnotation(element) ||
181                     skipElementBySuppressedFiles(element)
182 
183     private fun skipElementByVisibility(element: Any): Boolean =
184         element is PsiModifierListOwner &&
185                 element !is PsiParameter &&
186                 !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) &&
187                 (element.hasModifierProperty(PsiModifier.PRIVATE) ||
188                         element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
189                         element.isInternal())
190 
191     private fun skipElementBySuppressedFiles(element: Any): Boolean =
192             element is PsiElement && element.containingFile.virtualFile != null && File(element.containingFile.virtualFile.path).absoluteFile in options.suppressedFiles
193 
194     private fun PsiElement.isInternal(): Boolean {
195         val ktElement = (this as? KtLightElement<*, *>)?.kotlinOrigin ?: return false
196         return (ktElement as? KtModifierListOwner)?.hasModifier(KtTokens.INTERNAL_KEYWORD) ?: false
197     }
198 
199     fun <T : Any> DocumentationNode.appendMembers(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
200             appendChildren(elements, RefKind.Member, buildFn)
201 
202     fun <T : Any> DocumentationNode.appendDetails(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
203             appendChildren(elements, RefKind.Detail, buildFn)
204 
205     fun PsiClass.build(): DocumentationNode {
206         val kind = when {
207             isInterface -> NodeKind.Interface
208             isEnum -> NodeKind.Enum
209             isAnnotationType -> NodeKind.AnnotationClass
210             isException() -> NodeKind.Exception
211             else -> NodeKind.Class
212         }
213         val node = nodeForElement(this, kind)
214         superTypes.filter { !ignoreSupertype(it) }.forEach { superType ->
215             node.appendType(superType, NodeKind.Supertype)
216             val superClass = superType.resolve()
217             if (superClass != null) {
218                 link(superClass, node, RefKind.Inheritor)
219             }
220         }
221 
222         var methodsAndConstructors = methods
223 
224         if (constructors.isEmpty()) {
225             // Having no constructor represents a class that only has an implicit/default constructor
226             // so we create one synthetically for documentation
227             val factory = JavaPsiFacade.getElementFactory(this.project)
228             methodsAndConstructors += factory.createMethodFromText("public $name() {}", this)
229         }
230         node.appendDetails(typeParameters) { build() }
231         node.appendMembers(methodsAndConstructors) { build() }
232         node.appendMembers(fields) { build() }
233         node.appendMembers(innerClasses) { build() }
234         register(this, node)
235         return node
236     }
237 
238     fun PsiClass.isException() = InheritanceUtil.isInheritor(this, "java.lang.Throwable")
239 
240     fun ignoreSupertype(psiType: PsiClassType): Boolean = false
241 //            psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object")
242 
243     fun PsiClassType.isClass(qName: String): Boolean {
244         val shortName = qName.substringAfterLast('.')
245         if (className == shortName) {
246             val psiClass = resolve()
247             return psiClass?.qualifiedName == qName
248         }
249         return false
250     }
251 
252     fun PsiField.build(): DocumentationNode {
253         val node = nodeForElement(this, nodeKind())
254         node.appendType(type)
255 
256         node.appendConstantValueIfAny(this)
257         register(this, node)
258         return node
259     }
260 
261     private fun DocumentationNode.appendConstantValueIfAny(field: PsiField) {
262         val modifierList = field.modifierList ?: return
263         val initializer = field.initializer ?: return
264         if (modifierList.hasExplicitModifier(PsiModifier.FINAL) &&
265             modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
266             val value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false) ?: return
267             val text = when(value) {
268                 is String -> "\"${StringUtil.escapeStringCharacters(value)}\""
269                 else -> value.toString()
270             }
271             append(DocumentationNode(text, Content.Empty, NodeKind.Value), RefKind.Detail)
272         }
273     }
274 
275     private fun PsiField.nodeKind(): NodeKind = when {
276         this is PsiEnumConstant -> NodeKind.EnumItem
277         else -> NodeKind.Field
278     }
279 
280     fun PsiMethod.build(): DocumentationNode {
281         val node = nodeForElement(this, nodeKind(), name)
282 
283         if (!isConstructor) {
284             node.appendType(returnType)
285         }
286         node.appendDetails(parameterList.parameters) { build() }
287         node.appendDetails(typeParameters) { build() }
288         register(this, node)
289         return node
290     }
291 
292     private fun PsiMethod.nodeKind(): NodeKind = when {
293         isConstructor -> NodeKind.Constructor
294         else -> NodeKind.Function
295     }
296 
297     fun PsiParameter.build(): DocumentationNode {
298         val node = nodeForElement(this, NodeKind.Parameter)
299         node.appendType(type)
300         if (type is PsiEllipsisType) {
301             node.appendTextNode("vararg", NodeKind.Modifier, RefKind.Detail)
302         }
303         return node
304     }
305 
306     fun PsiTypeParameter.build(): DocumentationNode {
307         val node = nodeForElement(this, NodeKind.TypeParameter)
308         extendsListTypes.forEach { node.appendType(it, NodeKind.UpperBound) }
309         implementsListTypes.forEach { node.appendType(it, NodeKind.UpperBound) }
310         return node
311     }
312 
313     fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) {
314         val modifierList = element.modifierList ?: return
315 
316         PsiModifier.MODIFIERS.forEach {
317             if (modifierList.hasExplicitModifier(it)) {
318                 appendTextNode(it, NodeKind.Modifier)
319             }
320         }
321     }
322 
323     fun DocumentationNode.appendType(psiType: PsiType?, kind: NodeKind = NodeKind.Type) {
324         if (psiType == null) {
325             return
326         }
327 
328         val node = psiType.build(kind)
329         append(node, RefKind.Detail)
330 
331         // Attempt to create an external link if the psiType is one
332         if (psiType is PsiClassReferenceType) {
333             val target = psiType.reference.resolve()
334             if (target != null) {
335                 val externalLink = externalDocumentationLinkResolver.buildExternalDocumentationLink(target)
336                 if (externalLink != null) {
337                     node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
338                 }
339             }
340         }
341     }
342 
343     fun PsiType.build(kind: NodeKind = NodeKind.Type): DocumentationNode {
344         val name = mapTypeName(this)
345         val node = DocumentationNode(name, Content.Empty, kind)
346         if (this is PsiClassType) {
347             node.appendDetails(parameters) { build(NodeKind.Type) }
348             link(node, resolve())
349         }
350         if (this is PsiArrayType && this !is PsiEllipsisType) {
351             node.append(componentType.build(NodeKind.Type), RefKind.Detail)
352         }
353         return node
354     }
355 
356     fun PsiAnnotation.build(): DocumentationNode {
357         val node = DocumentationNode(nameReferenceElement?.text ?: "<?>", Content.Empty, NodeKind.Annotation)
358         parameterList.attributes.forEach {
359             val parameter = DocumentationNode(it.name ?: "value", Content.Empty, NodeKind.Parameter)
360             val value = it.value
361             if (value != null) {
362                 val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text
363                 val valueNode = DocumentationNode(valueText, Content.Empty, NodeKind.Value)
364                 parameter.append(valueNode, RefKind.Detail)
365             }
366             node.append(parameter, RefKind.Detail)
367         }
368         return node
369     }
370 }
371 
hasSuppressDocTagnull372 fun hasSuppressDocTag(element: Any?): Boolean {
373     val declaration = (element as? KtLightDeclaration<*, *>)?.kotlinOrigin as? KtDeclaration ?: return false
374     return PsiTreeUtil.findChildrenOfType(declaration.docComment, KDocTag::class.java).any { it.knownTag == KDocKnownTag.SUPPRESS }
375 }
376 
377 /**
378  * Determines if the @hide annotation is present in a Javadoc comment.
379  *
380  * @param element a doc element to analyze for the presence of @hide
381  *
382  * @return true if @hide is present, otherwise false
383  *
384  * Note: this does not process @hide annotations in KDoc.  For KDoc, use the @suppress tag instead, which is processed
385  * by [hasSuppressDocTag].
386  */
hasHideAnnotationnull387 fun hasHideAnnotation(element: Any?): Boolean {
388     return element is PsiDocCommentOwner && element.docComment?.run { findTagByName("hide") != null } ?: false
389 }
390