diff options
-rw-r--r-- | src/Formats/StructuredFormatService.kt | 11 | ||||
-rw-r--r-- | src/Kotlin/DocumentationBuilder.kt | 37 | ||||
-rw-r--r-- | src/Model/DocumentationNode.kt | 1 | ||||
-rw-r--r-- | test/data/format/extensions.class.md | 16 | ||||
-rw-r--r-- | test/data/format/extensions.kt | 19 | ||||
-rw-r--r-- | test/data/format/extensions.package.md | 18 | ||||
-rw-r--r-- | test/data/functions/functionWithReceiver.kt | 8 | ||||
-rw-r--r-- | test/data/properties/propertyWithReceiver.kt | 2 | ||||
-rw-r--r-- | test/src/format/MarkdownFormatTest.kt | 9 | ||||
-rw-r--r-- | test/src/model/FunctionTest.kt | 38 | ||||
-rw-r--r-- | test/src/model/PropertyTest.kt | 13 |
11 files changed, 153 insertions, 19 deletions
diff --git a/src/Formats/StructuredFormatService.kt b/src/Formats/StructuredFormatService.kt index b75f39d1..cb510f80 100644 --- a/src/Formats/StructuredFormatService.kt +++ b/src/Formats/StructuredFormatService.kt @@ -178,10 +178,15 @@ public abstract class StructuredFormatService(val locationService: LocationServi for ((breadcrumbs, items) in breakdownByLocation) { appendLine(to, breadcrumbs) appendLine(to) - appendLocation(location, to, items) + appendLocation(location, to, items.filter { it.kind != DocumentationNode.Kind.ExternalClass }) } for (node in nodes) { + if (node.kind == DocumentationNode.Kind.ExternalClass) { + appendSection(location, "Extensions for ${node.name}", node.members, node, to) + continue + } + appendSection(location, "Packages", node.members(DocumentationNode.Kind.Package), node, to) appendSection(location, "Types", node.members.filter { it.kind in setOf( @@ -191,6 +196,7 @@ public abstract class StructuredFormatService(val locationService: LocationServi DocumentationNode.Kind.Object, DocumentationNode.Kind.AnnotationClass) }, node, to) + appendSection(location, "Extensions for External Classes", node.members(DocumentationNode.Kind.ExternalClass), node, to) appendSection(location, "Constructors", node.members(DocumentationNode.Kind.Constructor), node, to) appendSection(location, "Properties", node.members(DocumentationNode.Kind.Property), node, to) appendSection(location, "Functions", node.members(DocumentationNode.Kind.Function), node, to) @@ -210,7 +216,8 @@ public abstract class StructuredFormatService(val locationService: LocationServi DocumentationNode.Kind.Function, DocumentationNode.Kind.PropertyAccessor, DocumentationNode.Kind.ClassObjectProperty, - DocumentationNode.Kind.ClassObjectFunction + DocumentationNode.Kind.ClassObjectFunction, + DocumentationNode.Kind.ExternalClass ) }, node, to) appendSection(location, "Extensions", node.extensions, node, to) diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index c00580c5..99c81760 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -15,6 +15,12 @@ import org.jetbrains.jet.lang.descriptors.impl.EnumEntrySyntheticClassDescriptor public data class DocumentationOptions(val includeNonPublic: Boolean = false) +private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: DeclarationDescriptor): Boolean { + val package1 = DescriptorUtils.getParentOfType(descriptor1, javaClass<PackageFragmentDescriptor>()) + val package2 = DescriptorUtils.getParentOfType(descriptor2, javaClass<PackageFragmentDescriptor>()) + return package1 != null && package2 != null && package1.fqName == package2.fqName +} + class DocumentationBuilder(val session: ResolveSession, val options: DocumentationOptions) { val visibleToDocumentation = setOf(Visibilities.INTERNAL, Visibilities.PROTECTED, Visibilities.PUBLIC) val descriptorToNode = hashMapOf<DeclarationDescriptor, DocumentationNode>() @@ -141,6 +147,22 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati descriptors.forEach { descriptor -> appendChild(descriptor, kind) } } + fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor, + externalClassNodes: MutableMap<FqName, DocumentationNode>): DocumentationNode { + if (descriptor is CallableMemberDescriptor) { + val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() + if (extensionClassDescriptor != null && !isSamePackage(descriptor, extensionClassDescriptor)) { + val fqName = DescriptorUtils.getFqNameFromTopLevelClass(extensionClassDescriptor) + return externalClassNodes.getOrPut(fqName, { + val newNode = DocumentationNode(fqName.asString(), Content.Empty, Kind.ExternalClass) + append(newNode, DocumentationReference.Kind.Member) + newNode + }) + } + } + return this + } + fun DocumentationNode.appendFragments(fragments: Collection<PackageFragmentDescriptor>) { val descriptors = hashMapOf<String, List<DeclarationDescriptor>>() for ((name, parts) in fragments.groupBy { it.fqName }) { @@ -149,7 +171,11 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati for ((packageName, declarations) in descriptors) { println(" package $packageName: ${declarations.count()} nodes") val packageNode = DocumentationNode(packageName, Content.Empty, Kind.Package) - packageNode.appendChildren(declarations, DocumentationReference.Kind.Member) + val externalClassNodes = hashMapOf<FqName, DocumentationNode>() + declarations.forEach { descriptor -> + val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes) + parent.appendChild(descriptor, DocumentationReference.Kind.Member) + } append(packageNode, DocumentationReference.Kind.Member) } } @@ -205,6 +231,15 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati private fun DeclarationDescriptor.inClassObject() = getContainingDeclaration().let { it is ClassDescriptor && it.getKind() == ClassKind.CLASS_OBJECT } + fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? { + val extensionReceiver = getExtensionReceiverParameter() + if (extensionReceiver != null) { + val type = extensionReceiver.getType() + return type.getConstructor().getDeclarationDescriptor() as? ClassDescriptor + } + return null + } + fun FunctionDescriptor.build(): DocumentationNode { val node = DocumentationNode(this, if (inClassObject()) Kind.ClassObjectFunction else Kind.Function) diff --git a/src/Model/DocumentationNode.kt b/src/Model/DocumentationNode.kt index caae77a8..5f9aabab 100644 --- a/src/Model/DocumentationNode.kt +++ b/src/Model/DocumentationNode.kt @@ -89,6 +89,7 @@ public open class DocumentationNode(val name: String, Module + ExternalClass Annotation Value diff --git a/test/data/format/extensions.class.md b/test/data/format/extensions.class.md new file mode 100644 index 00000000..a9747756 --- /dev/null +++ b/test/data/format/extensions.class.md @@ -0,0 +1,16 @@ +[test](out.md) / [foo](out.md) / [String](out.md) + + +### Extensions for String + + +| [fn](out.md) | `fun String.fn(): Unit` +`fun String.fn(x: Int): Unit` +Function with receiver + + | +| [foobar](out.md) | `val String.foobar: Int` +Property with receiver. + + | + diff --git a/test/data/format/extensions.kt b/test/data/format/extensions.kt new file mode 100644 index 00000000..6f2eff9d --- /dev/null +++ b/test/data/format/extensions.kt @@ -0,0 +1,19 @@ +package foo + +/** + * Function with receiver + */ +fun String.fn() { +} + +/** + * Function with receiver + */ +fun String.fn(x: Int) { +} + +/** + * Property with receiver. + */ +val String.foobar: Int + get() = size() * 2 diff --git a/test/data/format/extensions.package.md b/test/data/format/extensions.package.md new file mode 100644 index 00000000..13f40457 --- /dev/null +++ b/test/data/format/extensions.package.md @@ -0,0 +1,18 @@ +[test](out.md) / [foo](out.md) + + +# foo + + +``` +package foo +``` + + + + +### Extensions for External Classes + + +| [String](out.md) | `` | + diff --git a/test/data/functions/functionWithReceiver.kt b/test/data/functions/functionWithReceiver.kt index 663c3e56..c8473251 100644 --- a/test/data/functions/functionWithReceiver.kt +++ b/test/data/functions/functionWithReceiver.kt @@ -2,4 +2,10 @@ * Function with receiver */ fun String.fn() { -}
\ No newline at end of file +} + +/** + * Function with receiver + */ +fun String.fn(x: Int) { +} diff --git a/test/data/properties/propertyWithReceiver.kt b/test/data/properties/propertyWithReceiver.kt new file mode 100644 index 00000000..e282f6bd --- /dev/null +++ b/test/data/properties/propertyWithReceiver.kt @@ -0,0 +1,2 @@ +val String.foobar: Int + get() = size() * 2 diff --git a/test/src/format/MarkdownFormatTest.kt b/test/src/format/MarkdownFormatTest.kt index 08267d32..3d32743f 100644 --- a/test/src/format/MarkdownFormatTest.kt +++ b/test/src/format/MarkdownFormatTest.kt @@ -37,4 +37,13 @@ public class MarkdownFormatTest { markdownService.appendNodes(tempLocation, output, model.members.single().members) } } + + Test fun extensions() { + verifyOutput("test/data/format/extensions.kt", ".package.md") { model, output -> + markdownService.appendNodes(tempLocation, output, model.members) + } + verifyOutput("test/data/format/extensions.kt", ".class.md") { model, output -> + markdownService.appendNodes(tempLocation, output, model.members.single().members) + } + } } diff --git a/test/src/model/FunctionTest.kt b/test/src/model/FunctionTest.kt index bf7471ea..299f33a8 100644 --- a/test/src/model/FunctionTest.kt +++ b/test/src/model/FunctionTest.kt @@ -21,23 +21,32 @@ public class FunctionTest { Test fun functionWithReceiver() { verifyModel("test/data/functions/functionWithReceiver.kt") { model -> with(model.members.single().members.single()) { - assertEquals("fn", name) - assertEquals(DocumentationNode.Kind.Function, kind) - assertEquals("Function with receiver", content.summary.toTestString()) - assertEquals(4, details.count()) - assertEquals("internal", details.elementAt(0).name) - assertEquals("final", details.elementAt(1).name) - with(details.elementAt(2)) { - assertEquals("<this>", name) - assertEquals(DocumentationNode.Kind.Receiver, kind) - assertEquals(Content.Empty, content) - assertEquals("String", details.single().name) + assertEquals("String", name) + assertEquals(DocumentationNode.Kind.ExternalClass, kind) + assertEquals(2, members.count()) + with(members[0]) { + assertEquals("fn", name) + assertEquals(DocumentationNode.Kind.Function, kind) + assertEquals("Function with receiver", content.summary.toTestString()) + assertEquals(4, details.count()) + assertEquals("internal", details.elementAt(0).name) + assertEquals("final", details.elementAt(1).name) + with(details.elementAt(2)) { + assertEquals("<this>", name) + assertEquals(DocumentationNode.Kind.Receiver, kind) + assertEquals(Content.Empty, content) + assertEquals("String", details.single().name) + assertTrue(members.none()) + assertTrue(links.none()) + } + assertEquals("Unit", details.elementAt(3).name) assertTrue(members.none()) assertTrue(links.none()) } - assertEquals("Unit", details.elementAt(3).name) - assertTrue(members.none()) - assertTrue(links.none()) + with(members[1]) { + assertEquals("fn", name) + assertEquals(DocumentationNode.Kind.Function, kind) + } } } } @@ -186,4 +195,3 @@ Documentation""", content.description.toTestString()) } } } - diff --git a/test/src/model/PropertyTest.kt b/test/src/model/PropertyTest.kt index 0bf9714d..14c43f78 100644 --- a/test/src/model/PropertyTest.kt +++ b/test/src/model/PropertyTest.kt @@ -112,4 +112,17 @@ public class PropertyTest { } } } + + Test fun propertyWithReceiver() { + verifyModel("test/data/properties/propertyWithReceiver.kt") { model -> + with(model.members.single().members.single()) { + assertEquals("String", name) + assertEquals(DocumentationNode.Kind.ExternalClass, kind) + with(members.single()) { + assertEquals("foobar", name) + assertEquals(DocumentationNode.Kind.Property, kind) + } + } + } + } } |