blob: d8162c44c3f99750a6b0aa2cc9f727134dcf778a [file] [log] [blame]
Ilya Ryzhenkov11355ce2014-10-12 22:35:47 +04001package org.jetbrains.dokka
2
3import org.jetbrains.jet.lang.descriptors.*
4import org.jetbrains.dokka.DocumentationNode.Kind
5import org.jetbrains.jet.lang.types.TypeProjection
6import org.jetbrains.jet.lang.types.Variance
7import org.jetbrains.jet.lang.types.JetType
8import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
9import org.jetbrains.jet.lang.resolve.BindingContext
10import org.jetbrains.jet.lang.resolve.name.Name
11import org.jetbrains.jet.lang.resolve.scopes.JetScope
12import org.jetbrains.jet.lang.psi.JetFile
13import org.jetbrains.jet.lang.resolve.name.FqName
14
Ilya Ryzhenkov471039e2014-10-12 22:37:07 +040015public data class DocumentationOptions(val includeNonPublic: Boolean = false)
16
Ilya Ryzhenkov11355ce2014-10-12 22:35:47 +040017class DocumentationBuilder(val context: BindingContext, val options: DocumentationOptions) {
18 val descriptorToNode = hashMapOf<DeclarationDescriptor, DocumentationNode>()
19 val nodeToDescriptor = hashMapOf<DocumentationNode, DeclarationDescriptor>()
20 val links = hashMapOf<DocumentationNode, DeclarationDescriptor>()
21 val packages = hashMapOf<FqName, DocumentationNode>()
22
23 fun parseDocumentation(descriptor: DeclarationDescriptor): Content {
24 val docText = context.getDocumentationElements(descriptor).map { it.extractText() }.join("\n")
25 val tree = MarkdownProcessor.parse(docText)
26 //println(tree.toTestString())
27 val content = tree.toContent()
28 return content
29 }
30
31 fun link(node: DocumentationNode, descriptor: DeclarationDescriptor) {
32 links.put(node, descriptor)
33 }
34
35 fun register(descriptor: DeclarationDescriptor, node: DocumentationNode) {
36 descriptorToNode.put(descriptor, node)
37 nodeToDescriptor.put(node, descriptor)
38 }
39
40 fun DocumentationNode<T>(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named {
41 val doc = parseDocumentation(descriptor)
42 val node = DocumentationNode(descriptor.getName().asString(), doc, kind)
43 if (descriptor is MemberDescriptor) {
44 if (descriptor !is ConstructorDescriptor) {
45 node.appendModality(descriptor)
46 }
47 node.appendVisibility(descriptor)
48 }
49 return node
50 }
51
52 fun DocumentationNode.append(child: DocumentationNode, kind: DocumentationReference.Kind) {
53 addReferenceTo(child, kind)
54 when (kind) {
55 DocumentationReference.Kind.Detail -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
56 DocumentationReference.Kind.Member -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
57 DocumentationReference.Kind.Owner -> child.addReferenceTo(this, DocumentationReference.Kind.Member)
58 }
59 }
60
61 fun DocumentationNode.appendModality(descriptor: MemberDescriptor) {
62 val modifier = descriptor.getModality().name().toLowerCase()
63 val node = DocumentationNode(modifier, Content.Empty, DocumentationNode.Kind.Modifier)
64 append(node, DocumentationReference.Kind.Detail)
65 }
66
67 fun DocumentationNode.appendVisibility(descriptor: DeclarationDescriptorWithVisibility) {
68 val modifier = descriptor.getVisibility().toString()
69 val node = DocumentationNode(modifier, Content.Empty, DocumentationNode.Kind.Modifier)
70 append(node, DocumentationReference.Kind.Detail)
71 }
72
73 fun DocumentationNode.appendSupertypes(descriptor: ClassDescriptor) {
74 val superTypes = descriptor.getTypeConstructor().getSupertypes()
75 for (superType in superTypes) {
76 if (superType.toString() != "Any")
77 appendType(superType, DocumentationNode.Kind.Supertype)
78 }
79 }
80
81 fun DocumentationNode.appendProjection(projection: TypeProjection, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) {
82 val prefix = when (projection.getProjectionKind()) {
83 Variance.IN_VARIANCE -> "in "
84 Variance.OUT_VARIANCE -> "out "
85 else -> ""
86 }
87 appendType(projection.getType(), kind, prefix)
88 }
89
90 fun DocumentationNode.appendType(jetType: JetType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type, prefix: String = "") {
91 if (jetType == null)
92 return
93 val classifierDescriptor = jetType.getConstructor().getDeclarationDescriptor()
94 val name = when (classifierDescriptor) {
95 is Named -> prefix + classifierDescriptor.getName().asString() + if (jetType.isNullable()) "?" else ""
96 else -> "<anonymous>"
97 }
98 val node = DocumentationNode(name, Content.Empty, kind)
99 if (classifierDescriptor != null)
100 link(node, classifierDescriptor)
101
102 append(node, DocumentationReference.Kind.Detail)
103 for (typeArgument in jetType.getArguments())
104 node.appendProjection(typeArgument)
105 }
106
107 val visibleToDocumentation = setOf(Visibilities.INTERNAL, Visibilities.PROTECTED, Visibilities.PUBLIC)
108
109 fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) {
110 // do not include generated code
111 if (descriptor is CallableMemberDescriptor && descriptor.getKind() != CallableMemberDescriptor.Kind.DECLARATION)
112 return
113
114 if (options.includeNonPublic
115 || descriptor !is MemberDescriptor
116 || descriptor.getVisibility() in visibleToDocumentation) {
117 append(descriptor.build(), kind)
118 }
119 }
120
121 fun DocumentationNode.appendChildren(descriptors: Iterable<DeclarationDescriptor>, kind: DocumentationReference.Kind) {
122 descriptors.forEach { descriptor -> appendChild(descriptor, kind) }
123 }
124
125 fun DocumentationNode.appendFiles(sourceFiles : List<JetFile>) {
126 for (sourceFile in sourceFiles) {
127 val fragment = context.getPackageFragment(sourceFile)!!
128 val packageNode = packages.getOrPut(fragment.fqName) {
129 val packageNode = DocumentationNode(fragment.fqName.asString(), Content.Empty, Kind.Package)
130 append(packageNode, DocumentationReference.Kind.Member)
131 packageNode
132 }
133 packageNode.appendChildren(fragment.getMemberScope().getAllDescriptors(), DocumentationReference.Kind.Member)
134 }
135 }
136
137 fun DeclarationDescriptor.build(): DocumentationNode = when (this) {
138 is ClassDescriptor -> build()
139 is ConstructorDescriptor -> build()
140 is ScriptDescriptor -> build()
141 is FunctionDescriptor -> build()
142 is PropertyDescriptor -> build()
143 is PropertyGetterDescriptor -> build()
144 is PropertySetterDescriptor -> build()
145 is TypeParameterDescriptor -> build()
146 is ValueParameterDescriptor -> build()
147 is ReceiverParameterDescriptor -> build()
148 else -> throw IllegalStateException("Descriptor $this is not known")
149 }
150
151 fun ScriptDescriptor.build(): DocumentationNode = getClassDescriptor().build()
152 fun ClassDescriptor.build(): DocumentationNode {
153 val kind = when (getKind()) {
154 ClassKind.OBJECT -> Kind.Object
155 ClassKind.CLASS_OBJECT -> Kind.Object
156 ClassKind.TRAIT -> Kind.Interface
157 ClassKind.ENUM_CLASS -> Kind.Enum
158 ClassKind.ENUM_ENTRY -> Kind.EnumItem
159 else -> Kind.Class
160 }
161 val node = DocumentationNode(this, kind)
162 node.appendSupertypes(this)
163 if (getKind() != ClassKind.OBJECT) {
164 node.appendChildren(getTypeConstructor().getParameters(), DocumentationReference.Kind.Detail)
165 node.appendChildren(getConstructors(), DocumentationReference.Kind.Member)
166 val classObjectDescriptor = getClassObjectDescriptor()
167 if (classObjectDescriptor != null)
168 node.appendChild(classObjectDescriptor, DocumentationReference.Kind.Member)
169 }
170 node.appendChildren(getDefaultType().getMemberScope().getAllDescriptors(), DocumentationReference.Kind.Member)
171 register(this, node)
172 return node
173 }
174
175
176 fun ConstructorDescriptor.build(): DocumentationNode {
177 val node = DocumentationNode(this, Kind.Constructor)
178 node.appendChildren(getValueParameters(), DocumentationReference.Kind.Detail)
179 register(this, node)
180 return node
181 }
182
183 fun FunctionDescriptor.build(): DocumentationNode {
184 val node = DocumentationNode(this, Kind.Function)
185
186 node.appendChildren(getTypeParameters(), DocumentationReference.Kind.Detail)
187 getExtensionReceiverParameter()?.let { node.appendChild(it, DocumentationReference.Kind.Detail) }
188 node.appendChildren(getValueParameters(), DocumentationReference.Kind.Detail)
189 node.appendType(getReturnType())
190 register(this, node)
191 return node
192
193 }
194
195 fun PropertyDescriptor.build(): DocumentationNode {
196 val node = DocumentationNode(this, Kind.Property)
197 node.appendType(getReturnType())
198 node.appendChildren(getTypeParameters(), DocumentationReference.Kind.Detail)
199 getGetter()?.let {
200 if (!it.isDefault())
201 node.appendChild(it, DocumentationReference.Kind.Member)
202 }
203 getSetter()?.let {
204 if (!it.isDefault())
205 node.appendChild(it, DocumentationReference.Kind.Member)
206 }
207
208 register(this, node)
209 return node
210 }
211
212 fun ValueParameterDescriptor.build(): DocumentationNode {
213 val node = DocumentationNode(this, Kind.Parameter)
214 node.appendType(getType())
215 return node
216 }
217
218 fun TypeParameterDescriptor.build(): DocumentationNode {
219 val doc = parseDocumentation(this)
220 val name = getName().asString()
221 val prefix = when (getVariance()) {
222 Variance.IN_VARIANCE -> "in "
223 Variance.OUT_VARIANCE -> "out "
224 else -> ""
225 }
226
227 val node = DocumentationNode(prefix + name, doc, DocumentationNode.Kind.TypeParameter)
228
229 val builtIns = KotlinBuiltIns.getInstance()
230 for (constraint in getUpperBounds()) {
231 if (constraint == builtIns.getDefaultBound())
232 continue
233 val constraintNode = DocumentationNode(constraint.toString(), Content.Empty, DocumentationNode.Kind.UpperBound)
234 node.append(constraintNode, DocumentationReference.Kind.Detail)
235 }
236
237 for (constraint in getLowerBounds()) {
238 if (builtIns.isNothing(constraint))
239 continue
240 val constraintNode = DocumentationNode(constraint.toString(), Content.Empty, DocumentationNode.Kind.LowerBound)
241 node.append(constraintNode, DocumentationReference.Kind.Detail)
242 }
243 return node
244 }
245
246 fun ReceiverParameterDescriptor.build(): DocumentationNode {
247 val node = DocumentationNode(this, Kind.Receiver)
248 node.appendType(getType())
249 return node
250 }
251
252 /**
253 * Generates cross-references for documentation such as extensions for a type, inheritors, etc
254 *
255 * $receiver: [DocumentationContext] for node/descriptor resolutions
256 * $node: [DocumentationNode] to visit
257 */
258 public fun resolveReferences(node: DocumentationNode) {
259 node.details(DocumentationNode.Kind.Receiver).forEach { detail ->
260 val receiverType = detail.detail(DocumentationNode.Kind.Type)
261 val descriptor = links[receiverType]
262 if (descriptor != null) {
263 val typeNode = descriptorToNode[descriptor]
264 // if typeNode is null, extension is to external type like in a library
265 // should we create dummy node here?
266 typeNode?.addReferenceTo(node, DocumentationReference.Kind.Extension)
267 }
268 }
269 node.details(DocumentationNode.Kind.Supertype).forEach { detail ->
270 val descriptor = links[detail]
271 if (descriptor != null) {
272 val typeNode = descriptorToNode[descriptor]
273 typeNode?.addReferenceTo(node, DocumentationReference.Kind.Inheritor)
274 }
275 }
276 node.details.forEach { detail ->
277 val descriptor = links[detail]
278 if (descriptor != null) {
279 val typeNode = descriptorToNode[descriptor]
280 if (typeNode != null) {
281 detail.addReferenceTo(typeNode, DocumentationReference.Kind.Link)
282 }
283 }
284 }
285
286 resolveContentLinks(node, node.doc)
287
288 for (child in node.members) {
289 resolveReferences(child)
290 }
291 for (child in node.details) {
292 resolveReferences(child)
293 }
294 }
295
296 fun getResolutionScope(node: DocumentationNode): JetScope {
297 val descriptor = nodeToDescriptor[node] ?: throw IllegalArgumentException("Node is not known to this context")
298 return context.getResolutionScope(descriptor)
299 }
300
301 fun resolveContentLinks(node: DocumentationNode, content: ContentNode) {
302 val snapshot = content.children.toList()
303 for (child in snapshot) {
304 if (child is ContentExternalLink) {
305 val referenceText = child.href
306 if (Name.isValidIdentifier(referenceText)) {
307 val scope = getResolutionScope(node)
308 val symbolName = Name.guess(referenceText)
309 val symbol = scope.getLocalVariable(symbolName) ?:
310 scope.getProperties(symbolName).firstOrNull() ?:
311 scope.getFunctions(symbolName).firstOrNull() ?:
312 scope.getClassifier(symbolName)
313
314 if (symbol != null) {
315 val targetNode = descriptorToNode[symbol]
316 val contentLink = if (targetNode != null) ContentNodeLink(targetNode) else ContentExternalLink("#")
317
318 val index = content.children.indexOf(child)
319 content.children.remove(index)
320 contentLink.children.addAll(child.children)
321 content.children.add(index, contentLink)
322 }
323 }
324 }
325 resolveContentLinks(node, child)
326 }
327 }
328}