All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.anaplan.engineering.kazuki.ksp.KazukiSymbolProcessor.kt Maven / Gradle / Ivy

There is a newer version: 0.0.1-alpha.13
Show newest version
package com.anaplan.engineering.kazuki.ksp

import com.anaplan.engineering.kazuki.core.Module
import com.anaplan.engineering.kazuki.core.PrimitiveInvariant
import com.anaplan.engineering.kazuki.ksp.KazukiSymbolProcessor.KazukiLogger
import com.anaplan.engineering.kazuki.ksp.type.PrimitiveTypeProcessor
import com.anaplan.engineering.kazuki.ksp.type.TypeGenerationContext
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSNode
import com.google.devtools.ksp.validate

@OptIn(KspExperimental::class)
class KazukiSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {

    // TODO - auto generate tests for equals, hashcode, toString? -- maybe do with opt-in property
    private val processingState = KazukiProcessingState(environment)

    override fun process(resolver: Resolver): List {
        val allModules =
            resolver.getSymbolsWithAnnotation(Module::class.qualifiedName.orEmpty())
                .filterIsInstance().groupBy { it.validate() }

        val typeGenerationContext = TypeGenerationContext(processingState, resolver)
        val primitiveTypeProcessor = PrimitiveTypeProcessor(typeGenerationContext, environment.codeGenerator)
        initializePrimitiveInvariants(resolver, primitiveTypeProcessor)

        val moduleProcessor = ModuleProcessor(typeGenerationContext, environment.codeGenerator)
        allModules[true]?.forEach { moduleProcessor.generateImplementation(it) }

        if (processingState.hasErrors()) {
            processingState.errors.forEach {
                environment.logger.error(it)
            }
            return emptyList()
        }
        return allModules[false] ?: emptyList()
    }

    class KazukiLogger(private val environment: SymbolProcessorEnvironment): KSPLogger by environment.logger {

        enum class Level {
            DEBUG, INFO, WARN, ERROR
        }

        companion object {
            const val DebugLevelPropertyName = "com.anaplan.engineering.kazuki.compile.debug.level"
        }

        // Enables redirection of Kazuki debug logging so that we can view this info without enabling compiler debug
        // logging more widely
        private val debugRedirect by lazy {
            val property = environment.options[DebugLevelPropertyName] ?: Level.DEBUG.name
            val level = Level.valueOf(property.uppercase())
            environment.logger.info("Kazuki debug level logging will be redirected to $level")
            when (level) {
                Level.DEBUG -> { m: String, s: KSNode? -> logging(m, s) }
                Level.INFO -> { m: String, s: KSNode? -> info(m, s) }
                Level.WARN -> { m: String, s: KSNode? -> warn(m, s) }
                Level.ERROR -> { m: String, s: KSNode? -> error(m, s) }
            }
        }

        fun debug(message: String, symbol: KSNode? = null) = debugRedirect(message, symbol)
    }

    private class KazukiProcessingState(environment: SymbolProcessorEnvironment): ProcessingState {
        override val primitiveInvariants: MutableMap = mutableMapOf()
        override val errors: MutableList = mutableListOf()
        override val logger = KazukiLogger(environment)
    }

    // TOOD - load from libs
    private fun initializePrimitiveInvariants(resolver: Resolver, primitiveTypeProcessor: PrimitiveTypeProcessor) =
        processingState.primitiveInvariants.putAll(
            resolver.getSymbolsWithAnnotation(PrimitiveInvariant::class.qualifiedName.orEmpty())
                .filterIsInstance()
                .filter(KSNode::validate).associateBy {
                    val primitiveTypeAlias = primitiveTypeProcessor.processPrimitiveType(it)
                    "${it.packageName.asString()}.${primitiveTypeAlias.name}"
                }
        )


}

class KazukiSymbolProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment) =
        KazukiSymbolProcessor(environment)

}

interface ProcessingState {
    val primitiveInvariants: MutableMap
    val errors: MutableList
    val logger: KazukiLogger

    fun hasErrors() = errors.isNotEmpty()
}

internal const val InvalidInternalStateType = "Internal state should not be a Kazuki-generated object"




© 2015 - 2025 Weber Informatics LLC | Privacy Policy