From 879b6a0a3f081007fc2ae96b06210274bbf4d2a0 Mon Sep 17 00:00:00 2001 From: Ilya Ryzhenkov Date: Mon, 20 Apr 2015 15:02:30 +0300 Subject: [PATCH] Implement deprecated validator. --- .../validation/DefaultSymbolUsageValidator.kt | 2 +- .../validation/DeprecatedSymbolValidator.kt | 138 ++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DeprecatedSymbolValidator.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DefaultSymbolUsageValidator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DefaultSymbolUsageValidator.kt index 839e9ea6792..13208cfaea9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DefaultSymbolUsageValidator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DefaultSymbolUsageValidator.kt @@ -1,3 +1,3 @@ package org.jetbrains.kotlin.resolve.validation -object DefaultSymbolUsageValidator : CompositeSymbolUsageValidator() \ No newline at end of file +object DefaultSymbolUsageValidator : CompositeSymbolUsageValidator(DeprecatedSymbolValidator()) \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DeprecatedSymbolValidator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DeprecatedSymbolValidator.kt new file mode 100644 index 00000000000..cdfa7b32e12 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/validation/DeprecatedSymbolValidator.kt @@ -0,0 +1,138 @@ +package org.jetbrains.kotlin.resolve.validation + +import com.intellij.psi.PsiElement +import com.intellij.psi.tree.TokenSet +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.DescriptorUtils + +public class DeprecatedSymbolValidator : SymbolUsageValidator { + private val JAVA_DEPRECATED = FqName(javaClass().getName()) + private val KOTLIN_DEPRECATED = DescriptorUtils.getFqNameSafe(KotlinBuiltIns.getInstance().getDeprecatedAnnotation()) + + override fun validateCall(targetDescriptor: CallableDescriptor, trace: BindingTrace, element: PsiElement) { + val deprecated = targetDescriptor.getDeprecatedAnnotation() + if (deprecated != null) { + val (annotation, target) = deprecated + trace.report(createDeprecationDiagnostic(element, target, annotation)) + } + else if (targetDescriptor is PropertyDescriptor) { + propertyGetterWorkaround(targetDescriptor, trace, element) + } + } + + override fun validateTypeUsage(targetDescriptor: ClassifierDescriptor, trace: BindingTrace, element: PsiElement) { + // Do not check types in annotation entries to prevent cycles in resolve, rely on call message + val annotationEntry = JetStubbedPsiUtil.getPsiOrStubParent(element, javaClass(), true) + if (annotationEntry != null && annotationEntry.getCalleeExpression().getConstructorReferenceExpression() == element) + return + + // Do not check types in calls to super constructor in extends list, rely on call message + val superExpression = JetStubbedPsiUtil.getPsiOrStubParent(element, javaClass(), true) + if (superExpression != null && superExpression.getCalleeExpression().getConstructorReferenceExpression() == element) + return + + val deprecated = targetDescriptor.getDeprecatedAnnotation() + if (deprecated != null) { + val (annotation, target) = deprecated + trace.report(createDeprecationDiagnostic(element, target, annotation)) + } + } + + private fun DeclarationDescriptor.getDeprecatedAnnotation(): Pair? { + val ownAnnotation = getDeclaredDeprecatedAnnotation() + if (ownAnnotation != null) + return ownAnnotation to this + when (this) { + is ConstructorDescriptor -> { + val classDescriptor = getContainingDeclaration() + val classAnnotation = classDescriptor.getDeclaredDeprecatedAnnotation() + if (classAnnotation != null) + return classAnnotation to classDescriptor + } + is PropertyAccessorDescriptor -> { + val propertyDescriptor = getContainingDeclaration() + val propertyAnnotation = propertyDescriptor.getDeclaredDeprecatedAnnotation() + if (propertyAnnotation != null) + return propertyAnnotation to propertyDescriptor + } + } + return null + } + + private fun DeclarationDescriptor.getDeclaredDeprecatedAnnotation(): AnnotationDescriptor? { + return getAnnotations().findAnnotation(KOTLIN_DEPRECATED) ?: getAnnotations().findAnnotation(JAVA_DEPRECATED) + } + + private fun createDeprecationDiagnostic(element: PsiElement, descriptor: DeclarationDescriptor, deprecated: AnnotationDescriptor): Diagnostic { + val message = getMessageFromAnnotationDescriptor(deprecated) + return if (message == null) + Errors.DEPRECATED_SYMBOL.on(element, descriptor) + else + Errors.DEPRECATED_SYMBOL_WITH_MESSAGE.on(element, descriptor, message) + } + + private fun getMessageFromAnnotationDescriptor(descriptor: AnnotationDescriptor): String? { + val parameterName = Name.identifier("value") + for ((parameterDescriptor, argument) in descriptor.getAllValueArguments()) { + if (parameterDescriptor.getName() == parameterName) { + val parameterValue = argument.getValue() + if (parameterValue is String) { + return parameterValue + } + else + return null + } + } + return null + } + + private val PROPERTY_SET_OPERATIONS = TokenSet.create(JetTokens.EQ, JetTokens.PLUSEQ, JetTokens.MINUSEQ, JetTokens.MULTEQ, JetTokens.DIVEQ, JetTokens.PERCEQ, JetTokens.PLUSPLUS, JetTokens.MINUSMINUS) + fun propertyGetterWorkaround(propertyDescriptor: PropertyDescriptor, trace: BindingTrace, expression: PsiElement) { + // property getters do not come as callable yet, so we analyse surroundings to check for deprecation annotation on getter + val binaryExpression = PsiTreeUtil.getParentOfType(expression, javaClass()) + if (binaryExpression != null) { + val left = binaryExpression.getLeft() + if (left == expression) { + val operation = binaryExpression.getOperationToken() + if (operation != null && operation in PROPERTY_SET_OPERATIONS) + return + } + + val jetReferenceExpressions = PsiTreeUtil.getChildrenOfType(left, javaClass()) + if (jetReferenceExpressions != null) { + for (expr in jetReferenceExpressions) { + if (expr == expression) { + val operation = binaryExpression.getOperationToken() + if (operation != null && operation in PROPERTY_SET_OPERATIONS) + return // skip binary set operations + } + } + } + } + + val unaryExpression = PsiTreeUtil.getParentOfType(expression, javaClass()) + if (unaryExpression != null) { + val operation = unaryExpression.getOperationReference().getReferencedNameElementType() + if (operation != null && operation in PROPERTY_SET_OPERATIONS) + return // skip unary set operations + + } + + val callableExpression = PsiTreeUtil.getParentOfType(expression, javaClass()) + if (callableExpression != null && callableExpression.getCallableReference() == expression) { + return // skip Type::property + } + + propertyDescriptor.getGetter()?.let { validateCall(it, trace, expression) } + } +} \ No newline at end of file