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

org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirAnnotationClassDeclarationChecker.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.fir.analysis.checkers.declaration

import com.intellij.lang.LighterASTNode
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassKind.ANNOTATION_CLASS
import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveArrayTypeByElementType
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveTypes
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.unsignedTypes
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.KtNodeTypes.FUN
import org.jetbrains.kotlin.KtNodeTypes.VALUE_PARAMETER
import org.jetbrains.kotlin.lexer.KtTokens.VAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.VAR_KEYWORD
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtParameter

object FirAnnotationClassDeclarationChecker : FirBasicDeclarationChecker() {
    override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
        if (declaration !is FirRegularClass) return
        if (declaration.classKind != ANNOTATION_CLASS) return
        if (declaration.isLocal) reporter.report(declaration.source, FirErrors.LOCAL_ANNOTATION_CLASS_ERROR)

        for (it in declaration.declarations) {
            when {
                it is FirConstructor && it.isPrimary -> {
                    for (parameter in it.valueParameters) {
                        when (val parameterSourceElement = parameter.source) {
                            is FirPsiSourceElement<*> -> {
                                val parameterPsiElement = parameterSourceElement.psi as KtParameter

                                if (!parameterPsiElement.hasValOrVar())
                                    reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
                                else if (parameterPsiElement.isMutable)
                                    reporter.report(parameterSourceElement, FirErrors.VAR_ANNOTATION_PARAMETER)
                            }
                            is FirLightSourceElement -> {
                                val kidsRef = Ref>()
                                parameterSourceElement.tree.getChildren(parameterSourceElement.element, kidsRef)

                                if (kidsRef.get().any { it?.tokenType == VAR_KEYWORD })
                                    reporter.report(parameterSourceElement, FirErrors.VAR_ANNOTATION_PARAMETER)
                                else if (kidsRef.get().all { it?.tokenType != VAL_KEYWORD })
                                    reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
                            }
                        }

                        val typeRef = parameter.returnTypeRef
                        val coneType = typeRef.coneTypeSafe()
                        val classId = coneType?.classId

                        if (coneType != null) when {
                            classId == ClassId.fromString("") -> {
                                // TODO: replace with UNRESOLVED_REFERENCE check
                            }
                            coneType.isNullable -> {
                                reporter.report(typeRef.source, FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER)
                            }
                            classId in primitiveTypes -> {
                                // DO NOTHING: primitives are allowed as annotation class parameter
                            }
                            classId in unsignedTypes -> {
                                // TODO: replace with EXPERIMENTAL_UNSIGNED_LITERALS check
                            }
                            classId == StandardClassIds.KClass -> {
                                // DO NOTHING: KClass is allowed
                            }
                            classId == StandardClassIds.String -> {
                                // DO NOTHING: String is allowed
                            }
                            classId in primitiveArrayTypeByElementType.values -> {
                                // DO NOTHING: primitive arrays are allowed
                            }
                            classId == StandardClassIds.Array -> {
                                if (!isAllowedArray(typeRef, context.session))
                                    reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
                            }
                            isAllowedClassKind(coneType, context.session) -> {
                                // DO NOTHING: annotation or enum classes are allowed
                            }
                            else -> {
                                reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
                            }
                        }
                    }
                }
                it is FirRegularClass -> {
                    // DO NOTHING: nested annotation classes are allowed in 1.3+
                }
                it is FirProperty && it.source?.elementType == VALUE_PARAMETER -> {
                    // DO NOTHING to avoid reporting constructor properties
                }
                it is FirSimpleFunction && it.source?.elementType != FUN -> {
                    // DO NOTHING to avoid reporting synthetic functions
                    // TODO: replace with origin check
                }
                else -> {
                    reporter.report(it.source, FirErrors.ANNOTATION_CLASS_MEMBER)
                }
            }
        }
    }

    private fun isAllowedClassKind(cone: ConeLookupTagBasedType, session: FirSession): Boolean {
        val typeRefClassKind = (cone.lookupTag.toSymbol(session)
            ?.fir as? FirRegularClass)
            ?.classKind
            ?: return false

        return typeRefClassKind == ANNOTATION_CLASS || typeRefClassKind == ENUM_CLASS
    }

    private fun isAllowedArray(typeRef: FirTypeRef, session: FirSession): Boolean {
        val typeArguments = typeRef.coneType.typeArguments

        if (typeArguments.size != 1) return false

        val arrayType = (typeArguments[0] as? ConeKotlinTypeProjection)
            ?.type
            ?: return false

        if (arrayType.isNullable) return false

        val arrayTypeClassId = arrayType.classId

        when {
            arrayTypeClassId == StandardClassIds.KClass -> {
                // KClass is allowed
                return true
            }
            arrayTypeClassId == StandardClassIds.String -> {
                // String is allowed
                return true
            }
            isAllowedClassKind(arrayType as ConeLookupTagBasedType, session) -> {
                // annotation or enum classes are allowed
                return true
            }
        }

        return false
    }

    private inline fun  DiagnosticReporter.report(
        source: T?,
        factory: FirDiagnosticFactory0
    ) {
        source?.let { report(factory.on(it)) }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy