diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedJavaAnnotationFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedJavaAnnotationFix.kt index 9737b054b7e..956cf87c8ae 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedJavaAnnotationFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedJavaAnnotationFix.kt @@ -1,64 +1,97 @@ package org.jetbrains.kotlin.idea.quickfix +import com.intellij.codeInsight.actions.OptimizeImportsProcessor import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.load.java.components.JavaAnnotationMapper +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtAnnotationEntry -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.kotlin.psi.KtValueArgument -import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm -internal class DeprecatedJavaAnnotationFix(element: KtAnnotationEntry, - private val annotationFqName: FqName, - private val arguments: List) : KotlinQuickFixAction(element) { +internal class DeprecatedJavaAnnotationFix( + element: KtAnnotationEntry, + private val annotationFqName: FqName +) : KotlinQuickFixAction(element) { override fun getFamilyName() = "Replace Annotation" override fun getText(): String = "Replace annotation with ${annotationFqName.asString()}" + override fun startInWriteAction(): Boolean = false + override fun invoke(project: Project, editor: Editor?, file: KtFile) { val element = element ?: return - val psiFactory = KtPsiFactory(project) - val argumentString = if(arguments.isEmpty()) { - "" - } else { - "("+ arguments.joinToString(",") { it.text } + ")" + val arguments = updateAnnotation(psiFactory) + val replacementAnnotation = psiFactory.createAnnotationEntry("@$annotationFqName") + val valueArgumentList = psiFactory.buildValueArgumentList { + appendFixedText("(") + arguments.forEach { argument -> + appendExpression(argument.getArgumentExpression()) + } + appendFixedText(")") } - element.replace(psiFactory.createAnnotationEntry("@" + annotationFqName.shortName() + argumentString)) + if (arguments.isNotEmpty()) { + replacementAnnotation.add(valueArgumentList) + } - for ((java, kotlin) in JavaAnnotationMapper.javaToKotlinNameMap) { - if (kotlin == annotationFqName) { - val oldImport = file.importDirectives.find { it -> it.importedFqName == java } ?: return - oldImport.delete() - break + val replaced = runWriteAction { + element.replaced(replacementAnnotation) + } + OptimizeImportsProcessor(project, file).run() + runWriteAction { + ShortenReferences.DEFAULT.process(replaced) + } + } + + private fun updateAnnotation(psiFactory: KtPsiFactory): List { + val bindingContext = element?.analyze() ?: return emptyList() + + val descriptor = bindingContext[BindingContext.ANNOTATION, element] + val name = descriptor?.fqName ?: return emptyList() + + val argumentOutput = mutableListOf() + if (name == RETENTION_FQ_NAME) { + for (arg in descriptor.allValueArguments.values) { + val typeAndValue = arg.value as? Pair<*, *> + val classId = typeAndValue?.first as? ClassId + val value = typeAndValue?.second + + if (classId == RETENTION_POLICY_ID) { + val argument = when ((value as? Name)?.asString()) { + "SOURCE" -> psiFactory.createArgument("kotlin.annotation.AnnotationRetention.SOURCE") + "CLASS" -> psiFactory.createArgument("kotlin.annotation.AnnotationRetention.BINARY") + "RUNTIME" -> psiFactory.createArgument("kotlin.annotation.AnnotationRetention.RUNTIME") + else -> psiFactory.createArgument("${classId.shortClassName}.$value") + } + argumentOutput.add(argument) + } } } - file.importList?.add(psiFactory.createImportDirective(ImportPath(annotationFqName, false, null))) + return argumentOutput } companion object Factory : KotlinSingleIntentionActionFactory() { + private val RETENTION_FQ_NAME = FqName("java.lang.annotation.Retention") + + private val RETENTION_POLICY_ID = ClassId(FqName("java.lang.annotation"), FqName("RetentionPolicy"), false) + override fun createAction(diagnostic: Diagnostic): IntentionAction? { val castedDiagnostic = ErrorsJvm.DEPRECATED_JAVA_ANNOTATION.cast(diagnostic) val updatedAnnotation = castedDiagnostic.a as? FqName ?: return null - val entry = diagnostic.psiElement as? KtAnnotationEntry ?: return null - val arguments = mutableListOf() - entry.valueArguments.forEach { - (it as KtValueArgument).children.forEach { child -> - arguments.add(child.context as KtValueArgument) - } - } - - return DeprecatedJavaAnnotationFix(entry, updatedAnnotation, arguments) + return DeprecatedJavaAnnotationFix(entry, updatedAnnotation) } } diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt b/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt index e5160ea08f5..92e11d88060 100644 --- a/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt @@ -1,5 +1,4 @@ // "Replace annotation with kotlin.annotation.Retention" "true" -// ERROR: Type mismatch: inferred type is RetentionPolicy but AnnotationRetention was expected import java.lang.annotation.RetentionPolicy import java.lang.annotation.Retention diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt.after b/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt.after index f65a21279d3..410b90e7962 100644 --- a/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt.after +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt.after @@ -1,8 +1,4 @@ // "Replace annotation with kotlin.annotation.Retention" "true" -// ERROR: Type mismatch: inferred type is RetentionPolicy but AnnotationRetention was expected -import java.lang.annotation.RetentionPolicy -import kotlin.annotation.Retention - -@Retention(RetentionPolicy.SOURCE) +@Retention(AnnotationRetention.SOURCE) annotation class Foo \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt b/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt new file mode 100644 index 00000000000..b98d987d147 --- /dev/null +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt @@ -0,0 +1,7 @@ +// "Replace annotation with kotlin.annotation.Retention" "true" + +import java.lang.annotation.RetentionPolicy +import java.lang.annotation.Retention + +@Retention(RetentionPolicy.CLASS) +annotation class Foo \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt.after b/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt.after new file mode 100644 index 00000000000..e6c13ad9cd5 --- /dev/null +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt.after @@ -0,0 +1,4 @@ +// "Replace annotation with kotlin.annotation.Retention" "true" + +@Retention(AnnotationRetention.BINARY) +annotation class Foo \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt b/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt new file mode 100644 index 00000000000..4ef7dd5aeaf --- /dev/null +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt @@ -0,0 +1,7 @@ +// "Replace annotation with kotlin.annotation.Retention" "true" + +import java.lang.annotation.RetentionPolicy +import java.lang.annotation.Retention + +@Retention(RetentionPolicy.RUNTIME) +annotation class Foo \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt.after b/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt.after new file mode 100644 index 00000000000..8af3e2fc6c5 --- /dev/null +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt.after @@ -0,0 +1,4 @@ +// "Replace annotation with kotlin.annotation.Retention" "true" + +@Retention(AnnotationRetention.RUNTIME) +annotation class Foo \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt.after b/idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt.after index 2e10b63540a..06672a9aac2 100644 --- a/idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt.after +++ b/idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt.after @@ -1,6 +1,4 @@ // "Replace annotation with kotlin.annotation.MustBeDocumented" "true" -import kotlin.annotation.MustBeDocumented - @MustBeDocumented annotation class Foo \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index d6321ad908f..15b82e531f2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -5656,6 +5656,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt"); } + @TestMetadata("withClassRetention.kt") + public void testWithClassRetention() throws Exception { + runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withClassRetention.kt"); + } + + @TestMetadata("withRuntimeRetention.kt") + public void testWithRuntimeRetention() throws Exception { + runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withRuntimeRetention.kt"); + } + @TestMetadata("withoutArguments.kt") public void testWithoutArguments() throws Exception { runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt");