<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