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