aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/links/DRI.kt
blob: b6270467104501df8f8d3ace2225a33e53b4722a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package org.jetbrains.dokka.links

import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeProjection
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull

/**
 * [DRI] stands for DokkaResourceIdentifier
 */
data class DRI(
    val packageName: String? = null,
    val classNames: String? = null,
    val callable: Callable? = null,
    val target: Int? = null,
    val extra: String? = null
) {
    override fun toString(): String =
        "${packageName.orEmpty()}/${classNames.orEmpty()}/${callable?.name.orEmpty()}/${callable?.signature().orEmpty()}/${target?.toString().orEmpty()}/${extra.orEmpty()}"

    companion object {
        fun from(descriptor: DeclarationDescriptor) = descriptor.parentsWithSelf.run {
            val callable = firstIsInstanceOrNull<CallableDescriptor>()
            val params = callable?.let { listOfNotNull(it.extensionReceiverParameter) + it.valueParameters }.orEmpty()
            DRI(
                firstIsInstanceOrNull<PackageFragmentDescriptor>()?.fqName?.asString(),
                filterIsInstance<ClassDescriptor>().toList().takeIf { it.isNotEmpty() }?.asReversed()
                    ?.joinToString(separator = ".") { it.name.asString() },
                callable?.let { Callable.from(it) },
                firstIsInstanceOrNull<ParameterDescriptor>()?.let { params.indexOf(it) },
                null
            )
        }

        val topLevel = DRI()
    }
}

fun DRI.withClass(name: String) = copy(classNames = if (classNames.isNullOrBlank()) name else "$classNames.$name")

val DRI.parent: DRI
    get() = when {
        extra != null -> copy(extra = null)
        target != null -> copy(target = null)
        callable != null -> copy(callable = null)
        classNames != null -> copy(classNames = classNames.substringBeforeLast('.').takeIf { it.isNotBlank() })
        else -> DRI.topLevel
    }

data class Callable(
    val name: String,
    val receiver: TypeReference? = null,
    val params: List<TypeReference>
) {
    fun signature() = "${receiver?.toString().orEmpty()}#${params.joinToString("#")}"

    companion object {
        fun from(descriptor: CallableDescriptor) = with(descriptor) {
            Callable(
                name.asString(),
                extensionReceiverParameter?.let { TypeReference.from(it) },
                valueParameters.mapNotNull { TypeReference.from(it) }
            )
        }
    }
}

data class TypeReference(val classNames: String, val typeBounds: List<TypeReference> = emptyList()) {
    override fun toString() = classNames + if (typeBounds.isNotEmpty()) {
        "[${typeBounds.joinToString(",")}]"
    } else {
        ""
    }

    companion object {
        fun from(d: ReceiverParameterDescriptor): TypeReference? =
            when (val value = d.value) {
                is ExtensionReceiver -> TypeReference(
                    classNames = value.type.constructorName.orEmpty(),
                    typeBounds = value.type.arguments.map { from(it) }
                )
                else -> run {
                    println("Unknown value type for $d")
                    null
                }
            }

        fun from(d: ValueParameterDescriptor): TypeReference? = from(d.type)

        private fun from(tp: TypeParameterDescriptor): TypeReference =
            TypeReference("", tp.upperBounds.map { from(it) })

        private fun from(t: KotlinType): TypeReference =
            when (val d = t.constructor.declarationDescriptor) {
                is TypeParameterDescriptor -> from(d)
                else -> TypeReference(t.constructorName.orEmpty(), t.arguments.map { from(it) })
            }

        private fun from(t: TypeProjection): TypeReference =
            if (t.isStarProjection) {
                starProjection
            } else {
                from(t.type)
            }

        val starProjection = TypeReference("*")
    }
}

private operator fun <T> List<T>.component6(): T = get(5)

private val KotlinType.constructorName
    get() = constructor.declarationDescriptor?.fqNameSafe?.asString()