<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