Migrate RetentionPolicy arguments in deprecated Java quick-fix

#KT-29666 Fixed
This commit is contained in:
Matthew Runo
2019-05-07 00:12:59 +03:00
committed by Mikhail Glukhikh
parent db5396c1c2
commit 39f576d75c
9 changed files with 96 additions and 38 deletions
@@ -1,64 +1,97 @@
package org.jetbrains.kotlin.idea.quickfix package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.actions.OptimizeImportsProcessor
import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.diagnostics.Diagnostic 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.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
internal class DeprecatedJavaAnnotationFix(element: KtAnnotationEntry, internal class DeprecatedJavaAnnotationFix(
private val annotationFqName: FqName, element: KtAnnotationEntry,
private val arguments: List<KtValueArgument>) : KotlinQuickFixAction<KtAnnotationEntry>(element) { private val annotationFqName: FqName
) : KotlinQuickFixAction<KtAnnotationEntry>(element) {
override fun getFamilyName() = "Replace Annotation" override fun getFamilyName() = "Replace Annotation"
override fun getText(): String = "Replace annotation with ${annotationFqName.asString()}" override fun getText(): String = "Replace annotation with ${annotationFqName.asString()}"
override fun startInWriteAction(): Boolean = false
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return val element = element ?: return
val psiFactory = KtPsiFactory(project) val psiFactory = KtPsiFactory(project)
val argumentString = if(arguments.isEmpty()) { val arguments = updateAnnotation(psiFactory)
"" val replacementAnnotation = psiFactory.createAnnotationEntry("@$annotationFqName")
} else { val valueArgumentList = psiFactory.buildValueArgumentList {
"("+ arguments.joinToString(",") { it.text } + ")" 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) { val replaced = runWriteAction {
if (kotlin == annotationFqName) { element.replaced(replacementAnnotation)
val oldImport = file.importDirectives.find { it -> it.importedFqName == java } ?: return }
oldImport.delete() OptimizeImportsProcessor(project, file).run()
break runWriteAction {
ShortenReferences.DEFAULT.process(replaced)
}
}
private fun updateAnnotation(psiFactory: KtPsiFactory): List<KtValueArgument> {
val bindingContext = element?.analyze() ?: return emptyList()
val descriptor = bindingContext[BindingContext.ANNOTATION, element]
val name = descriptor?.fqName ?: return emptyList()
val argumentOutput = mutableListOf<KtValueArgument>()
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() { 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? { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val castedDiagnostic = ErrorsJvm.DEPRECATED_JAVA_ANNOTATION.cast(diagnostic) val castedDiagnostic = ErrorsJvm.DEPRECATED_JAVA_ANNOTATION.cast(diagnostic)
val updatedAnnotation = castedDiagnostic.a as? FqName ?: return null val updatedAnnotation = castedDiagnostic.a as? FqName ?: return null
val entry = diagnostic.psiElement as? KtAnnotationEntry ?: return null val entry = diagnostic.psiElement as? KtAnnotationEntry ?: return null
val arguments = mutableListOf<KtValueArgument>() return DeprecatedJavaAnnotationFix(entry, updatedAnnotation)
entry.valueArguments.forEach {
(it as KtValueArgument).children.forEach { child ->
arguments.add(child.context as KtValueArgument)
}
}
return DeprecatedJavaAnnotationFix(entry, updatedAnnotation, arguments)
} }
} }
@@ -1,5 +1,4 @@
// "Replace annotation with kotlin.annotation.Retention" "true" // "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.RetentionPolicy
import java.lang.annotation.Retention import java.lang.annotation.Retention
@@ -1,8 +1,4 @@
// "Replace annotation with kotlin.annotation.Retention" "true" // "Replace annotation with kotlin.annotation.Retention" "true"
// ERROR: Type mismatch: inferred type is RetentionPolicy but AnnotationRetention was expected
import java.lang.annotation.RetentionPolicy @<caret>Retention(AnnotationRetention.SOURCE)
import kotlin.annotation.Retention
@Retention<caret>(RetentionPolicy.SOURCE)
annotation class Foo annotation class Foo
@@ -0,0 +1,7 @@
// "Replace annotation with kotlin.annotation.Retention" "true"
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
@Retention<caret>(RetentionPolicy.CLASS)
annotation class Foo
@@ -0,0 +1,4 @@
// "Replace annotation with kotlin.annotation.Retention" "true"
@<caret>Retention(AnnotationRetention.BINARY)
annotation class Foo
@@ -0,0 +1,7 @@
// "Replace annotation with kotlin.annotation.Retention" "true"
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
@Retention<caret>(RetentionPolicy.RUNTIME)
annotation class Foo
@@ -0,0 +1,4 @@
// "Replace annotation with kotlin.annotation.Retention" "true"
@<caret>Retention(AnnotationRetention.RUNTIME)
annotation class Foo
@@ -1,6 +1,4 @@
// "Replace annotation with kotlin.annotation.MustBeDocumented" "true" // "Replace annotation with kotlin.annotation.MustBeDocumented" "true"
import kotlin.annotation.MustBeDocumented
@MustBeDocumented<caret> @MustBeDocumented<caret>
annotation class Foo annotation class Foo
@@ -5656,6 +5656,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withArgument.kt"); 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") @TestMetadata("withoutArguments.kt")
public void testWithoutArguments() throws Exception { public void testWithoutArguments() throws Exception {
runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt"); runTest("idea/testData/quickfix/deprecatedJavaAnnotation/withoutArguments.kt");