Add quickfix for replacing positioned arguments in Java annotation

This commit is contained in:
Denis Zharkov
2015-05-27 12:01:32 +03:00
parent 0c818767a6
commit 53e9234ea9
19 changed files with 184 additions and 8 deletions
@@ -60,12 +60,7 @@ public class JavaAnnotationCallChecker : CallChecker {
resolvedCall: ResolvedCall<*>,
context: BasicCallResolutionContext
) {
resolvedCall.getValueArguments().filter {
p ->
p.key.getName() != JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME &&
p.value is ExpressionValueArgument &&
!((p.value as ExpressionValueArgument).getValueArgument()?.isNamed() ?: true)
}.forEach {
getJavaAnnotationCallValueArgumentsThatShouldBeNamed(resolvedCall).forEach {
reportOnValueArgument(context, it, ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION)
}
}
@@ -0,0 +1,32 @@
/*
* 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.load.kotlin
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
fun getJavaAnnotationCallValueArgumentsThatShouldBeNamed(resolvedCall: ResolvedCall<*>): Map<ValueParameterDescriptor, ResolvedValueArgument> =
resolvedCall.getValueArguments().filter {
p ->
p.key.getName() != JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME &&
p.value is ExpressionValueArgument &&
!((p.value as ExpressionValueArgument).getValueArgument()?.isNamed() ?: true)
}
@@ -76,7 +76,8 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe
Errors.UNNECESSARY_NOT_NULL_ASSERTION,
Errors.UNNECESSARY_SAFE_CALL,
Errors.USELESS_CAST,
Errors.USELESS_ELVIS
Errors.USELESS_ELVIS,
ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION
)
private fun Diagnostic.isObsoleteLabel(): Boolean {
@@ -346,5 +346,7 @@ public class QuickFixRegistrar {
QuickFixes.factories.put(DEPRECATED_SYMBOL_WITH_MESSAGE, DeprecatedSymbolUsageFix.Companion);
QuickFixes.factories.put(DEPRECATED_SYMBOL_WITH_MESSAGE, DeprecatedSymbolUsageInWholeProjectFix.Companion);
QuickFixes.factories.put(ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION, ReplaceJavaAnnotationPositionedArgumentsFix.Companion);
}
}
@@ -0,0 +1,54 @@
/*
* 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
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.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionForFirstParentOfType
import org.jetbrains.kotlin.load.kotlin.getJavaAnnotationCallValueArgumentsThatShouldBeNamed
import org.jetbrains.kotlin.psi.JetAnnotationEntry
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
public class ReplaceJavaAnnotationPositionedArgumentsFix(element: JetAnnotationEntry)
: JetIntentionAction<JetAnnotationEntry>(element), CleanupFix {
override fun getText(): String = "Replace invalid positioned arguments for annotation"
override fun getFamilyName(): String = getText()
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return
val psiFactory = JetPsiFactory(project)
getJavaAnnotationCallValueArgumentsThatShouldBeNamed(resolvedCall).forEach argumentProcessor@{
argument ->
val valueArgument = (argument.value as? ExpressionValueArgument)?.getValueArgument() ?: return@argumentProcessor
val expression = valueArgument.getArgumentExpression() ?: return@argumentProcessor
valueArgument.asElement().replace(psiFactory.createArgument(expression, argument.getKey().getName().asString()))
}
}
companion object : JetSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic) =
diagnostic.createIntentionForFirstParentOfType(::ReplaceJavaAnnotationPositionedArgumentsFix)
}
}
@@ -0,0 +1,7 @@
public @interface JavaAnn {
int value();
String arg1();
Class<?>[] arg2();
Class<?> arg3();
int arg4() default 0;
}
@@ -42,3 +42,5 @@ fun unnecessaryExclExcl(x: String) {
fun unnecessaryCast(x: String) = x as String
fun unnecessaryElvis(x: String) = x ?: ""
JavaAnn(1, "abc", arrayOf(javaClass<Array<Int>>()), javaClass<String>()) class MyClass
@@ -44,3 +44,5 @@ fun unnecessaryExclExcl(x: String) {
fun unnecessaryCast(x: String) = x
fun unnecessaryElvis(x: String) = x
JavaAnn(1, arg1 = "abc", arg2 = arrayOf(Array<Int>::class), arg3 = String::class) class MyClass
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: Only named arguments are available for Java annotations
Ann(1, /*abc*/arg1 = "abc", arg2 = arrayOf(Int::class, Array<Int>::class), arg3 = String::class) class A
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: Only named arguments are available for Java annotations
Ann(1, /*abc*/"abc", arrayOf(Int::class, Array<Int>::class)<caret>, arg3 = String::class) class A
@@ -0,0 +1,7 @@
public @interface Ann {
int value();
String arg1();
Class<?>[] arg2();
Class<?> arg3();
int arg4() default 0;
}
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: No value passed for parameter arg2
Ann(1, arg1 = "abc") class A
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: No value passed for parameter arg2
Ann(1, "abc"<caret>) class A
@@ -0,0 +1,6 @@
public @interface Ann {
int value();
String arg1();
Class<?> arg2();
int arg3() default 0;
}
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: An integer literal does not conform to the expected type kotlin.String
Ann(1, arg1 = 2) class A
@@ -0,0 +1,6 @@
// "Replace invalid positioned arguments for annotation" "true"
// WITH_RUNTIME
// ERROR: Only named arguments are available for Java annotations
// ERROR: An integer literal does not conform to the expected type kotlin.String
Ann(1, 2<caret>) class A
@@ -0,0 +1,5 @@
public @interface Ann {
int value();
String arg1();
int arg2() default 0;
}
@@ -33,7 +33,7 @@ class KotlinCleanupInspectionTest(): JetLightCodeInsightFixtureTestCase() {
public fun testCleanup() {
myFixture.enableInspections(javaClass<KotlinCleanupInspection>())
myFixture.configureByFile("cleanup.kt")
myFixture.configureByFiles("cleanup.kt", "JavaAnn.java")
val project = myFixture.getProject()
val managerEx = InspectionManager.getInstance(project)
@@ -948,6 +948,33 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/migration/javaAnnotationPositionedArguments")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaAnnotationPositionedArguments extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInJavaAnnotationPositionedArguments() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/javaAnnotationPositionedArguments"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
}
@TestMetadata("basicMultiple.before.Main.kt")
public void testBasicMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/javaAnnotationPositionedArguments/basicMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("noValueForArgumentMultiple.before.Main.kt")
public void testNoValueForArgumentMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/javaAnnotationPositionedArguments/noValueForArgumentMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("wrongTypeMultiple.before.Main.kt")
public void testWrongTypeMultiple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/javaAnnotationPositionedArguments/wrongTypeMultiple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/migration/lambdaSyntax")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)