Dokka plugin for documenting pseudo-constructors for Kotlin.
Most object-oriented languages have constructors that can be used to create an instance of a class. But mostly, we need some preprocessing with the arguments: validating, transforming, etc. Java introduced flexible constructor bodies for this purpose, but Kotlin cannot have such feature because we're stuck at this C-style constructor syntax:
class MyClass(val i: Int) {
constructor(s: String): this(s.toInt()) // Might cause `IllegalArgumentException`
}Or we might have an interface that can be instantiated by everyone, but giving a helper function would be great:
interface IntWrapper {
val i: Int
}
fun wrap(i: Int): IntWrapper = object: IntWrapper {
override val i: Int = i
}And sometimes we need a generic constructor:
class Logger<T>(val forClass: KClass<T>)
inline fun <reified T> T.createLogger(): Logger = Logger(T::class)This leads us to using functions that look like constructors:
operator fun invokefunctions in companion objects or an extension to one- Functions with the same name of the class (
fun Char())
These are what we call pseudo-constructors,
since they don't look different with normal constructors: MyClass(<parameters>)
This plugin injects such functions to the Constructors table of a class documentation:

Because they are technically not constructors, this is purely decorative:
navigating to such constructor leads to the actual definition of the pseudo-constructor,
which at the example above is an operator fun invoke in a companion object,
and the function's name is displayed at the first column of the Constructor table
for when using it as a function reference(::MyClass).
Summary:
If the function can be used with the same syntax as normal constructors in the class
and is annotated with @ConstructorLike, it is a pseudo-constructor.
To make a function as a pseudo-constructor, it must be annotated with @ConstructorLike.
Other functions will not be included.
The target type of the annotated function is its return type.
The target type must not be kotlin.Unit, kotlin.Nothing, an annotation class, enum class, or object.
It must also be in the same module and package with the function
so that the plugin can inject the constructor to the documentation.
Then, the function must be an operator fun invoke or its name must match the target type's simple name.
For ease of parsing, we define a receiver type of the function:
for extension functions, it is the receiver itself, and for member functions, it is the classlike owning the function.
While a function can have no receiver type in case it is a package-level non-extension non-operator fun invoke function,
it cannot have two receiver types: the function cannot be an extension member function.
Finally, it is validated based on the receiver type's kind:
companion object: the target type must be the parent of the companion if the function is anoperator fun invoke, otherwise the target type must be a nested class of the parent of the receiver type. Other functions that do not target the parent classlike will be parsed regarding the companion as anobject.- Plain classlikes: the function must not be an
operator fun invoke, the target type must be a nested class of the receiver type, and if the receiver type is not anobject, the target type must also beinner. - No receiver type: the function must not be an
operator fun invoke, and the target type must be a package-level classlike.
If the function violates anything from above, the plugin will raise a warning and will not include the function as a pseudo-constructor.
Examples
To be brief, function bodies are omitted.
class MyClass { // Also applies to abstract classes and interfaces
class NestedClass
inner class InnerClass
companion object {
class NestedInObject // Also applies to nested classes in standalone objects
// OK: used as `MyClass()`
@ConstructorLike
operator fun invoke(): MyClass
// Bad: it is not marked `operator`
@ConstructorLike
fun invoke(): MyClass
// Will not be presented afterward, but applies on all cases.
// Bad: it is an extension
@ConstructorLike
operator fun Any.invoke(): MyClass
// Bad: it does not return `MyClass`
@ConstructorLike
operator fun invoke(): Any
// In practice, this will cause a 'target in different module' warning.
// OK: used as `MyClass.NestedClass()`
@ConstructorLike
fun NestedClass(): NestedClass
// Bad: it is an extension
@ConstructorLike
fun Any.NestedClass(): NestedClass
// Will not be presented afterward, the function can either be an extension or a member, but not both.
// Bad: it is not named `NestedClass`
@ConstructorLike
fun createNested(): NestedClass
// Will not be presented afterward, but if the function is not an `operator fun invoke`, its name must match its target type.
// Bad: it does not return `NestedClass`
@ConstructorLike
fun NestedClass(): Any
// Will not be presented afterward, same reason as above.
// Bad: target type is `InnerClass`
@ConstructorLike
fun InnerClass(): InnerClass
// You cannot create inner classes with a companion object in any way.
// OK: used as `MyClass.Companion.NestedInObject()`
@ConstructorLike
fun NestedInObject(): NestedInObject
}
// Bad: target type is `NestedClass`
@ConstructorLike
fun NestedClass(): NestedClass
// You cannot create nested classes with the outer class in any way, except using companion objects.
// OK: used as `myClassInstance.InnerClass()`
@ConstructorLike
fun InnerClass(): InnerClass
// Bad: it does not return `InnerClass`
@ConstructorLike
fun InnerClass(): Any
// Bad: it is an `operator fun invoke`
@ConstructorLike
operator fun invoke(): InnerClass
}
// OK: used as `MyClass()`
@ConstructorLike
operator fun MyClass.Companion.invoke(): MyClass
// Bad: it is not an extension function on `MyClass.Companion`
@ConstructorLike
operator fun MyClass.invoke(): MyClass
// Bad: it does not return `MyClass`
@ConstructorLike
operator fun MyClass.Companion.invoke(): Any
// OK: used as `MyClass()`
@ConstructorLike
fun MyClass(): MyClass
// Bad: it is an extension
@ConstructorLike
fun Any.MyClass(): MyClass
// OK: used as `MyClass.NestedClass()`
@ConstructorLike
fun MyClass.Companion.NestedClass(): NestedClass
// Bad: it is not an extension on `MyClass.Companion`
@ConstructorLike
fun MyClass.NestedClass(): NestedClass
// OK: used as `myClassInstance.InnerClass()`
@ConstructorLike
fun MyClass.InnerClass(): InnerClass
// Bad: it is not an extension on `MyClass`
@ConstructorLike
fun MyClass.Companion.InnerClass(): InnerClass
// OK: used as `MyClass.Companion.NestedInObject()`
@ConstructorLike
fun MyClass.Companion.NestedInObject(): NestedInObjectIn case of expect/actual declarations,
only the expect matters since actual declarations must match the signature of expect.
But make sure to annotate actual declarations with @ConstructorLike too,
otherwise the behavior of the plugin is undefined.
This project's source code is mainly licensed with MPL 2.0. Note that some files have different licenses other than MPL: currently, only DefaultPageCreatorFunctions.kt is licensed with Apache 2.0.
For more third-party license information, see third-party-licenses/.