Explicit Api mode: prototype with check for missing explicit visibility

Add cli flag with 3-state switch
This commit is contained in:
Leonid Startsev
2019-05-24 15:18:23 +03:00
parent a7d60fbf2d
commit 7058492b55
10 changed files with 109 additions and 0 deletions
@@ -27,6 +27,9 @@ object AnalysisFlags {
@JvmStatic
val allowResultReturnType by AnalysisFlag.Delegates.Boolean
@JvmStatic
val apiMode by AnalysisFlag.Delegates.ApiModeDisabledByDefault
@JvmStatic
val constraintSystemForOverloadResolution by AnalysisFlag.Delegates.ConstraintSystemForOverloadResolution
@@ -207,6 +207,8 @@ public interface Errors {
DiagnosticFactory2<PsiElement, KtModifierKeywordToken, String> DEPRECATED_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING);
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_VISIBILITY_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);
@@ -119,6 +119,8 @@ public class DefaultErrorMessages {
MAP.put(DEPRECATED_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is deprecated for ''{1}''", TO_STRING, TO_STRING);
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_VISIBILITY_IN_API_MODE_MIGRATION, "This declaration is effectively public API and API mode is on, but no visibility 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);
@@ -32,6 +32,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
ReservedMembersAndConstructsForInlineClass(),
ResultClassInReturnTypeChecker(),
LocalVariableTypeParametersChecker(),
ApiModeDeclarationChecker(),
TailrecFunctionChecker,
TrailingCommaDeclarationChecker
)
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2019 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.AnalysisFlags
import org.jetbrains.kotlin.config.ApiMode
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.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
class ApiModeDeclarationChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val state = isEnabled(context.languageVersionSettings)
if (state == ApiMode.DISABLED) return
val isApi = (descriptor as? DeclarationDescriptorWithVisibility)?.isEffectivelyPublicApi ?: return
if (!isApi) return
val modifier = declaration.visibilityModifier()?.node?.elementType as? KtModifierKeywordToken
if (modifier != null) return
if (excludeForDiagnostic(descriptor)) return
val diagnostic =
if (state == ApiMode.ENABLED)
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE.on(declaration)
else
Errors.NO_EXPLICIT_VISIBILITY_IN_API_MODE_MIGRATION.on(declaration)
context.trace.reportDiagnosticOnce(diagnostic)
}
/**
* Exclusion list:
* 1. Primary constructors of public API classes
* 3. Members of public API interfaces
* 4. do not report overrides of public API? effectively, this means 'no report on overrides at all'
*
* Do we need something like @PublicApiFile to disable (or invert) this inspection per-file?
*/
private fun excludeForDiagnostic(descriptor: DeclarationDescriptor): Boolean {
/* 1. */ if ((descriptor as? ClassConstructorDescriptor)?.isPrimary == true) return true
val isMemberOfPublicInterface =
(descriptor.containingDeclaration as? ClassDescriptor)?.let { DescriptorUtils.isInterface(it) && it.effectiveVisibility().publicApi }
/* 3. */ if (descriptor is CallableDescriptor && isMemberOfPublicInterface == true) return true
/* 4. */ if ((descriptor as? CallableDescriptor)?.overriddenDescriptors?.isNotEmpty() == true) return true
return false
}
companion object {
fun isEnabled(settings: LanguageVersionSettings): ApiMode {
return settings.getFlag(AnalysisFlags.apiMode)
}
}
}