org.jetbrains.kotlin.resolve.checkers.ExplicitApiDeclarationChecker.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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.resolve.checkers
import org.jetbrains.kotlin.config.AnalysisFlag
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.ExplicitApiMode
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
import org.jetbrains.kotlin.resolve.descriptorUtil.isPublishedApi
class ExplicitApiDeclarationChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
fun extractState(flag: AnalysisFlag): ExplicitApiMode? {
return context.languageVersionSettings.getFlag(flag).takeUnless { it == ExplicitApiMode.DISABLED }
}
val explicitApiState = extractState(AnalysisFlags.explicitApiMode)
val explicitReturnTypesState = extractState(AnalysisFlags.explicitReturnTypes)
if (explicitApiState == null && explicitReturnTypesState == null) return
if (descriptor !is DeclarationDescriptorWithVisibility) return
if (descriptor is ClassDescriptor && descriptor.kind == ClassKind.ENUM_ENTRY) return // Enum entries does not have visibilities
if (!descriptor.isEffectivelyPublicApi && !descriptor.isPublishedApi()) return
if (explicitApiState != null) {
checkVisibilityModifier(explicitApiState, declaration, descriptor, context)
}
checkExplicitReturnType(explicitApiState ?: explicitReturnTypesState!!, declaration, descriptor, context)
}
private fun checkVisibilityModifier(
state: ExplicitApiMode,
declaration: KtDeclaration,
descriptor: DeclarationDescriptorWithVisibility,
context: DeclarationCheckerContext
) {
val modifier = declaration.visibilityModifier()
if (modifier != null) return
if (explicitVisibilityIsNotRequired(descriptor)) return
val diagnostic =
if (state == ExplicitApiMode.STRICT)
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE
else
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING
context.trace.reportDiagnosticOnce(diagnostic.on(declaration))
}
private fun checkExplicitReturnType(
state: ExplicitApiMode,
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
context: DeclarationCheckerContext
) {
if (declaration !is KtCallableDeclaration) return
if (!returnTypeCheckIsApplicable(declaration)) return
val shouldReport = returnTypeRequired(
declaration, descriptor,
checkForPublicApi = true,
checkForInternal = false,
checkForPrivate = false
)
if (shouldReport) {
val diagnostic =
if (state == ExplicitApiMode.STRICT)
Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE
else
Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING
context.trace.reportDiagnosticOnce(diagnostic.on(declaration))
}
}
companion object {
/**
* 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
*
* Do we need something like @PublicApiFile to disable (or invert) this inspection per-file?
*/
fun explicitVisibilityIsNotRequired(descriptor: DeclarationDescriptor): Boolean {
/* 1. */ if ((descriptor as? ClassConstructorDescriptor)?.isPrimary == true) return true
/* 2. */ if (descriptor is PropertyDescriptor && (descriptor.containingDeclaration as? ClassDescriptor)?.isData == true) return true
/* 3. */ if ((descriptor as? CallableDescriptor)?.overriddenDescriptors?.isNotEmpty() == true) return true
/* 4. */ if (descriptor is PropertyAccessorDescriptor) return true
/* 5. */ if (descriptor is PropertyDescriptor && (descriptor.containingDeclaration as? ClassDescriptor)?.kind == ClassKind.ANNOTATION_CLASS) return true
return false
}
fun returnTypeRequired(
element: KtCallableDeclaration,
descriptor: DeclarationDescriptor?,
checkForPublicApi: Boolean,
checkForInternal: Boolean,
checkForPrivate: Boolean
): Boolean {
if (element.containingClassOrObject?.isLocal == true) return false
if (element is KtFunction && element.isLocal) return false
if (element is KtProperty && element.isLocal) return false
val callableMemberDescriptor = descriptor as? CallableMemberDescriptor
val visibility = callableMemberDescriptor?.effectiveVisibility()?.toVisibility()
val isPublicApi =
visibility?.isPublicAPI == true || (visibility == Visibilities.Internal && callableMemberDescriptor.isPublishedApi())
return (checkForPublicApi && isPublicApi) || (checkForInternal && visibility == Visibilities.Internal) ||
(checkForPrivate && visibility == Visibilities.Internal)
}
fun returnTypeCheckIsApplicable(element: KtCallableDeclaration): Boolean {
if (element.containingFile is KtCodeFragment) return false
if (element is KtFunctionLiteral) return false // TODO(Mikhail Glukhikh): should KtFunctionLiteral be KtCallableDeclaration at all?
if (element is KtConstructor<*>) return false
if (element.typeReference != null) return false
if (element is KtNamedFunction && element.hasBlockBody()) return false
return true
}
fun publicReturnTypeShouldBePresentInApiMode(
element: KtCallableDeclaration,
languageVersionSettings: LanguageVersionSettings,
descriptor: DeclarationDescriptor?
): Boolean {
val isInApiMode = languageVersionSettings.getFlag(AnalysisFlags.explicitApiMode) != ExplicitApiMode.DISABLED
return isInApiMode && returnTypeRequired(
element,
descriptor,
checkForPublicApi = true,
checkForInternal = false,
checkForPrivate = false
)
}
}
}
val LanguageVersionSettings.explicitApiEnabled: Boolean
get() = getFlag(AnalysisFlags.explicitApiMode) != ExplicitApiMode.DISABLED