Support custom message in RequiresOptIn
#KT-34648 Fixed
This commit is contained in:
@@ -255,11 +255,11 @@ public interface Errors {
|
||||
DiagnosticFactory1<PsiElement, FqName> ILLEGAL_KOTLIN_VERSION_STRING_VALUE = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> NEWER_VERSION_IN_SINCE_KOTLIN = DiagnosticFactory1.create(WARNING);
|
||||
|
||||
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_API_USAGE = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_API_USAGE_ERROR = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_API_USAGE = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_API_USAGE_ERROR = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory2<PsiElement, FqName, DeclarationDescriptor> EXPERIMENTAL_OVERRIDE = DiagnosticFactory2.create(WARNING);
|
||||
DiagnosticFactory2<PsiElement, FqName, DeclarationDescriptor> EXPERIMENTAL_OVERRIDE_ERROR = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_OVERRIDE = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_OVERRIDE_ERROR = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<PsiElement> EXPERIMENTAL_IS_NOT_ENABLED = DiagnosticFactory0.create(WARNING);
|
||||
DiagnosticFactory0<PsiElement> EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION = DiagnosticFactory0.create(ERROR);
|
||||
@@ -270,8 +270,8 @@ public interface Errors {
|
||||
DiagnosticFactory1<KtAnnotationEntry, FqName> USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<KtAnnotationEntry, String> EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING);
|
||||
DiagnosticFactory1<PsiElement, String> EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<PsiElement> NON_PARENTHESIZED_ANNOTATIONS_ON_FUNCTIONAL_TYPES = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
|
||||
+6
-6
@@ -147,11 +147,11 @@ public class DefaultErrorMessages {
|
||||
MAP.put(ILLEGAL_KOTLIN_VERSION_STRING_VALUE, "Invalid @{0} annotation value (should be ''major.minor'' or ''major.minor.patch'')", TO_STRING);
|
||||
MAP.put(NEWER_VERSION_IN_SINCE_KOTLIN, "The version is greater than the specified API version {0}", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_API_USAGE, "This declaration is experimental and its usage should be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE_ERROR, "This declaration is experimental and its usage must be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE, "{0}", STRING);
|
||||
MAP.put(EXPERIMENTAL_API_USAGE_ERROR, "{0}", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_OVERRIDE, "This declaration overrides experimental member of supertype ''{1}'' and should be annotated with ''@{0}''", TO_STRING, NAME);
|
||||
MAP.put(EXPERIMENTAL_OVERRIDE_ERROR, "This declaration overrides experimental member of supertype ''{1}'' and must be annotated with ''@{0}''", TO_STRING, NAME);
|
||||
MAP.put(EXPERIMENTAL_OVERRIDE, "{0}", STRING);
|
||||
MAP.put(EXPERIMENTAL_OVERRIDE_ERROR, "{0}", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_IS_NOT_ENABLED, "This class can only be used with the compiler argument '-Xopt-in=kotlin.RequiresOptIn'");
|
||||
MAP.put(EXPERIMENTAL_CAN_ONLY_BE_USED_AS_ANNOTATION, "This class can only be used as an annotation");
|
||||
@@ -161,8 +161,8 @@ public class DefaultErrorMessages {
|
||||
MAP.put(USE_EXPERIMENTAL_ARGUMENT_IS_NOT_MARKER, "Annotation ''{0}'' is not an opt-in requirement marker, therefore its usage in @OptIn is ignored", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_ANNOTATION_WITH_WRONG_TARGET, "Opt-in requirement marker annotation cannot be used on the following code elements: {0}. Please remove these targets", STRING);
|
||||
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "Unsigned literals are experimental and their usages should be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "Unsigned literals are experimental and their usages must be marked with ''@{0}'' or ''@OptIn({0}::class)''", TO_STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "{0}", STRING);
|
||||
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "{0}", STRING);
|
||||
|
||||
MAP.put(NON_PARENTHESIZED_ANNOTATIONS_ON_FUNCTIONAL_TYPES, "Non-parenthesized annotations on function types without receiver aren't yet supported (see KT-31734 for details)");
|
||||
|
||||
|
||||
+41
-15
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
import org.jetbrains.kotlin.resolve.constants.EnumValue
|
||||
import org.jetbrains.kotlin.resolve.constants.KClassValue
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
@@ -51,7 +52,7 @@ import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
|
||||
|
||||
data class Experimentality(val annotationFqName: FqName, val severity: Severity) {
|
||||
data class Experimentality(val annotationFqName: FqName, val severity: Severity, val message: String?) {
|
||||
enum class Severity { WARNING, ERROR }
|
||||
|
||||
companion object {
|
||||
@@ -59,9 +60,14 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
}
|
||||
}
|
||||
|
||||
class ExperimentalityDiagnostic(
|
||||
val factory: DiagnosticFactory1<PsiElement, String>,
|
||||
val defaultMessage: (FqName) -> String
|
||||
)
|
||||
|
||||
data class ExperimentalityDiagnostics(
|
||||
val warning: DiagnosticFactory1<PsiElement, FqName>,
|
||||
val error: DiagnosticFactory1<PsiElement, FqName>
|
||||
val warning: ExperimentalityDiagnostic,
|
||||
val error: ExperimentalityDiagnostic
|
||||
)
|
||||
|
||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||
@@ -84,18 +90,30 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
internal val WAS_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
|
||||
|
||||
private val LEVEL = Name.identifier("level")
|
||||
private val MESSAGE = Name.identifier("message")
|
||||
private val WARNING_LEVEL = Name.identifier("WARNING")
|
||||
private val ERROR_LEVEL = Name.identifier("ERROR")
|
||||
|
||||
private val EXPERIMENTAL_API_DIAGNOSTICS = ExperimentalityDiagnostics(
|
||||
Errors.EXPERIMENTAL_API_USAGE, Errors.EXPERIMENTAL_API_USAGE_ERROR
|
||||
internal fun getDefaultDiagnosticMessage(prefix: String) = fun(fqName: FqName): String {
|
||||
return "$prefix with '@${fqName.asString()}' or '@OptIn(${fqName.asString()}::class)'"
|
||||
}
|
||||
|
||||
private val USAGE_DIAGNOSTICS = ExperimentalityDiagnostics(
|
||||
warning = ExperimentalityDiagnostic(
|
||||
Errors.EXPERIMENTAL_API_USAGE,
|
||||
getDefaultDiagnosticMessage("This declaration is experimental and its usage should be marked")
|
||||
),
|
||||
error = ExperimentalityDiagnostic(
|
||||
Errors.EXPERIMENTAL_API_USAGE_ERROR,
|
||||
getDefaultDiagnosticMessage("This declaration is experimental and its usage must be marked")
|
||||
)
|
||||
)
|
||||
|
||||
fun reportNotAcceptedExperimentalities(
|
||||
experimentalities: Collection<Experimentality>, element: PsiElement, context: CheckerContext
|
||||
) {
|
||||
reportNotAcceptedExperimentalities(
|
||||
experimentalities, element, context.languageVersionSettings, context.trace, EXPERIMENTAL_API_DIAGNOSTICS
|
||||
experimentalities, element, context.languageVersionSettings, context.trace, USAGE_DIAGNOSTICS
|
||||
)
|
||||
}
|
||||
|
||||
@@ -106,13 +124,13 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
trace: BindingTrace,
|
||||
diagnostics: ExperimentalityDiagnostics
|
||||
) {
|
||||
for ((annotationFqName, severity) in experimentalities) {
|
||||
for ((annotationFqName, severity, message) in experimentalities) {
|
||||
if (!element.isExperimentalityAccepted(annotationFqName, languageVersionSettings, trace.bindingContext)) {
|
||||
val diagnostic = when (severity) {
|
||||
Experimentality.Severity.WARNING -> diagnostics.warning
|
||||
Experimentality.Severity.ERROR -> diagnostics.error
|
||||
}
|
||||
trace.reportDiagnosticOnce(diagnostic.on(element, annotationFqName))
|
||||
trace.reportDiagnosticOnce(diagnostic.factory.on(element, message ?: diagnostic.defaultMessage(annotationFqName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,13 +171,16 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
?: annotations.findAnnotation(OLD_EXPERIMENTAL_FQ_NAME)
|
||||
?: return null
|
||||
|
||||
val severity = when ((experimental.allValueArguments[LEVEL] as? EnumValue)?.enumEntryName) {
|
||||
val arguments = experimental.allValueArguments
|
||||
val severity = when ((arguments[LEVEL] as? EnumValue)?.enumEntryName) {
|
||||
WARNING_LEVEL -> Experimentality.Severity.WARNING
|
||||
ERROR_LEVEL -> Experimentality.Severity.ERROR
|
||||
else -> Experimentality.DEFAULT_SEVERITY
|
||||
}
|
||||
|
||||
return Experimentality(fqNameSafe, severity)
|
||||
val message = (arguments[MESSAGE] as? StringValue)?.value
|
||||
|
||||
return Experimentality(fqNameSafe, severity, message)
|
||||
}
|
||||
|
||||
private fun PsiElement.isExperimentalityAccepted(annotationFqName: FqName, context: CheckerContext): Boolean =
|
||||
@@ -344,12 +365,17 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
|
||||
|
||||
for ((experimentality, member) in experimentalOverridden) {
|
||||
if (!declaration.isExperimentalityAccepted(experimentality.annotationFqName, context)) {
|
||||
val diagnostic = when (experimentality.severity) {
|
||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE
|
||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_OVERRIDE_ERROR
|
||||
}
|
||||
val reportOn = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration
|
||||
context.trace.report(diagnostic.on(reportOn, experimentality.annotationFqName, member.containingDeclaration))
|
||||
|
||||
val (diagnostic, defaultMessageVerb) = when (experimentality.severity) {
|
||||
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_OVERRIDE to "should"
|
||||
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_OVERRIDE_ERROR to "must"
|
||||
}
|
||||
val message = experimentality.message
|
||||
?: "This declaration overrides experimental member of supertype " +
|
||||
"'${member.containingDeclaration.name.asString()}' and $defaultMessageVerb be annotated " +
|
||||
"with '@${experimentality.annotationFqName.asString()}'"
|
||||
context.trace.report(diagnostic.on(reportOn, message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -319,7 +319,14 @@ class ConstantExpressionEvaluator(
|
||||
|
||||
companion object {
|
||||
private val EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS = ExperimentalUsageChecker.ExperimentalityDiagnostics(
|
||||
Errors.EXPERIMENTAL_UNSIGNED_LITERALS, Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR
|
||||
warning = ExperimentalUsageChecker.ExperimentalityDiagnostic(
|
||||
Errors.EXPERIMENTAL_UNSIGNED_LITERALS,
|
||||
ExperimentalUsageChecker.getDefaultDiagnosticMessage("Unsigned literals are experimental and their usages should be marked")
|
||||
),
|
||||
error = ExperimentalUsageChecker.ExperimentalityDiagnostic(
|
||||
Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR,
|
||||
ExperimentalUsageChecker.getDefaultDiagnosticMessage("Unsigned literals are experimental and their usages must be marked")
|
||||
)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
||||
Reference in New Issue
Block a user