• 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     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, sdkExtSince, 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         sdkExtSince?.let {
144             node.append(it, RefKind.Detail)
145         }
146         deprecatedLevel?.let {
147             node.append(it, RefKind.Detail)
148         }
149         artifactId?.let {
150             node.append(it, RefKind.Detail)
151         }
152         attrs.forEach {
153             refGraph.link(node, it, RefKind.Detail)
154             refGraph.link(it, node, RefKind.Owner)
155         }
156         attribute?.let {
157             val attrName = node.qualifiedName()
158             refGraph.register("Attr:$attrName", attribute)
159         }
160         return node
161     }
162 
163     fun ignoreAnnotation(annotation: PsiAnnotation) = when(annotation.qualifiedName) {
164         "java.lang.SuppressWarnings" -> true
165         else -> false
166     }
167 
168     fun <T : Any> DocumentationNode.appendChildren(elements: Array<T>,
169                                                    kind: RefKind = RefKind.Member,
170                                                    buildFn: T.() -> DocumentationNode) {
171         elements.forEach {
172             if (!skipElement(it)) {
173                 append(it.buildFn(), kind)
174             }
175         }
176     }
177 
178     private fun skipFile(javaFile: PsiJavaFile): Boolean = options.effectivePackageOptions(javaFile.packageName).suppress
179 
180     private fun skipElement(element: Any) =
181             skipElementByVisibility(element) ||
182                     hasSuppressDocTag(element) ||
183                     hasHideAnnotation(element) ||
184                     skipElementBySuppressedFiles(element)
185 
186     private fun skipElementByVisibility(element: Any): Boolean =
187         element is PsiModifierListOwner &&
188                 element !is PsiParameter &&
189                 !(options.effectivePackageOptions((element.containingFile as? PsiJavaFile)?.packageName ?: "").includeNonPublic) &&
190                 (element.hasModifierProperty(PsiModifier.PRIVATE) ||
191                         element.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) ||
192                         element.isInternal())
193 
194     private fun skipElementBySuppressedFiles(element: Any): Boolean =
195             element is PsiElement && element.containingFile.virtualFile != null && File(element.containingFile.virtualFile.path).absoluteFile in options.suppressedFiles
196 
197     private fun PsiElement.isInternal(): Boolean {
198         val ktElement = (this as? KtLightElement<*, *>)?.kotlinOrigin ?: return false
199         return (ktElement as? KtModifierListOwner)?.hasModifier(KtTokens.INTERNAL_KEYWORD) ?: false
200     }
201 
202     fun <T : Any> DocumentationNode.appendMembers(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
203             appendChildren(elements, RefKind.Member, buildFn)
204 
205     fun <T : Any> DocumentationNode.appendDetails(elements: Array<T>, buildFn: T.() -> DocumentationNode) =
206             appendChildren(elements, RefKind.Detail, buildFn)
207 
208     fun PsiClass.build(): DocumentationNode {
209         val kind = when {
210             isInterface -> NodeKind.Interface
211             isEnum -> NodeKind.Enum
212             isAnnotationType -> NodeKind.AnnotationClass
213             isException() -> NodeKind.Exception
214             else -> NodeKind.Class
215         }
216         val node = nodeForElement(this, kind)
217         superTypes.filter { !ignoreSupertype(it) }.forEach { superType ->
218             node.appendType(superType, NodeKind.Supertype)
219             val superClass = superType.resolve()
220             if (superClass != null) {
221                 link(superClass, node, RefKind.Inheritor)
222             }
223         }
224 
225         var methodsAndConstructors = methods
226 
227         if (constructors.isEmpty()) {
228             // Having no constructor represents a class that only has an implicit/default constructor
229             // so we create one synthetically for documentation
230             val factory = JavaPsiFacade.getElementFactory(this.project)
231             methodsAndConstructors += factory.createMethodFromText("public $name() {}", this)
232         }
233         node.appendDetails(typeParameters) { build() }
234         node.appendMembers(methodsAndConstructors) { build() }
235         node.appendMembers(fields) { build() }
236         node.appendMembers(innerClasses) { build() }
237         register(this, node)
238         return node
239     }
240 
241     fun PsiClass.isException() = InheritanceUtil.isInheritor(this, "java.lang.Throwable")
242 
243     fun ignoreSupertype(psiType: PsiClassType): Boolean = false
244 //            psiType.isClass("java.lang.Enum") || psiType.isClass("java.lang.Object")
245 
246     fun PsiClassType.isClass(qName: String): Boolean {
247         val shortName = qName.substringAfterLast('.')
248         if (className == shortName) {
249             val psiClass = resolve()
250             return psiClass?.qualifiedName == qName
251         }
252         return false
253     }
254 
255     fun PsiField.build(): DocumentationNode {
256         val node = nodeForElement(this, nodeKind())
257         node.appendType(type)
258 
259         node.appendConstantValueIfAny(this)
260         register(this, node)
261         return node
262     }
263 
264     private fun DocumentationNode.appendConstantValueIfAny(field: PsiField) {
265         val modifierList = field.modifierList ?: return
266         val initializer = field.initializer ?: return
267         if (modifierList.hasExplicitModifier(PsiModifier.FINAL) &&
268             modifierList.hasExplicitModifier(PsiModifier.STATIC)) {
269             val value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false) ?: return
270             val text = when(value) {
271                 is String -> "\"${StringUtil.escapeStringCharacters(value)}\""
272                 else -> value.toString()
273             }
274             append(DocumentationNode(text, Content.Empty, NodeKind.Value), RefKind.Detail)
275         }
276     }
277 
278     private fun PsiField.nodeKind(): NodeKind = when {
279         this is PsiEnumConstant -> NodeKind.EnumItem
280         else -> NodeKind.Field
281     }
282 
283     fun PsiMethod.build(): DocumentationNode {
284         val node = nodeForElement(this, nodeKind(), name)
285 
286         if (!isConstructor) {
287             node.appendType(returnType)
288         }
289         node.appendDetails(parameterList.parameters) { build() }
290         node.appendDetails(typeParameters) { build() }
291         register(this, node)
292         return node
293     }
294 
295     private fun PsiMethod.nodeKind(): NodeKind = when {
296         isConstructor -> NodeKind.Constructor
297         else -> NodeKind.Function
298     }
299 
300     fun PsiParameter.build(): DocumentationNode {
301         val node = nodeForElement(this, NodeKind.Parameter)
302         node.appendType(type)
303         if (type is PsiEllipsisType) {
304             node.appendTextNode("vararg", NodeKind.Modifier, RefKind.Detail)
305         }
306         return node
307     }
308 
309     fun PsiTypeParameter.build(): DocumentationNode {
310         val node = nodeForElement(this, NodeKind.TypeParameter)
311         extendsListTypes.forEach { node.appendType(it, NodeKind.UpperBound) }
312         implementsListTypes.forEach { node.appendType(it, NodeKind.UpperBound) }
313         return node
314     }
315 
316     fun DocumentationNode.appendModifiers(element: PsiModifierListOwner) {
317         val modifierList = element.modifierList ?: return
318 
319         PsiModifier.MODIFIERS.forEach {
320             if (modifierList.hasExplicitModifier(it)) {
321                 appendTextNode(it, NodeKind.Modifier)
322             }
323         }
324     }
325 
326     fun DocumentationNode.appendType(psiType: PsiType?, kind: NodeKind = NodeKind.Type) {
327         if (psiType == null) {
328             return
329         }
330 
331         val node = psiType.build(kind)
332         append(node, RefKind.Detail)
333 
334         // Attempt to create an external link if the psiType is one
335         if (psiType is PsiClassReferenceType) {
336             val target = psiType.reference.resolve()
337             if (target != null) {
338                 val externalLink = externalDocumentationLinkResolver.buildExternalDocumentationLink(target)
339                 if (externalLink != null) {
340                     node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
341                 }
342             }
343         }
344     }
345 
346     fun PsiType.build(kind: NodeKind = NodeKind.Type): DocumentationNode {
347         val name = mapTypeName(this)
348         val node = DocumentationNode(name, Content.Empty, kind)
349         if (this is PsiClassType) {
350             node.appendDetails(parameters) { build(NodeKind.Type) }
351             link(node, resolve())
352         }
353         if (this is PsiArrayType && this !is PsiEllipsisType) {
354             node.append(componentType.build(NodeKind.Type), RefKind.Detail)
355         }
356         return node
357     }
358 
359     fun PsiAnnotation.build(): DocumentationNode {
360         val node = DocumentationNode(nameReferenceElement?.text ?: "<?>", Content.Empty, NodeKind.Annotation)
361         parameterList.attributes.forEach {
362             val parameter = DocumentationNode(it.name ?: "value", Content.Empty, NodeKind.Parameter)
363             val value = it.value
364             if (value != null) {
365                 val valueText = (value as? PsiLiteralExpression)?.value as? String ?: value.text
366                 val valueNode = DocumentationNode(valueText, Content.Empty, NodeKind.Value)
367                 parameter.append(valueNode, RefKind.Detail)
368             }
369             node.append(parameter, RefKind.Detail)
370         }
371         return node
372     }
373 }
374 
hasSuppressDocTagnull375 fun hasSuppressDocTag(element: Any?): Boolean {
376     val declaration = (element as? KtLightDeclaration<*, *>)?.kotlinOrigin as? KtDeclaration ?: return false
377     return PsiTreeUtil.findChildrenOfType(declaration.docComment, KDocTag::class.java).any { it.knownTag == KDocKnownTag.SUPPRESS }
378 }
379 
380 /**
381  * Determines if the @hide annotation is present in a Javadoc comment.
382  *
383  * @param element a doc element to analyze for the presence of @hide
384  *
385  * @return true if @hide is present, otherwise false
386  *
387  * Note: this does not process @hide annotations in KDoc.  For KDoc, use the @suppress tag instead, which is processed
388  * by [hasSuppressDocTag].
389  */
hasHideAnnotationnull390 fun hasHideAnnotation(element: Any?): Boolean {
391     return element is PsiDocCommentOwner && element.docComment?.run { findTagByName("hide") != null } ?: false
392 }
393