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

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