Skip to content

Commit 03a2736

Browse files
committed
Add dev.mokkery.mockable plugin
1 parent 2b94a25 commit 03a2736

62 files changed

Lines changed: 1519 additions & 109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build-mokkery/src/main/kotlin/Utils.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,20 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
99
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
1010
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
1111
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
12+
import java.net.URI
1213
import java.util.*
1314

15+
const val testingRepoName = "testing"
16+
17+
val Project.testingRepoUrl: URI
18+
get() = rootProject
19+
.layout
20+
.buildDirectory
21+
.dir("testing-repository")
22+
.get()
23+
.asFile
24+
.toURI()
25+
1426
fun Project.loadLocalProperties() {
1527
val secretPropsFile = rootProject.file("local.properties")
1628
if (secretPropsFile.exists()) {

build-mokkery/src/main/kotlin/mokkery-publish.gradle.kts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,20 @@ plugins {
1212
id("org.jetbrains.dokka")
1313
}
1414

15+
publishing.repositories {
16+
maven {
17+
name = testingRepoName
18+
url = testingRepoUrl
19+
}
20+
}
21+
1522
loadLocalProperties()
1623

1724
dokka.dokkaSourceSets.configureEach {
1825
sourceLink {
1926
localDirectory = rootDir
2027
remoteUrl = URI("${GitHttpsUrl}/tree/master")
21-
remoteLineSuffix ="#L"
22-
}
23-
}
24-
25-
publishing.repositories {
26-
maven {
27-
name = "testing"
28-
url = rootProject.layout
29-
.buildDirectory
30-
.dir("testing-repository")
31-
.let(::uri)
28+
remoteLineSuffix = "#L"
3229
}
3330
}
3431

mokkery-core-plugin/src/main/kotlin/dev/mokkery/plugin/core/MokkeryConfigApi.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ fun <T> CompilerConfiguration.getSingleOrDefault(option: MokkeryOption<T>): T {
1010
?: option.defaultValues.singleOrNull()
1111
?: error("No value for ${option.configurationKey}")
1212
}
13+
14+
fun <T> CompilerConfiguration.getAllOrDefault(option: MokkeryOption<T>): List<T> = get(option.configurationKey)
15+
?: option.defaultValues
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.mokkery.plugin.core
2+
3+
import org.jetbrains.kotlin.name.Name
4+
5+
object MokkeryCore {
6+
7+
object Names {
8+
val mockableConstructorMarkerParam = Name.identifier("mokkeryMockableMarker")
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.mokkery.plugin.core.fir
2+
3+
import org.jetbrains.kotlin.fir.FirSession
4+
import org.jetbrains.kotlin.fir.declarations.constructors
5+
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
6+
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
7+
8+
fun FirClassSymbol<*>.hasMokkeryGeneratedConstructor(
9+
session: FirSession
10+
): Boolean = constructors(session).any(FirConstructorSymbol::isMokkeryGeneratedConstructor)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.mokkery.plugin.core.fir
2+
3+
import dev.mokkery.plugin.core.MokkeryCore
4+
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
5+
import org.jetbrains.kotlin.fir.types.isUnit
6+
7+
fun FirConstructorSymbol.isMokkeryGeneratedConstructor(): Boolean {
8+
val param = valueParameterSymbols.firstOrNull() ?: return false
9+
return param.name == MokkeryCore.Names.mockableConstructorMarkerParam
10+
&& param.resolvedReturnType.isUnit
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dev.mokkery.plugin.core.ir
2+
3+
import dev.mokkery.plugin.core.MokkeryCore
4+
import org.jetbrains.kotlin.ir.declarations.IrClass
5+
import org.jetbrains.kotlin.ir.declarations.IrConstructor
6+
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
7+
import org.jetbrains.kotlin.ir.types.isUnit
8+
import org.jetbrains.kotlin.ir.util.constructors
9+
10+
fun IrClass.findMokkeryConstructor(): IrConstructor? = constructors.find { it.isMokkeryConstructor() }
11+
12+
fun IrConstructor.isMokkeryConstructor(): Boolean {
13+
val valueParam = parameters.find { it.kind == IrParameterKind.Regular } ?: return false
14+
return valueParam.name == MokkeryCore.Names.mockableConstructorMarkerParam && valueParam.type.isUnit()
15+
}

mokkery-core-tooling/src/main/kotlin/dev/mokkery/internal/options/MokkeryOption.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ private class CachedMokkeryProjection<T>(
4040
private val builder: (MokkeryOption<*>) -> T
4141
) : MokkeryOptionProjection<T> {
4242
private val store = mutableMapOf<MokkeryOption<*>, T>()
43-
override fun project(option: MokkeryOption<*>): T = store.getOrPut(option) { builder(option) }
43+
override fun project(option: MokkeryOption<*>): T = synchronized(store) {
44+
store.getOrPut(option) { builder(option) }
45+
}
4446
}
4547

4648
@Suppress("UNCHECKED_CAST")

mokkery-core-tooling/src/main/kotlin/dev/mokkery/internal/options/MokkeryOptionType.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import dev.mokkery.MockMode
44
import dev.mokkery.annotations.InternalMokkeryApi
55
import dev.mokkery.internal.serialization.AnnotationSelectorSerializer
66
import dev.mokkery.internal.serialization.BooleanSerializer
7+
import dev.mokkery.internal.serialization.FqNameSerializer
78
import dev.mokkery.internal.serialization.MokkerySerializer
89
import dev.mokkery.internal.serialization.VerifyModeSerializer
910
import dev.mokkery.internal.serialization.enumSerializer
@@ -24,6 +25,11 @@ public data class MokkeryOptionType<T>(
2425
serializer = BooleanSerializer
2526
)
2627

28+
public val fqName: MokkeryOptionType<String> = MokkeryOptionType(
29+
description = "<fq-name>",
30+
serializer = FqNameSerializer
31+
)
32+
2733
public val verifyMode: MokkeryOptionType<VerifyMode> = MokkeryOptionType(
2834
description = $$"VerifyMode expression <exhaustive|order|exhaustiveOrder|not|soft|atLeast($n)|atMost($n)|inRange($range)|exactly($n)>",
2935
serializer = VerifyModeSerializer
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dev.mokkery.internal.serialization
2+
3+
internal object FqNameSerializer : MokkerySerializer<String> {
4+
override fun serialize(obj: String): String = obj.also(::validate)
5+
6+
override fun deserialize(string: String): String = string.also(::validate)
7+
8+
private fun validate(fqName: String) {
9+
require(fqName.isNotEmpty()) { "Invalid FQ name - must not be empty" }
10+
val segments = fqName.split(".")
11+
var offset = 0
12+
segments.forEach { segment ->
13+
if (segment.isEmpty()) fqNameError(fqName, offset, "empty segment")
14+
val illegalIndex = segment.indexOfFirst { !it.isJavaIdentifierPart() }
15+
.takeIf { it != -1 }
16+
?: if (!segment.first().isJavaIdentifierStart()) 0 else null
17+
if (illegalIndex != null) {
18+
val char = segment[illegalIndex]
19+
val message = if (illegalIndex == 0) "illegal start character '$char'"
20+
else "illegal character '$char'"
21+
fqNameError(fqName, offset + illegalIndex, message)
22+
}
23+
offset += segment.length + 1
24+
}
25+
}
26+
27+
private fun fqNameError(fqName: String, position: Int, message: String): Nothing {
28+
error("Invalid FQ name - $message:\n$fqName\n${" ".repeat(position)}^")
29+
}
30+
}

0 commit comments

Comments
 (0)