Add explicit return type check for API mode

It will use the same SpecifyTypeExplicitlyIntention as in other places; to reuse check logic, some parts of code were moved from corresponding inspection (PublicApiImplicitTypeInspection) into ApiModeDeclarationChecker.

Also disable RedundantVisibilityModifierInspection when API mode is on.
This commit is contained in:
Leonid Startsev
2019-09-09 20:19:32 +03:00
parent 7058492b55
commit 2b708093c0
7 changed files with 111 additions and 33 deletions
@@ -208,7 +208,9 @@ public interface Errors {
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, KtModifierKeywordToken> DEPRECATED_MODIFIER = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> REDUNDANT_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING);
DiagnosticFactory0<PsiElement> NO_EXPLICIT_VISIBILITY_IN_API_MODE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NO_EXPLICIT_RETURN_TYPE_IN_API_MODE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION = DiagnosticFactory0.create(WARNING);
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> WRONG_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> DEPRECATED_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(WARNING);
DiagnosticFactory1<PsiElement, KtModifierKeywordToken> ILLEGAL_INLINE_PARAMETER_MODIFIER = DiagnosticFactory1.create(ERROR);
@@ -120,7 +120,9 @@ public class DefaultErrorMessages {
MAP.put(DEPRECATED_MODIFIER, "Modifier ''{0}'' is deprecated, use ''{1}'' instead", TO_STRING, TO_STRING);
MAP.put(REDUNDANT_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is redundant for ''{1}''", TO_STRING, TO_STRING);
MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE, "This declaration is effectively public API and API mode is on, but no visibility is specified");
MAP.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE, "This declaration is effectively public API and API mode is on, but no explicit return type is specified");
MAP.put(NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION, "This declaration is effectively public API and API mode is on, but no visibility is specified");
MAP.put(NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION, "This declaration is effectively public API and API mode is on, but no explicit return type is specified");
MAP.put(WRONG_MODIFIER_CONTAINING_DECLARATION, "Modifier ''{0}'' is not applicable inside ''{1}''", TO_STRING, TO_STRING);
MAP.put(DEPRECATED_MODIFIER_CONTAINING_DECLARATION, "Modifier ''{0}'' is deprecated inside ''{1}''", TO_STRING, TO_STRING);
MAP.put(ILLEGAL_INLINE_PARAMETER_MODIFIER, "Modifier ''{0}'' is allowed only for function parameters of an inline function", TO_STRING);
@@ -5,14 +5,17 @@
package org.jetbrains.kotlin.resolve.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.ApiMode
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
@@ -24,16 +27,64 @@ class ApiModeDeclarationChecker : DeclarationChecker {
val isApi = (descriptor as? DeclarationDescriptorWithVisibility)?.isEffectivelyPublicApi ?: return
if (!isApi) return
checkVisibilityModifier(state, declaration, descriptor, context)
checkExplicitReturnType(state, declaration, descriptor, context)
}
private fun checkVisibilityModifier(
state: ApiMode,
declaration: KtDeclaration,
descriptor: DeclarationDescriptor,
context: DeclarationCheckerContext
) {
val modifier = declaration.visibilityModifier()?.node?.elementType as? KtModifierKeywordToken
if (modifier != null) return
if (excludeForDiagnostic(descriptor)) return
context.selectDiagnosticAndReport(
declaration,
state,
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE,
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION
)
}
private fun checkExplicitReturnType(
state: ApiMode,
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) context.selectDiagnosticAndReport(
declaration,
state,
Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE,
Errors.NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_MIGRATION
)
}
private fun DeclarationCheckerContext.selectDiagnosticAndReport(
on: KtDeclaration,
state: ApiMode,
ifError: DiagnosticFactory0<PsiElement>,
ifWarning: DiagnosticFactory0<PsiElement>
) {
val diagnostic =
if (state == ApiMode.ENABLED)
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE.on(declaration)
ifError.on(on)
else
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION.on(declaration)
context.trace.reportDiagnosticOnce(diagnostic)
ifWarning.on(on)
trace.reportDiagnosticOnce(diagnostic)
}
/**
@@ -57,5 +108,36 @@ class ApiModeDeclarationChecker : DeclarationChecker {
fun isEnabled(settings: LanguageVersionSettings): ApiMode {
return settings.getFlag(AnalysisFlags.apiMode)
}
fun returnTypeRequired(
element: KtCallableDeclaration,
descriptor: DeclarationDescriptor?,
checkForPublicApi: Boolean,
checkForInternal: Boolean,
checkForPrivate: Boolean
): Boolean =
element.containingClassOrObject?.isLocal != true &&
when (element) {
is KtFunction -> !element.isLocal
is KtProperty -> !element.isLocal
else -> false
} && run {
val callableMemberDescriptor = descriptor as? CallableMemberDescriptor
val visibility = callableMemberDescriptor?.effectiveVisibility()?.toVisibility()
(checkForPublicApi && visibility?.isPublicAPI == true) || (checkForInternal && visibility == Visibilities.INTERNAL) ||
(checkForPrivate && visibility == Visibilities.PRIVATE)
}
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
}
}
}