godot.annotation.processor.ext.ksAnnotationExt.kt Maven / Gradle / Ivy
package godot.annotation.processor.ext
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSType
import godot.annotation.ColorNoAlpha
import godot.annotation.Dir
import godot.annotation.EnumFlag
import godot.annotation.EnumTypeHint
import godot.annotation.ExpEasing
import godot.annotation.Export
import godot.annotation.IntFlag
import godot.annotation.MultilineText
import godot.annotation.PlaceHolderText
import godot.annotation.RegisterClass
import godot.annotation.RegisterConstructor
import godot.annotation.RegisterFunction
import godot.annotation.RegisterProperty
import godot.annotation.RegisterSignal
import godot.annotation.Rpc
import godot.annotation.Tool
import godot.entrygenerator.model.ColorNoAlphaHintAnnotation
import godot.entrygenerator.model.DirHintAnnotation
import godot.entrygenerator.model.EnumFlagHintStringAnnotation
import godot.entrygenerator.model.ExpEasingHintAnnotation
import godot.entrygenerator.model.ExportAnnotation
import godot.entrygenerator.model.FileHintAnnotation
import godot.entrygenerator.model.GodotAnnotation
import godot.entrygenerator.model.GodotBaseTypeAnnotation
import godot.entrygenerator.model.IntFlagHintAnnotation
import godot.entrygenerator.model.MultilineTextHintAnnotation
import godot.entrygenerator.model.PlaceHolderTextHintAnnotation
import godot.entrygenerator.model.Range
import godot.entrygenerator.model.RangeHintAnnotation
import godot.entrygenerator.model.RegisterClassAnnotation
import godot.entrygenerator.model.RegisterConstructorAnnotation
import godot.entrygenerator.model.RegisterFunctionAnnotation
import godot.entrygenerator.model.RegisterPropertyAnnotation
import godot.entrygenerator.model.RegisterSignalAnnotation
import godot.entrygenerator.model.RpcAnnotation
import godot.entrygenerator.model.RpcMode
import godot.entrygenerator.model.Sync
import godot.entrygenerator.model.ToolAnnotation
import godot.entrygenerator.model.TransferMode
internal val KSAnnotation.fqNameUnsafe: String
get() = requireNotNull(this.annotationType.resolve().declaration.qualifiedName?.asString()) {
"$this has no resolvable fqName"
}
internal val KSAnnotation.rangeEnum: Range
get() = ((arguments.firstOrNull { it.name?.asString() == "or" }?.value ?: arguments[3].value) as? KSType)
?.declaration
?.qualifiedName
?.asString()
?.let { enumFqName ->
when (enumFqName) {
"${godot.registration.Range::class.qualifiedName}.${godot.registration.Range.NONE.name}" -> Range.NONE
"${godot.registration.Range::class.qualifiedName}.${godot.registration.Range.OR_GREATER.name}" -> Range.OR_GREATER
"${godot.registration.Range::class.qualifiedName}.${godot.registration.Range.OR_LESSER.name}" -> Range.OR_LESSER
else -> throw IllegalArgumentException("Unknown enum $enumFqName for hint annotation IntRange")
}
} ?: Range.NONE
internal val KSAnnotation.rpcModeEnum: RpcMode
get() = when (((arguments.firstOrNull { it.name?.asString() == "rpcMode" }?.value ?: arguments[0].value) as? KSType)?.declaration?.qualifiedName?.asString()) {
"${godot.annotation.RpcMode.ANY::class.qualifiedName}.${godot.annotation.RpcMode.ANY.name}" -> RpcMode.ANY
"${godot.annotation.RpcMode.AUTHORITY::class.qualifiedName}.${godot.annotation.RpcMode.AUTHORITY.name}" -> RpcMode.AUTHORITY
else -> RpcMode.DISABLED
}
internal val KSAnnotation.rpcSyncEnum: Sync
get() = when (((arguments.firstOrNull { it.name?.asString() == "sync" }?.value ?: arguments[1].value) as? KSType)?.declaration?.qualifiedName?.asString()) {
"${godot.annotation.Sync.SYNC::class.qualifiedName}.${godot.annotation.Sync.SYNC.name}" -> Sync.SYNC
"${godot.annotation.Sync.NO_SYNC::class.qualifiedName}.${godot.annotation.Sync.NO_SYNC.name}" -> Sync.NO_SYNC
else -> Sync.NO_SYNC
}
internal val KSAnnotation.rpcTransferModeEnum: TransferMode
get() = when (((arguments.firstOrNull { it.name?.asString() == "transferMode" }?.value ?: arguments[2].value) as? KSType)?.declaration?.qualifiedName?.asString()) {
"${godot.annotation.TransferMode.RELIABLE::class.qualifiedName}.${godot.annotation.TransferMode.RELIABLE.name}" -> TransferMode.RELIABLE
"${godot.annotation.TransferMode.UNRELIABLE::class.qualifiedName}.${godot.annotation.TransferMode.UNRELIABLE.name}" -> TransferMode.UNRELIABLE
"${godot.annotation.TransferMode.UNRELIABLE_ORDERED::class.qualifiedName}.${godot.annotation.TransferMode.UNRELIABLE_ORDERED.name}" -> TransferMode.UNRELIABLE_ORDERED
else -> TransferMode.RELIABLE
}
internal val KSAnnotation.rpcChannel: Int
get() = (arguments.firstOrNull { it.name?.asString() == "transferChannel" }?.value ?: arguments[3].value) as Int
internal fun KSAnnotation.mapToAnnotation(parentDeclaration: KSDeclaration): GodotAnnotation? {
return when (fqNameUnsafe) {
RegisterClass::class.qualifiedName -> RegisterClassAnnotation(
customName = arguments.first().value as? String,
symbolProcessorSource = this
)
RegisterConstructor::class.qualifiedName -> RegisterConstructorAnnotation(this)
RegisterFunction::class.qualifiedName -> RegisterFunctionAnnotation(this)
RegisterProperty::class.qualifiedName -> RegisterPropertyAnnotation(this)
RegisterSignal::class.qualifiedName -> RegisterSignalAnnotation(this)
Tool::class.qualifiedName -> ToolAnnotation(this)
Export::class.qualifiedName -> ExportAnnotation(this)
Rpc::class.qualifiedName -> RpcAnnotation(
rpcMode = rpcModeEnum,
sync = rpcSyncEnum,
transferMode = rpcTransferModeEnum,
transferChannel = rpcChannel,
symbolProcessorSource = this
)
"godot.annotation.GodotBaseType" -> GodotBaseTypeAnnotation(this) //is internal
EnumFlag::class.qualifiedName -> {
val setType = (parentDeclaration as KSPropertyDeclaration).type.resolve()
require(setType.declaration.qualifiedName?.asString()?.matches(Regex("^kotlin\\.collections\\..*Set\$")) ?: false) {
"Property annotated with @EnumFlag has to be of type kotlin.collection.Set but was of type ${setType.declaration.qualifiedName?.asString()}"
}
val enumDeclaration = setType.arguments.first().type?.resolve()?.declaration
require(enumDeclaration is KSClassDeclaration) {
"Type parameter of EnumFlag set has to be a class declaration"
}
require(enumDeclaration.classKind == ClassKind.ENUM_CLASS) {
"Content type of enumFlag set has to be of class kind ENUM but was class kind ${enumDeclaration.classKind}. Property: ${parentDeclaration.qualifiedName}"
}
val enumValueNames = enumDeclaration
.declarations
.filterIsInstance()
.filter { it.classKind == ClassKind.ENUM_ENTRY }
.map { it.simpleName.asString() }
.toList()
EnumFlagHintStringAnnotation(enumValueNames = enumValueNames, source = this)
}
IntFlag::class.qualifiedName -> IntFlagHintAnnotation(
@Suppress("UNCHECKED_CAST")
(arguments.firstOrNull()?.value as? ArrayList)?.toList() ?: emptyList(),
this
)
MultilineText::class.qualifiedName -> MultilineTextHintAnnotation(this)
PlaceHolderText::class.qualifiedName -> PlaceHolderTextHintAnnotation(this)
ColorNoAlpha::class.qualifiedName -> ColorNoAlphaHintAnnotation(this)
godot.annotation.IntRange::class.qualifiedName -> provideRangeHintAnnotation(-1)
godot.annotation.LongRange::class.qualifiedName -> provideRangeHintAnnotation(-1L)
godot.annotation.FloatRange::class.qualifiedName -> provideRangeHintAnnotation(-1f)
godot.annotation.DoubleRange::class.qualifiedName -> provideRangeHintAnnotation(-1.0)
EnumTypeHint::class.qualifiedName -> null
ExpEasing::class.qualifiedName -> {
val attenuation = ((arguments.firstOrNull { it.name?.asString() == "attenuation" }?.value ?: arguments.firstOrNull()?.value) as? Boolean) ?: false
val isPositiveOnly = ((arguments.firstOrNull { it.name?.asString() == "isPositiveOnly" }?.value ?: arguments[1].value) as? Boolean) ?: false
ExpEasingHintAnnotation(
attenuation = attenuation,
isPositiveOnly = isPositiveOnly,
source = this
)
}
godot.annotation.File::class.qualifiedName -> {
@Suppress("UNCHECKED_CAST")
val extensions = ((arguments.firstOrNull { it.name?.asString() == "extensions" }?.value ?: arguments.firstOrNull()?.value) as? ArrayList)?.toList() ?: emptyList()
val global = ((arguments.firstOrNull { it.name?.asString() == "global" }?.value ?: arguments[1].value) as? Boolean) ?: false
FileHintAnnotation(
extensions = extensions,
global = global,
source = this
)
}
Dir::class.qualifiedName -> {
val global = ((arguments.firstOrNull { it.name?.asString() == "global" }?.value ?: arguments.firstOrNull()?.value) as? Boolean) ?: false
DirHintAnnotation(
global = global,
source = this
)
}
else -> null
}
}
@Suppress("UNCHECKED_CAST")
private fun KSAnnotation.provideRangeHintAnnotation(stepDefault: T): RangeHintAnnotation {
val start = (arguments.firstOrNull { it.name?.asString() == "start" }?.value ?: arguments.first().value) as T
val end = (arguments.firstOrNull { it.name?.asString() == "end" }?.value ?: arguments[1].value) as T
val step = ((arguments.firstOrNull { it.name?.asString() == "step" }?.value ?: arguments[2].value) as? T) ?: stepDefault
val or = rangeEnum
val hideSlider = (arguments.firstOrNull { it.name?.asString() == "hideSlider" }?.value ?: arguments[4].value) as Boolean
val isRadians = (arguments.firstOrNull { it.name?.asString() == "isRadians" }?.value ?: arguments[5].value) as Boolean
val isDegrees = (arguments.firstOrNull { it.name?.asString() == "isDegrees" }?.value ?: arguments[6].value) as Boolean
val isExp = (arguments.firstOrNull { it.name?.asString() == "isExp" }?.value ?: arguments[7].value) as Boolean
val suffix = ((arguments.firstOrNull { it.name?.asString() == "suffix" }?.value ?: arguments[8].value) as String).let { suffix ->
if (suffix == "") {
null
} else {
suffix
}
}
return RangeHintAnnotation(
start = start,
end = end,
step = step,
or = or,
hideSlider = hideSlider,
isRadians = isRadians,
isDegrees = isDegrees,
isExp = isExp,
suffix = suffix,
symbolProcessorSource = this
)
}