Add quickfix for usages of javaClass<T>() in annotations loaded from Java

This commit is contained in:
Denis Zharkov
2015-04-17 17:48:20 +03:00
parent 7325a459e5
commit cc9322fbdd
35 changed files with 444 additions and 10 deletions
@@ -33,6 +33,8 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.typeUtil.isArrayOfJavaLangClass
import org.jetbrains.kotlin.types.typeUtil.isJavaLangClass
public class JavaAnnotationCallChecker : CallChecker {
override fun <F : CallableDescriptor?> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
@@ -53,15 +55,7 @@ public class JavaAnnotationCallChecker : CallChecker {
}
}
private fun JetType.isJavaLangClassOrArray() = isJavaLangClass() ||
(KotlinBuiltIns.isArray(this) && getArguments().first().getType().isJavaLangClass())
private fun JetType.isJavaLangClass(): Boolean {
val classifier = getConstructor().getDeclarationDescriptor()
if (classifier !is ClassDescriptor) return false
return DescriptorUtils.isJavaLangClass(classifier)
}
private fun JetType.isJavaLangClassOrArray() = isJavaLangClass() || isArrayOfJavaLangClass()
private fun reportErrorsOnPositionedArguments(
resolvedCall: ResolvedCall<*>,
@@ -65,6 +65,9 @@ public class JetPsiFactory(private val project: Project) {
return createProperty("val x = $text").getInitializer()!!
}
public fun createClassLiteral(className: String): JetClassLiteralExpression =
createExpression("$className::class") as JetClassLiteralExpression
public fun createCallArguments(text: String): JetValueArgumentList {
val property = createProperty("val x = foo $text")
return (property.getInitializer() as JetCallExpression).getValueArgumentList()!!
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.types.isDynamic
import org.jetbrains.kotlin.types.TypeProjection
import org.jetbrains.kotlin.types.TypeProjectionImpl
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.DelegatingType
private fun JetType.getContainedTypeParameters(): Collection<TypeParameterDescriptor> {
@@ -83,4 +84,14 @@ fun JetType.replaceAnnotations(newAnnotations: Annotations): JetType {
override fun getAnnotations() = newAnnotations
}
}
}
public fun JetType.isJavaLangClass(): Boolean {
val classifier = getConstructor().getDeclarationDescriptor()
if (classifier !is ClassDescriptor) return false
return DescriptorUtils.isJavaLangClass(classifier)
}
public fun JetType.isArrayOfJavaLangClass(): Boolean =
KotlinBuiltIns.isArray(this) && getArguments().firstOrNull()?.getType()?.isJavaLangClass() ?: false
@@ -328,6 +328,12 @@ make.type.implicit.in.lambda.family=Make Types Implicit In Lambda (May Break Cod
invert.if.condition=Invert If Condition
invert.if.condition.family=Invert If Condition
replace.java.class.argument=Replace javaClass<T>() with T::class
replace.java.class.argument.family=Replace javaClass<T>() with T::class
replace.java.class.argument.in.whole.project=Replace javaClass<T>() with T::class in whole project
replace.java.class.argument.in.whole.project.family=Replace javaClass<T>() with T::class in whole project
replace.java.class.argument.in.whole.project.modal.title=Replacing javaClass<T>() with T::class in whole project
property.is.implemented.too.many=Has implementations
property.is.overridden.too.many=Is overridden in subclasses
property.is.implemented.header=Is implemented in <br/>
@@ -25,7 +25,10 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.*;
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory;
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterActionFactory;
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory;
import org.jetbrains.kotlin.idea.quickfix.replaceJavaClass.ReplaceJavaClassAsAnnotationArgumentFix;
import org.jetbrains.kotlin.idea.quickfix.replaceJavaClass.ReplaceJavaClassAsAnnotationArgumentInWholeProjectFix;
import org.jetbrains.kotlin.psi.JetClass;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm;
import static org.jetbrains.kotlin.diagnostics.Errors.*;
import static org.jetbrains.kotlin.lexer.JetTokens.*;
@@ -315,5 +318,8 @@ public class QuickFixRegistrar {
QuickFixes.factories.put(EXPLICIT_DELEGATION_CALL_REQUIRED, InsertDelegationCallQuickfix.InsertThisDelegationCallFactory.INSTANCE$);
QuickFixes.factories.put(EXPLICIT_DELEGATION_CALL_REQUIRED, InsertDelegationCallQuickfix.InsertSuperDelegationCallFactory.INSTANCE$);
QuickFixes.factories.put(ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION, ReplaceJavaClassAsAnnotationArgumentFix.Companion);
QuickFixes.factories.put(ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION, ReplaceJavaClassAsAnnotationArgumentInWholeProjectFix.Companion);
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix.replaceJavaClass
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.JetWholeProjectModalAction
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import java.util.ArrayList
public class ReplaceJavaClassAsAnnotationArgumentFix(
annotationEntry: JetAnnotationEntry
) : JetIntentionAction<JetAnnotationEntry>(annotationEntry) {
private val psiFactory: JetPsiFactory = JetPsiFactory(annotationEntry)
override fun getText() = JetBundle.message("replace.java.class.argument")
override fun getFamilyName() = JetBundle.message("replace.java.class.argument.family")
override fun invoke(project: Project, editor: Editor?, file: JetFile?) {
processTasks(createReplacementTasks(element), psiFactory)
}
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val entry = diagnostic.getPsiElement().getNonStrictParentOfType<JetAnnotationEntry>() ?: return null
return ReplaceJavaClassAsAnnotationArgumentFix(entry)
}
}
}
public class ReplaceJavaClassAsAnnotationArgumentInWholeProjectFix(
annotationEntry: JetAnnotationEntry
) : JetWholeProjectModalAction<JetAnnotationEntry, Collection<ReplacementTask>>(
annotationEntry, JetBundle.message("replace.java.class.argument.in.whole.project.modal.title")
) {
private val psiFactory: JetPsiFactory = JetPsiFactory(annotationEntry)
override fun getText() = JetBundle.message("replace.java.class.argument.in.whole.project")
override fun getFamilyName() = JetBundle.message("replace.java.class.argument.in.whole.project.family")
override fun collectDataForFile(project: Project, file: JetFile): Collection<ReplacementTask>? {
val result = arrayListOf<ReplacementTask>()
file.accept(object : JetTreeVisitorVoid() {
override fun visitAnnotationEntry(annotationEntry: JetAnnotationEntry) {
result.addAll(createReplacementTasks(annotationEntry))
}
})
return result
}
override fun applyChangesForFile(project: Project, file: JetFile, data: Collection<ReplacementTask>) {
processTasks(data, psiFactory)
}
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val entry = diagnostic.getPsiElement().getNonStrictParentOfType<JetAnnotationEntry>() ?: return null
return ReplaceJavaClassAsAnnotationArgumentInWholeProjectFix(entry)
}
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.quickfix.replaceJavaClass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.typeUtil.isArrayOfJavaLangClass
import org.jetbrains.kotlin.types.typeUtil.isJavaLangClass
private trait ReplacementTask
private class JavaClassCallReplacementTask(
val javaClassCall: JetExpression,
val className: String
) : ReplacementTask
fun createReplacementTasks(element: JetAnnotationEntry): List<ReplacementTask> {
val replacementTasks = arrayListOf<ReplacementTask>()
element.accept(object : JetTreeVisitorVoid() {
override fun visitCallExpression(expression: JetCallExpression) {
expression.acceptChildren(this)
val context = expression.analyze()
val resolvedCall = expression.getResolvedCall(context) ?: return
if (!context.getDiagnostics().any { it.isJavaLangClassArgumentInAnnotation(expression) }) return
val returnType = resolvedCall.getResultingDescriptor().getReturnType() ?: return
if (returnType.isJavaLangClass()) {
val inferredType = returnType.getArguments().firstOrNull()?.getType() ?: return
if (inferredType.isError()) return
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(inferredType)
replacementTasks.add(JavaClassCallReplacementTask(expression, renderedType))
}
}
})
return replacementTasks
}
private fun Diagnostic.isJavaLangClassArgumentInAnnotation(expression: JetCallExpression) =
getFactory() == ErrorsJvm.JAVA_LANG_CLASS_ARGUMENT_IN_ANNOTATION &&
getPsiElement().isAncestor(expression)
fun processTasks(replacementTasks: Collection<ReplacementTask>, psiFactory: JetPsiFactory) {
val elementsToShorten = arrayListOf<JetElement>()
replacementTasks.forEach {
task ->
when (task) {
is JavaClassCallReplacementTask -> {
val newElement = task.javaClassCall.replace(psiFactory.createClassLiteral(task.className)) as JetElement
elementsToShorten.add(newElement)
}
else -> error("Unexpected task type: ${task.javaClass.getName()}")
}
}
ShortenReferences.DEFAULT.process(elementsToShorten)
}
@@ -0,0 +1,12 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
import java.lang.Number
Ann(arg = array(Int::class, String::class)) class MyClass1
Ann(arg = array<java.lang.Class<*>>(Number::class, String::class)) class MyClass2
Ann(arg = array<java.lang.Class<out kotlin.Comparable<*>>>(Int::class, String::class)) class MyClass3
Ann(arg = array<java.lang.Class<Int>>(Int::class)) class MyClass4
@@ -0,0 +1,10 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
Ann(arg = array(javaClass<Int>(), javaClass<String>()<caret>)) class MyClass1
Ann(arg = array<java.lang.Class<*>>(javaClass<java.lang.Number>(), javaClass<String>())) class MyClass2
Ann(arg = array<java.lang.Class<out kotlin.Comparable<*>>>(javaClass<kotlin.Int>(), javaClass<String>())) class MyClass3
Ann(arg = array<java.lang.Class<Int>>(javaClass<kotlin.Int>())) class MyClass4
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
Ann(String::class<caret>) class MyClass
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
Ann(javaClass<String>()<caret>) class MyClass
@@ -0,0 +1,3 @@
public @interface Ann {
Class<?> value();
}
@@ -0,0 +1,6 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
import java.util.Random
Ann(A::class, A::class, *array(A::class), arg1 = A.B::class, arg2 = Random::class) class MyClass
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
Ann(javaClass(), javaClass(), *array(javaClass())<caret>, arg1 = javaClass(), arg2 = javaClass()) class MyClass
@@ -0,0 +1,11 @@
public @interface Ann {
Class<? extends A>[] value();
Class<? extends A.B> arg1();
Class<? extends java.util.Random> arg2();
}
class A {
static class B {}
}
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
Ann(String::class) class MyClass
@@ -0,0 +1,20 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
Ann(String::class, arg = Int::class, args = array()) class MyClass1
Ann(String::class, arg = Int::class, x = 1, args = array(Double::class)) class MyClass2 {
Ann(String::class, arg = Int::class, args = array(Double::class)) class Nested {
Ann(String::class, arg = Int::class, args = array(Double::class)) fun foo1() {
[Ann(String::class, arg = Int::class, args = array(Double::class))] class Local
}
[Ann(String::class, arg = Int::class, args = array(Double::class), x = 1)] fun foo2() {
[Ann(String::class, arg = Int::class, args = array(Double::class))] val local = 0
}
}
inner Ann(Double::class) class Inner
}
@@ -0,0 +1,20 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
Ann(javaClass<String>()<caret>, arg = javaClass<Int>(), args = array()) class MyClass1
Ann(javaClass<String>(), arg = javaClass<Int>(), x = 1, args = array(javaClass<Double>())) class MyClass2 {
Ann(javaClass<String>(), arg = javaClass<Int>(), args = array(javaClass<Double>())) class Nested {
Ann(javaClass<String>(), arg = javaClass<Int>(), args = array(javaClass<Double>())) fun foo1() {
[Ann(javaClass<String>(), arg = javaClass<Int>(), args = array(javaClass<Double>()))] class Local
}
[Ann(javaClass<String>(), arg = javaClass<Int>(), args = array(javaClass<Double>()), x = 1)] fun foo2() {
[Ann(javaClass<String>(), arg = javaClass<Int>(), args = array(javaClass<Double>()))] val local = 0
}
}
inner Ann(javaClass<Double>()) class Inner
}
@@ -0,0 +1,7 @@
public @interface Ann {
Class<?> value();
int x() default 1;
double y() default 1.0;
Class<?> arg() default String;
Class<?>[] args() default {};
}
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class in whole project" "true"
// WITH_RUNTIME
Ann(javaClass<String>()) class MyClass
@@ -0,0 +1,6 @@
// "Replace javaClass<T>() with T::class" "true"
// ERROR: An annotation parameter must be a `javaClass<T>()` call
// WITH_RUNTIME
val jClass = javaClass<String>()
Ann(jClass, Int::class) class MyClass1
@@ -0,0 +1,6 @@
// "Replace javaClass<T>() with T::class" "true"
// ERROR: An annotation parameter must be a `javaClass<T>()` call
// WITH_RUNTIME
val jClass = javaClass<String>()
Ann(jClass, javaClass<Int>()<caret>) class MyClass1
@@ -0,0 +1,3 @@
public @interface Ann {
Class<?>[] value();
}
@@ -0,0 +1,6 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
import java.lang
Ann(String::class, x = 2, arg = (Int::class), args = array((Any::class), lang.String::class)) class MyClass
@@ -0,0 +1,4 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
Ann(javaClass<String>(), x = 2, arg = (javaClass<Int>()), args = array((javaClass<Any>()), javaClass<java.lang.String>())<caret>) class MyClass
@@ -0,0 +1,7 @@
public @interface Ann {
Class<?> value();
int x();
double y() default 1.0;
Class<?> arg();
Class<?>[] args();
}
@@ -0,0 +1,5 @@
// "Replace javaClass<T>() with T::class" "true"
// ERROR: Unresolved reference: Err
// WITH_RUNTIME
Ann(javaClass<Err>(), Int::class) class MyClass1
@@ -0,0 +1,5 @@
// "Replace javaClass<T>() with T::class" "true"
// ERROR: Unresolved reference: Err
// WITH_RUNTIME
Ann(javaClass<Err>(), javaClass<Int>()<caret>) class MyClass1
@@ -0,0 +1,3 @@
public @interface Ann {
Class<?>[] value();
}
@@ -0,0 +1,13 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
import java.lang
Ann(
String::class,
Int::class,
*array(Double::class),
x = 2,
arg = Int::class,
args = array(Any::class, lang.String::class))
class MyClass
@@ -0,0 +1,11 @@
// "Replace javaClass<T>() with T::class" "true"
// WITH_RUNTIME
Ann(
javaClass<String>(),
javaClass<Int>(),
*array(javaClass<Double>()),
x = 2,
arg = javaClass<Int>(),
args = array(javaClass<Any>(), javaClass<java.lang.String>())<caret>)
class MyClass
@@ -0,0 +1,7 @@
public @interface Ann {
Class<?>[] value();
int x();
double y() default 1.0;
Class<?> arg();
Class<?>[] args();
}
@@ -878,6 +878,63 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ReplaceJavaClassWithKClassForJavaAnnotation extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInReplaceJavaClassWithKClassForJavaAnnotation() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
}
@TestMetadata("arrayWithExplicitTypeArgumentMultiple.before.Main.kt")
public void testArrayWithExplicitTypeArgumentMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/arrayWithExplicitTypeArgumentMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("basicMultiple.before.Main.kt")
public void testBasicMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/basicMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("implicitParameterMultiple.before.Main.kt")
public void testImplicitParameterMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/implicitParameterMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("manyFilesMultiple.before.Main.kt")
public void testManyFilesMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/manyFilesMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("nonConstMultiple.before.Main.kt")
public void testNonConstMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/nonConstMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("severalArgsMultiple.before.Main.kt")
public void testSeveralArgsMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/severalArgsMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("unresolvedMultiple.before.Main.kt")
public void testUnresolvedMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/unresolvedMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("valueAsVarargMultiple.before.Main.kt")
public void testValueAsVarargMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/replaceJavaClassWithKClassForJavaAnnotation/valueAsVarargMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/modifiers")
@@ -3084,6 +3084,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/modifiers")