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

io.github.gmazzo.codeowners.compiler.CodeOwnersIrTransformer.kt Maven / Gradle / Ivy

The newest version!
@file:OptIn(UnsafeDuringIrConstructionAPI::class)

package io.github.gmazzo.codeowners.compiler

import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.jvm.ir.getIoFile
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.nameWithoutExtension
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrMutableAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.createBlockBody
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.addChild
import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.ir.util.fileOrNull
import org.jetbrains.kotlin.ir.util.primaryConstructor
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.platform.konan.isNative

internal class CodeOwnersIrTransformer(
    private val context: IrPluginContext,
    private val mappings: CodeOwnersMappings,
) : IrElementTransformer> {

    private val requiresProvider = context.platform?.let { it.isJs() || it.isNative() } == true

    private val stringArray = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType)

    private val annotationCodeOwners by lazy {
        context.referenceClass(ClassId.fromString("io/github/gmazzo/codeowners/CodeOwners"))!!
    }

    private val annotationCodeOwnersProvider by lazy {
        context.referenceClass(ClassId.fromString("io/github/gmazzo/codeowners/CodeOwnersProviderKey"))!!
    }

    private val classCodeOwnersProvider by lazy {
        context.referenceClass(ClassId.fromString("io/github/gmazzo/codeowners/CodeOwnersProvider"))!!
    }

    private var fileCodeOwnersProvider: IrClassSymbol? = null

    override fun visitFile(declaration: IrFile, data: Set) = declaration.apply {
        val owners = declaration.getIoFile()?.let(mappings::resolve)?.owners ?: return@apply

        addAnnotation(owners)
        super.visitFile(declaration, owners)

        fileCodeOwnersProvider = null
    }

    override fun visitClass(declaration: IrClass, data: Set) = declaration.apply {
        // we only decorate top level classes
        if (parent !is IrFile) return@apply

        val ownersValue = addAnnotation(data)

        if (requiresProvider) {
            addCodeOwnersProviderAnnotation(ownersValue)
        }

        super.visitClass(declaration, data)
    }

    private fun IrMutableAnnotationContainer.addAnnotation(owners: Set): IrVararg {
        val ownersValue = IrVarargImpl(
            UNDEFINED_OFFSET,
            UNDEFINED_OFFSET,
            stringArray,
            context.irBuiltIns.stringType,
            owners.map { value ->
                IrConstImpl.string(
                    UNDEFINED_OFFSET,
                    UNDEFINED_OFFSET,
                    context.irBuiltIns.stringType,
                    value,
                )
            },
        )

        annotations += IrConstructorCallImpl.fromSymbolOwner(
            SYNTHETIC_OFFSET,
            SYNTHETIC_OFFSET,
            annotationCodeOwners.defaultType,
            annotationCodeOwners.owner.primaryConstructor!!.symbol,
        ).apply {
            putValueArgument(0, ownersValue)
        }
        return ownersValue
    }

    private fun IrFile.addCodeOwnersProvider(className: String, owners: IrVararg) = context.irFactory.buildClass {
        startOffset = SYNTHETIC_OFFSET
        endOffset = SYNTHETIC_OFFSET
        name = Name.identifier(className)
        kind = ClassKind.OBJECT
        visibility = DescriptorVisibilities.INTERNAL
    }.apply {
        [email protected](this)

        superTypes += classCodeOwnersProvider.defaultType
        createImplicitParameterDeclarationWithWrappedDescriptor()
        addConstructor {
            isPrimary = true
            visibility = DescriptorVisibilities.INTERNAL
        }.body = context.irFactory.createBlockBody(
            UNDEFINED_OFFSET,
            UNDEFINED_OFFSET,
            listOfNotNull(
                IrDelegatingConstructorCallImpl.fromSymbolOwner(
                    UNDEFINED_OFFSET,
                    UNDEFINED_OFFSET,
                    context.irBuiltIns.unitType,
                    classCodeOwnersProvider.owner.primaryConstructor!!.symbol
                ).apply {
                    putValueArgument(0, owners)
                },
                IrInstanceInitializerCallImpl(
                    UNDEFINED_OFFSET,
                    UNDEFINED_OFFSET,
                    context.irBuiltIns.anyClass,
                    context.irBuiltIns.unitType
                )
            )
        )
    }.symbol

    private fun IrClass.addCodeOwnersProviderAnnotation(ownersValue: IrVararg) {
        if (fileCodeOwnersProvider == null) {
            val file = fileOrNull ?: return

            fileCodeOwnersProvider = file.addCodeOwnersProvider("${file.nameWithoutExtension}\$CODEOWNERS", ownersValue)
        }

        annotations += IrConstructorCallImpl.fromSymbolOwner(
            UNDEFINED_OFFSET,
            UNDEFINED_OFFSET,
            annotationCodeOwnersProvider.defaultType,
            annotationCodeOwnersProvider.owner.primaryConstructor!!.symbol,
        ).apply {
            val starType = fileCodeOwnersProvider!!.starProjectedType

            putValueArgument(
                0, IrClassReferenceImpl(
                    UNDEFINED_OFFSET,
                    UNDEFINED_OFFSET,
                    context.irBuiltIns.kClassClass.starProjectedType,
                    starType.classifierOrFail,
                    starType,
                )
            )
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy