mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-12-26 10:37:14 +00:00
commit
1b4900d691
@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.21.2
|
||||
|
||||
* `KSP`:
|
||||
* `ClassCasts`:
|
||||
* Module has been initialized
|
||||
|
||||
## 0.21.1
|
||||
|
||||
* `KSP`:
|
||||
|
@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.21.1
|
||||
android_code_version=260
|
||||
version=0.21.2
|
||||
android_code_version=261
|
||||
|
7
ksp/classcasts/build.gradle
Normal file
7
ksp/classcasts/build.gradle
Normal file
@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
21
ksp/classcasts/generator/build.gradle
Normal file
21
ksp/classcasts/generator/build.gradle
Normal file
@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
apply from: "$publishJvmOnlyPath"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.generator")
|
||||
api project(":micro_utils.ksp.classcasts")
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
90
ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt
Normal file
90
ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt
Normal file
@ -0,0 +1,90 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.symbol.ClassKind
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||
|
||||
|
||||
private fun FileSpec.Builder.addTopLevelImport(className: ClassName) {
|
||||
className.topLevelClassName().let {
|
||||
addImport(it.packageName, it.simpleNames)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FileSpec.Builder.createTypeDefinition(ksClassDeclaration: KSClassDeclaration): TypeName {
|
||||
val className = ksClassDeclaration.toClassName()
|
||||
return if (ksClassDeclaration.typeParameters.isNotEmpty()) {
|
||||
className.parameterizedBy(
|
||||
ksClassDeclaration.typeParameters.map {
|
||||
it.bounds.first().resolve().also {
|
||||
val typeClassName = it.toClassName()
|
||||
addTopLevelImport(typeClassName)
|
||||
}.toTypeName()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
className
|
||||
}
|
||||
}
|
||||
|
||||
internal fun FileSpec.Builder.fill(
|
||||
sourceKSClassDeclaration: KSClassDeclaration,
|
||||
subtypes: Set<KSClassDeclaration>
|
||||
) {
|
||||
subtypes.forEach { targetClassDeclaration ->
|
||||
val sourceClassName = sourceKSClassDeclaration.toClassName()
|
||||
val targetClassClassName = targetClassDeclaration.toClassName()
|
||||
val targetClassTypeDefinition = createTypeDefinition(targetClassDeclaration)
|
||||
val simpleName = targetClassDeclaration.simpleName.asString()
|
||||
val withFirstLowerCase = simpleName.replaceFirstChar { it.lowercase() }
|
||||
val castedOrNullName = "${withFirstLowerCase}OrNull"
|
||||
|
||||
addTopLevelImport(targetClassClassName)
|
||||
addFunction(
|
||||
FunSpec.builder(castedOrNullName).apply {
|
||||
receiver(sourceClassName)
|
||||
addCode(
|
||||
"return this as? %L",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(targetClassTypeDefinition.copy(nullable = true))
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
addFunction(
|
||||
FunSpec.builder("${withFirstLowerCase}OrThrow").apply {
|
||||
receiver(sourceClassName)
|
||||
addCode(
|
||||
"return this as %L",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(targetClassTypeDefinition)
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
addFunction(
|
||||
FunSpec.builder("if$simpleName").apply {
|
||||
val genericType = TypeVariableName("T", null)
|
||||
addTypeVariable(genericType)
|
||||
receiver(sourceClassName)
|
||||
addParameter(
|
||||
"block",
|
||||
LambdaTypeName.get(
|
||||
null,
|
||||
targetClassTypeDefinition,
|
||||
returnType = genericType
|
||||
)
|
||||
)
|
||||
addCode(
|
||||
"return ${castedOrNullName}() ?.let(block)",
|
||||
targetClassTypeDefinition
|
||||
)
|
||||
returns(genericType.copy(nullable = true))
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}
|
119
ksp/classcasts/generator/src/main/kotlin/Processor.kt
Normal file
119
ksp/classcasts/generator/src/main/kotlin/Processor.kt
Normal file
@ -0,0 +1,119 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getAllSuperTypes
|
||||
import com.google.devtools.ksp.getAnnotationsByType
|
||||
import com.google.devtools.ksp.isAnnotationPresent
|
||||
import com.google.devtools.ksp.processing.CodeGenerator
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.symbol.*
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import dev.inmo.micro_ksp.generator.writeFile
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
import java.io.File
|
||||
|
||||
class Processor(
|
||||
private val codeGenerator: CodeGenerator
|
||||
) : SymbolProcessor {
|
||||
@OptIn(KspExperimental::class)
|
||||
private fun FileSpec.Builder.generateClassCasts(
|
||||
ksClassDeclaration: KSClassDeclaration,
|
||||
resolver: Resolver
|
||||
) {
|
||||
val rootAnnotation = ksClassDeclaration.getAnnotationsByType(ClassCastsIncluded::class).first()
|
||||
val (includeRegex: Regex?, excludeRegex: Regex?) = rootAnnotation.let {
|
||||
it.typesRegex.takeIf { it.isNotEmpty() } ?.let(::Regex) to it.excludeRegex.takeIf { it.isNotEmpty() } ?.let(::Regex)
|
||||
}
|
||||
val classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>()
|
||||
|
||||
fun KSClassDeclaration.checkSupertypeLevel(levelsAllowed: Int?): Boolean {
|
||||
val supertypes by lazy {
|
||||
superTypes.map { it.resolve().declaration }
|
||||
}
|
||||
return when {
|
||||
levelsAllowed == null -> true
|
||||
levelsAllowed <= 0 -> false
|
||||
supertypes.any { it == ksClassDeclaration } -> true
|
||||
else -> supertypes.any {
|
||||
(it as? KSClassDeclaration) ?.checkSupertypeLevel(levelsAllowed - 1) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleDeclaration(ksDeclarationContainer: KSDeclarationContainer) {
|
||||
ksDeclarationContainer.declarations.forEach { potentialSubtype ->
|
||||
val simpleName = potentialSubtype.simpleName.getShortName()
|
||||
when {
|
||||
potentialSubtype === ksClassDeclaration -> {}
|
||||
potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class) -> return@forEach
|
||||
potentialSubtype !is KSClassDeclaration || !potentialSubtype.checkSupertypeLevel(rootAnnotation.levelsToInclude.takeIf { it >= 0 }) -> return@forEach
|
||||
excludeRegex ?.matches(simpleName) == true -> return@forEach
|
||||
includeRegex ?.matches(simpleName) == false -> {}
|
||||
else -> classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(potentialSubtype)
|
||||
}
|
||||
handleDeclaration(potentialSubtype as? KSDeclarationContainer ?: return@forEach)
|
||||
}
|
||||
}
|
||||
resolver.getAllFiles().forEach {
|
||||
handleDeclaration(it)
|
||||
}
|
||||
fun fillWithSealeds(current: KSClassDeclaration) {
|
||||
current.getSealedSubclasses().forEach {
|
||||
val simpleName = it.simpleName.getShortName()
|
||||
if (
|
||||
includeRegex ?.matches(simpleName) == false
|
||||
|| excludeRegex ?.matches(simpleName) == true
|
||||
|| it.isAnnotationPresent(ClassCastsExcluded::class)
|
||||
) {
|
||||
return@forEach
|
||||
}
|
||||
classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(it)
|
||||
fillWithSealeds(it)
|
||||
}
|
||||
}
|
||||
fillWithSealeds(ksClassDeclaration)
|
||||
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Suppress::class).apply {
|
||||
addMember("\"unused\"")
|
||||
addMember("\"RemoveRedundantQualifierName\"")
|
||||
addMember("\"RedundantVisibilityModifier\"")
|
||||
addMember("\"NOTHING_TO_INLINE\"")
|
||||
addMember("\"UNCHECKED_CAST\"")
|
||||
addMember("\"OPT_IN_USAGE\"")
|
||||
useSiteTarget(AnnotationSpec.UseSiteTarget.FILE)
|
||||
}.build()
|
||||
)
|
||||
fill(
|
||||
ksClassDeclaration,
|
||||
classesSubtypes.values.flatten().toSet()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
(resolver.getSymbolsWithAnnotation(ClassCastsIncluded::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
||||
val prefix = it.getAnnotationsByType(ClassCastsIncluded::class).first().outputFilePrefix
|
||||
it.writeFile(prefix = prefix, suffix = "ClassCasts") {
|
||||
FileSpec.builder(
|
||||
it.packageName.asString(),
|
||||
"${it.simpleName.getShortName()}ClassCasts"
|
||||
).apply {
|
||||
addFileComment(
|
||||
"""
|
||||
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
TO REGENERATE IT JUST DELETE FILE
|
||||
ORIGINAL FILE: ${it.containingFile ?.fileName}
|
||||
""".trimIndent()
|
||||
)
|
||||
generateClassCasts(it, resolver)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
11
ksp/classcasts/generator/src/main/kotlin/Provider.kt
Normal file
11
ksp/classcasts/generator/src/main/kotlin/Provider.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
|
||||
class Provider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||
environment.codeGenerator
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.Provider
|
27
ksp/classcasts/generator/test/build.gradle
Normal file
27
ksp/classcasts/generator/test/build.gradle
Normal file
@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
id "com.google.devtools.ksp"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.ksp.classcasts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
add("kspCommonMainMetadata", project(":micro_utils.ksp.classcasts.generator"))
|
||||
}
|
||||
|
||||
ksp {
|
||||
}
|
15
ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt
Normal file
15
ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator.test
|
||||
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
|
||||
@ClassCastsIncluded(levelsToInclude = 1)
|
||||
sealed interface Test {
|
||||
object A : Test
|
||||
@ClassCastsExcluded
|
||||
object B : Test // Will not be included in class casts due to annotation ClassCastsExcluded
|
||||
object C : Test
|
||||
interface D : Test {
|
||||
object DD : D // Will not be included in class casts due to levelsToInclude
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||
// TO REGENERATE IT JUST DELETE FILE
|
||||
// ORIGINAL FILE: Test.kt
|
||||
@file:Suppress(
|
||||
"unused",
|
||||
"RemoveRedundantQualifierName",
|
||||
"RedundantVisibilityModifier",
|
||||
"NOTHING_TO_INLINE",
|
||||
"UNCHECKED_CAST",
|
||||
"OPT_IN_USAGE",
|
||||
)
|
||||
|
||||
package dev.inmo.micro_utils.ksp.classcasts.generator.test
|
||||
|
||||
import dev.inmo.micro_utils.ksp.classcasts.generator.test.Test
|
||||
import kotlin.Suppress
|
||||
|
||||
public inline fun Test.aOrNull(): Test.A? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A
|
||||
|
||||
public inline fun Test.aOrThrow(): Test.A = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A
|
||||
|
||||
public inline fun <T> Test.ifA(block: (Test.A) -> T): T? = aOrNull() ?.let(block)
|
||||
|
||||
public inline fun Test.cOrNull(): Test.C? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C
|
||||
|
||||
public inline fun Test.cOrThrow(): Test.C = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C
|
||||
|
||||
public inline fun <T> Test.ifC(block: (Test.C) -> T): T? = cOrNull() ?.let(block)
|
||||
|
||||
public inline fun Test.dOrNull(): Test.D? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun Test.dOrThrow(): Test.D = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun <T> Test.ifD(block: (Test.D) -> T): T? = dOrNull() ?.let(block)
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ClassCastsExcluded
|
10
ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt
Normal file
10
ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.ksp.classcasts
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ClassCastsIncluded(
|
||||
val typesRegex: String = "",
|
||||
val excludeRegex: String = "",
|
||||
val outputFilePrefix: String = "",
|
||||
val levelsToInclude: Int = -1
|
||||
)
|
@ -55,6 +55,10 @@ String[] includes = [
|
||||
":ksp:sealed:generator",
|
||||
":ksp:sealed:generator:test",
|
||||
|
||||
":ksp:classcasts",
|
||||
":ksp:classcasts:generator",
|
||||
":ksp:classcasts:generator:test",
|
||||
|
||||
":dokka"
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user