Introduce declaration checker for DeprecatedSinceKotlin annotation

- DeprecatedSinceKotlin annotation should only be applicable when
   there's `@Deprecated` annotation on the same declaration
 - Deprecation level shouldn't be specified in the relevant
   `@Deprecated` annotation
 - Check that warningSince <= errorSince <= hiddenSince
This commit is contained in:
Mikhail Zarechenskiy
2020-06-17 15:52:57 +03:00
parent 0aaf29c045
commit 60c51476f2
12 changed files with 199 additions and 8 deletions
@@ -6657,6 +6657,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@TestMetadata("deprecatedSinceKotlinDeclaration.kt")
public void testDeprecatedSinceKotlinDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/deprecatedSinceKotlinDeclaration.kt");
}
@TestMetadata("error.kt")
public void testError() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt");
@@ -100,6 +100,10 @@ public interface Errors {
DiagnosticFactory3<PsiElement, DeclarationDescriptor, VersionRequirement.Version, Pair<LanguageVersion, String>>
VERSION_REQUIREMENT_DEPRECATION_ERROR = DiagnosticFactory3.create(ERROR);
DiagnosticFactory0<PsiElement> DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> API_NOT_AVAILABLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<PsiElement, FqName> MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR);
@@ -377,6 +377,10 @@ public class DefaultErrorMessages {
(obj, renderingContext) -> obj.equals(VersionRequirement.Version.INFINITY) ? "" : " is only available since Kotlin " + obj.asString() + " and",
versionRequirementMessage);
MAP.put(DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED, "DeprecatedSinceKotlin annotation can be used only together with Deprecated annotation");
MAP.put(DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL, "DeprecatedSinceKotlin annotation can be used only with unspecified deprecation level of Deprecated annotation");
MAP.put(DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS, "Values of DeprecatedSinceKotlin annotation should be ordered so 'warningSince' <= 'errorSince' <= 'hiddenSince' if specified");
MAP.put(API_NOT_AVAILABLE, "This declaration is only available since Kotlin {0} and cannot be used with the specified API version {1}", STRING, STRING);
MAP.put(MISSING_DEPENDENCY_CLASS, "Cannot access class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
@@ -36,7 +36,8 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
TailrecFunctionChecker,
TrailingCommaDeclarationChecker,
MissingDependencySupertypeChecker.ForDeclarations,
FunInterfaceDeclarationChecker()
FunInterfaceDeclarationChecker(),
DeprecatedSinceKotlinAnnotationChecker
)
private val DEFAULT_CALL_CHECKERS = listOf(
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2020 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.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.deprecation.getSinceVersion
import org.jetbrains.kotlin.resolve.source.getPsi
object DeprecatedSinceKotlinAnnotationChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val deprecatedSinceAnnotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecatedSinceKotlin) ?: return
val deprecatedSinceAnnotationPsi = deprecatedSinceAnnotation.source.getPsi() as? KtAnnotationEntry ?: return
val deprecatedAnnotation = descriptor.annotations.findAnnotation(KotlinBuiltIns.FQ_NAMES.deprecated)
val deprecatedSinceAnnotationName = deprecatedSinceAnnotationPsi.typeReference ?: return
if (deprecatedAnnotation == null) {
context.trace.report(
Errors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED.on(
deprecatedSinceAnnotationName
)
)
return
}
if (deprecatedAnnotation.argumentValue(Deprecated::level.name) != null) {
context.trace.report(
Errors.DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL.on(
deprecatedSinceAnnotationName
)
)
return
}
val warningSince = deprecatedSinceAnnotation.getSinceVersion("warningSince")
val errorSince = deprecatedSinceAnnotation.getSinceVersion("errorSince")
val hiddenSince = deprecatedSinceAnnotation.getSinceVersion("hiddenSince")
if (!lessOrNull(warningSince, errorSince) || !lessOrNull(errorSince, hiddenSince) || !lessOrNull(warningSince, hiddenSince)) {
context.trace.report(
Errors.DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS.on(
deprecatedSinceAnnotationName
)
)
return
}
}
private fun lessOrNull(a: ApiVersion?, b: ApiVersion?): Boolean =
if (a == null || b == null) true else a <= b
}
@@ -72,16 +72,13 @@ internal sealed class DeprecatedByAnnotation(
}
private fun computeLevelForDeprecatedSinceKotlin(annotation: AnnotationDescriptor, apiVersion: ApiVersion): DeprecationLevelValue? {
fun getSinceVersion(name: String): ApiVersion? =
annotation.argumentValue(name)?.safeAs<StringValue>()?.value?.takeUnless(String::isEmpty)?.let(ApiVersion.Companion::parse)
val hiddenSince = getSinceVersion("hiddenSince")
val hiddenSince = annotation.getSinceVersion("hiddenSince")
if (hiddenSince != null && apiVersion >= hiddenSince) return HIDDEN
val errorSince = getSinceVersion("errorSince")
val errorSince = annotation.getSinceVersion("errorSince")
if (errorSince != null && apiVersion >= errorSince) return ERROR
val warningSince = getSinceVersion("warningSince")
val warningSince = annotation.getSinceVersion("warningSince")
if (warningSince != null && apiVersion >= warningSince) return WARNING
return null
@@ -6,19 +6,26 @@
package org.jetbrains.kotlin.resolve.deprecation
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.container.DefaultImplementation
import org.jetbrains.kotlin.container.PlatformExtensionsClashResolver
import org.jetbrains.kotlin.container.PlatformSpecificExtension
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.deprecation.DeprecationLevelValue.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
fun Deprecation.deprecatedByOverriddenMessage(): String? = (this as? DeprecatedByOverridden)?.additionalMessage()
fun Deprecation.deprecatedByAnnotationReplaceWithExpression(): String? = (this as? DeprecatedByAnnotation)?.replaceWithValue
// The function extracts value of warningSince/errorSince/hiddenSince from DeprecatedSinceKotlin annotation
fun AnnotationDescriptor.getSinceVersion(name: String): ApiVersion? =
argumentValue(name)?.safeAs<StringValue>()?.value?.takeUnless(String::isEmpty)?.let(ApiVersion.Companion::parse)
internal fun createDeprecationDiagnostic(
element: PsiElement, deprecation: Deprecation, languageVersionSettings: LanguageVersionSettings
): Diagnostic {
@@ -0,0 +1,42 @@
@Deprecated("", ReplaceWith(""))
@DeprecatedSinceKotlin("", warningSince = "1.0", errorSince = "1.1", hiddenSince = "1.2")
fun good() {}
@DeprecatedSinceKotlin("")
class Clazz
@Deprecated("", level = DeprecationLevel.WARNING)
@DeprecatedSinceKotlin("")
fun fooWarning() {}
@Deprecated("", ReplaceWith(""), DeprecationLevel.WARNING)
@DeprecatedSinceKotlin("")
fun fooDefaultWarning() {}
@Deprecated("", level = DeprecationLevel.ERROR)
@DeprecatedSinceKotlin("")
fun fooError() {}
@Deprecated("", level = DeprecationLevel.HIDDEN)
@DeprecatedSinceKotlin("")
fun fooHidden() {}
@Deprecated("")
@DeprecatedSinceKotlin("", warningSince = "1.1", errorSince = "1.0")
fun fooWarningIsGreater1() {}
@Deprecated("")
@DeprecatedSinceKotlin("", warningSince = "1.1", hiddenSince = "1.0")
fun fooWarningIsGreater2() {}
@Deprecated("")
@DeprecatedSinceKotlin("", warningSince = "1.1", errorSince = "1.3", hiddenSince = "1.2")
fun fooErrorIsGreater() {}
@Deprecated("")
@DeprecatedSinceKotlin("", ReplaceWith(""), "1.2", "1.1", "1.1")
fun fooDefault() {}
@Deprecated("")
@DeprecatedSinceKotlin("", ReplaceWith(""), "1.1", "1.1", "1.1")
fun fooEqual() {}
@@ -0,0 +1,42 @@
@Deprecated("", ReplaceWith(""))
@DeprecatedSinceKotlin("", warningSince = "1.0", errorSince = "1.1", hiddenSince = "1.2")
fun good() {}
@<!DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED!>DeprecatedSinceKotlin<!>("")
class Clazz
@Deprecated("", level = DeprecationLevel.WARNING)
@<!DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL!>DeprecatedSinceKotlin<!>("")
fun fooWarning() {}
@Deprecated("", ReplaceWith(""), DeprecationLevel.WARNING)
@<!DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL!>DeprecatedSinceKotlin<!>("")
fun fooDefaultWarning() {}
@Deprecated("", level = DeprecationLevel.ERROR)
@<!DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL!>DeprecatedSinceKotlin<!>("")
fun fooError() {}
@Deprecated("", level = DeprecationLevel.HIDDEN)
@<!DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL!>DeprecatedSinceKotlin<!>("")
fun fooHidden() {}
@Deprecated("")
@<!DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS!>DeprecatedSinceKotlin<!>("", warningSince = "1.1", errorSince = "1.0")
fun fooWarningIsGreater1() {}
@Deprecated("")
@<!DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS!>DeprecatedSinceKotlin<!>("", warningSince = "1.1", hiddenSince = "1.0")
fun fooWarningIsGreater2() {}
@Deprecated("")
@<!DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS!>DeprecatedSinceKotlin<!>("", warningSince = "1.1", errorSince = "1.3", hiddenSince = "1.2")
fun fooErrorIsGreater() {}
@Deprecated("")
@<!DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS!>DeprecatedSinceKotlin<!>("", ReplaceWith(""), "1.2", "1.1", "1.1")
fun fooDefault() {}
@Deprecated("")
@DeprecatedSinceKotlin("", ReplaceWith(""), "1.1", "1.1", "1.1")
fun fooEqual() {}
@@ -0,0 +1,19 @@
package
@kotlin.Deprecated(message = "") @kotlin.DeprecatedSinceKotlin(errorSince = "1.1", hiddenSince = "1.1", message = "", replaceWith = kotlin.ReplaceWith(expression = "", imports = {}), warningSince = "1.2") public fun fooDefault(): kotlin.Unit
@kotlin.Deprecated(level = DeprecationLevel.WARNING, message = "", replaceWith = kotlin.ReplaceWith(expression = "", imports = {})) @kotlin.DeprecatedSinceKotlin(message = "") public fun fooDefaultWarning(): kotlin.Unit
@kotlin.Deprecated(message = "") @kotlin.DeprecatedSinceKotlin(errorSince = "1.1", hiddenSince = "1.1", message = "", replaceWith = kotlin.ReplaceWith(expression = "", imports = {}), warningSince = "1.1") public fun fooEqual(): kotlin.Unit
@kotlin.Deprecated(level = DeprecationLevel.ERROR, message = "") @kotlin.DeprecatedSinceKotlin(message = "") public fun fooError(): kotlin.Unit
@kotlin.Deprecated(message = "") @kotlin.DeprecatedSinceKotlin(errorSince = "1.3", hiddenSince = "1.2", message = "", warningSince = "1.1") public fun fooErrorIsGreater(): kotlin.Unit
@kotlin.Deprecated(level = DeprecationLevel.HIDDEN, message = "") @kotlin.DeprecatedSinceKotlin(message = "") public fun fooHidden(): kotlin.Unit
@kotlin.Deprecated(level = DeprecationLevel.WARNING, message = "") @kotlin.DeprecatedSinceKotlin(message = "") public fun fooWarning(): kotlin.Unit
@kotlin.Deprecated(message = "") @kotlin.DeprecatedSinceKotlin(errorSince = "1.0", message = "", warningSince = "1.1") public fun fooWarningIsGreater1(): kotlin.Unit
@kotlin.Deprecated(message = "") @kotlin.DeprecatedSinceKotlin(hiddenSince = "1.0", message = "", warningSince = "1.1") public fun fooWarningIsGreater2(): kotlin.Unit
@kotlin.Deprecated(message = "", replaceWith = kotlin.ReplaceWith(expression = "", imports = {})) @kotlin.DeprecatedSinceKotlin(errorSince = "1.1", hiddenSince = "1.2", message = "", warningSince = "1.0") public fun good(): kotlin.Unit
@kotlin.DeprecatedSinceKotlin(message = "") public final class Clazz {
public constructor Clazz()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -6664,6 +6664,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTestWithFirVali
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@TestMetadata("deprecatedSinceKotlinDeclaration.kt")
public void testDeprecatedSinceKotlinDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/deprecatedSinceKotlinDeclaration.kt");
}
@TestMetadata("error.kt")
public void testError() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt");
@@ -6659,6 +6659,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@TestMetadata("deprecatedSinceKotlinDeclaration.kt")
public void testDeprecatedSinceKotlinDeclaration() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/deprecatedSinceKotlinDeclaration.kt");
}
@TestMetadata("error.kt")
public void testError() throws Exception {
runTest("compiler/testData/diagnostics/tests/deprecated/deprecatedSinceKotlin/error.kt");