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

org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirExplicitApiDeclarationChecker.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2021 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.syntax

import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.KtRealSourceElementKind
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.ExplicitApiMode
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.getChild
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirPrimaryConstructor
import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility
import org.jetbrains.kotlin.fir.declarations.utils.isData
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.declarations.utils.visibility
import org.jetbrains.kotlin.fir.resolve.transformers.publishedApiEffectiveVisibility
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtDeclaration

object FirExplicitApiDeclarationChecker : FirDeclarationSyntaxChecker() {
    private val codeFragmentTypes =
        setOf(KtNodeTypes.BLOCK_CODE_FRAGMENT, KtNodeTypes.EXPRESSION_CODE_FRAGMENT, KtNodeTypes.TYPE_CODE_FRAGMENT)

    override fun checkPsiOrLightTree(
        element: FirDeclaration,
        source: KtSourceElement,
        context: CheckerContext,
        reporter: DiagnosticReporter
    ) {
        if ((source.kind !is KtRealSourceElementKind && source.kind != KtFakeSourceElementKind.PropertyFromParameter) ||
            element !is FirMemberDeclaration
        ) {
            return
        }
        val state = context.languageVersionSettings.getFlag(AnalysisFlags.explicitApiMode)
        if (state == ExplicitApiMode.DISABLED) return
        // Enum entries do not have visibilities
        if (element is FirEnumEntry) return
        if (!element.effectiveVisibility.publicApi && element.publishedApiEffectiveVisibility == null) return
        val lastDeclaration = context.containingDeclarations.lastOrNull()
        if ((lastDeclaration as? FirMemberDeclaration)?.effectiveVisibility?.publicApi == false) {
            return
        }

        checkVisibilityModifier(state, element, source, context, reporter)
        checkExplicitReturnType(state, element, source, context, reporter)
    }

    private fun checkVisibilityModifier(
        state: ExplicitApiMode,
        declaration: FirMemberDeclaration,
        source: KtSourceElement,
        context: CheckerContext,
        reporter: DiagnosticReporter
    ) {
        val visibilityModifier = source.getChild(KtNodeTypes.MODIFIER_LIST)?.getChild(KtTokens.VISIBILITY_MODIFIERS)
        if (visibilityModifier != null) return

        if (explicitVisibilityIsNotRequired(declaration, context)) return
        val factory = if (state == ExplicitApiMode.STRICT)
            FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE
        else
            FirErrors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING
        reporter.reportOn(source, factory, context)
    }

    /**
     * Exclusion list:
     * 1. Primary constructors of public API classes
     * 2. Properties of data classes in public API
     * 3. Overrides of public API. Effectively, this means 'no report on overrides at all'
     * 4. Getters and setters (because getters can't change visibility and setter-only explicit visibility looks ugly)
     * 5. Properties of annotations in public API
     * 6. Value parameter declaration
     * 7. An anonymous function
     * 8. A local named function
     *
     * TODO: Do we need something like @PublicApiFile to disable (or invert) this inspection per-file?
     */
    private fun explicitVisibilityIsNotRequired(declaration: FirMemberDeclaration, context: CheckerContext): Boolean {
        return when (declaration) {
            is FirPrimaryConstructor, // 1,
            is FirPropertyAccessor, // 4
            is FirValueParameter, // 6
            is FirAnonymousFunction -> true // 7
            is FirCallableDeclaration -> {
                val containingClass = context.containingDeclarations.lastOrNull() as? FirRegularClass
                // 2, 5
                if (declaration is FirProperty &&
                    containingClass != null &&
                    (containingClass.isData || containingClass.classKind == ClassKind.ANNOTATION_CLASS)
                ) {
                    return true
                }

                // 3, 8
                declaration.isOverride || declaration.isLocalMember
            }
            else -> false
        }
    }

    private fun checkExplicitReturnType(
        state: ExplicitApiMode,
        declaration: FirMemberDeclaration,
        source: KtSourceElement,
        context: CheckerContext,
        reporter: DiagnosticReporter
    ) {
        if (declaration !is FirCallableDeclaration) return
        if (!returnTypeCheckIsApplicable(source, context)) return

        val shouldReport = returnTypeRequired(declaration, context)
        if (shouldReport) {
            val factory =
                if (state == ExplicitApiMode.STRICT)
                    FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE
                else
                    FirErrors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING
            reporter.reportOn(source, factory, context)
        }
    }

    private fun returnTypeCheckIsApplicable(source: KtSourceElement, context: CheckerContext): Boolean {
        // Note that by default getChild uses `depth = -1`, which would find all descendents.
        if (source.getChild(KtNodeTypes.TYPE_REFERENCE, depth = 1) != null) return false
        // Do not check if the containing file is not a physical file.
        val containingFile = context.containingDeclarations.first()
        if (containingFile.source?.elementType in codeFragmentTypes) return false

        return when (source.elementType) {
            // Only require return type if the function is defined via `=`. If it has a block body or is abstract, we don't require return
            // type because not declaring means it returns `Unit`.
            KtNodeTypes.FUN -> source.getChild(KtTokens.EQ, depth = 1) != null
            KtNodeTypes.PROPERTY -> true
            else -> false
        }
    }

    private fun returnTypeRequired(declaration: FirCallableDeclaration, context: CheckerContext): Boolean {
        // If current declaration is local or it's a member in a local declaration (local class, etc), then we do not require return type.
        return !declaration.isLocalMember && context.containingDeclarations.lastOrNull()?.isLocalMember != true
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy