<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