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