From dcae66a72744a4adb7d1c0718c6469b5190c43fd Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Wed, 19 Apr 2017 15:04:20 +0300 Subject: [PATCH] Add quickfix for INAPPLICABLE_JVM_FIELD replaces with 'const' when possible #KT-10981 Fixed --- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 + .../quickfix/ReplaceJvmFieldWithConstFix.kt | 70 +++++++++++++++++++ .../replaceJvmFieldWithConst/class.kt | 8 +++ .../companionInInterface.kt | 7 ++ .../companionInInterface.kt.after | 7 ++ .../replaceJvmFieldWithConst/getter.kt | 8 +++ .../nonConstantInitializer.kt | 6 ++ .../replaceJvmFieldWithConst/nullable.kt | 5 ++ .../replaceJvmFieldWithConst/object.kt | 5 ++ .../replaceJvmFieldWithConst/object.kt.after | 5 ++ .../stringTemplateWithConstants.kt | 4 ++ .../stringTemplateWithConstants.kt.after | 4 ++ .../stringTemplateWithVal.kt | 7 ++ .../replaceJvmFieldWithConst/toplevel.kt | 3 + .../toplevel.kt.after | 3 + .../idea/quickfix/QuickFixTestGenerated.java | 63 +++++++++++++++++ 16 files changed, 207 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceJvmFieldWithConstFix.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/class.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt.after create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/object.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/object.kt.after create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt.after create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt create mode 100644 idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index c95e1341a56..8fa538bd5a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -483,5 +483,7 @@ class QuickFixRegistrar : QuickFixContributor { INVALID_TYPE_OF_ANNOTATION_MEMBER.registerFactory(TypeOfAnnotationMemberFix) ILLEGAL_INLINE_PARAMETER_MODIFIER.registerFactory(AddInlineToFunctionFix) + + INAPPLICABLE_JVM_FIELD.registerFactory(ReplaceJvmFieldWithConstFix) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceJvmFieldWithConstFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceJvmFieldWithConstFix.kt new file mode 100644 index 00000000000..b9533b34cdc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceJvmFieldWithConstFix.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2017 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.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.checkers.ConstModifierChecker +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + + +class ReplaceJvmFieldWithConstFix(annotation: KtAnnotationEntry) : KotlinQuickFixAction(annotation) { + override fun getText(): String = "Replace '@JvmField' with 'const'" + + override fun getFamilyName(): String = text + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val property = element?.getParentOfType(false) ?: return + element?.delete() + property.addModifier(KtTokens.CONST_KEYWORD) + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val annotation = diagnostic.psiElement as? KtAnnotationEntry ?: return null + val property = annotation.getParentOfType(false) ?: return null + val propertyDescriptor = property.descriptor as? PropertyDescriptor ?: return null + if (!ConstModifierChecker.canBeConst(property, property, propertyDescriptor)) { + return null + } + + val initializer = property.initializer ?: return null + if (!initializer.isConstantExpression()) { + return null + } + + return ReplaceJvmFieldWithConstFix(annotation) + } + + private fun KtExpression.isConstantExpression() = + ConstantExpressionEvaluator.getConstant(this, analyze(BodyResolveMode.PARTIAL))?.let { + !it.usesNonConstValAsConstant + } ?: false + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/class.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/class.kt new file mode 100644 index 00000000000..84cd4ee988b --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/class.kt @@ -0,0 +1,8 @@ +// "Replace '@JvmField' with 'const'" "false" +// WITH_RUNTIME +// ERROR: JvmField has no effect on a private property +// ACTION: Move to constructor +// ACTION: Specify type explicitly +class Foo { + @JvmField private val a = "Lorem ipsum" +} diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt new file mode 100644 index 00000000000..52b5e683c0a --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt @@ -0,0 +1,7 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +interface IFace { + companion object { + @JvmField val a = "Lorem ipsum" + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt.after b/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt.after new file mode 100644 index 00000000000..3276a2b85ca --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt.after @@ -0,0 +1,7 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +interface IFace { + companion object { + const val a = "Lorem ipsum" + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt new file mode 100644 index 00000000000..b43eab61048 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt @@ -0,0 +1,8 @@ +// "Replace '@JvmField' with 'const'" "false" +// WITH_RUNTIME +// ERROR: This annotation is not applicable to target 'top level property without backing field or delegate' +// ACTION: Make internal +// ACTION: Make private +// ACTION: Remove explicit type specification +@JvmField val number: Int + get() = 42 \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt new file mode 100644 index 00000000000..91a20f41a61 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt @@ -0,0 +1,6 @@ +// "Replace '@JvmField' with 'const'" "false" +// WITH_RUNTIME +// ERROR: JvmField has no effect on a private property +// ACTION: Remove explicit type specification +fun getText() = "" +@JvmField private val text: String = getText() \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt new file mode 100644 index 00000000000..890a529f668 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt @@ -0,0 +1,5 @@ +// "Replace '@JvmField' with 'const'" "false" +// WITH_RUNTIME +// ERROR: JvmField has no effect on a private property +// ACTION: Remove explicit type specification +@JvmField private val number: Int? = 42 \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt new file mode 100644 index 00000000000..ae579ad4f23 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt @@ -0,0 +1,5 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +object Foo { + @JvmField private val a = "Lorem ipsum" +} diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt.after b/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt.after new file mode 100644 index 00000000000..4e5c3bccb58 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/object.kt.after @@ -0,0 +1,5 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +object Foo { + const private val a = "Lorem ipsum" +} diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt new file mode 100644 index 00000000000..bd4cab6a7a0 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt @@ -0,0 +1,4 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +const val three = 3 +@JvmField private val text = "${2 + three}" \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt.after b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt.after new file mode 100644 index 00000000000..400bd487cfb --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt.after @@ -0,0 +1,4 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +const val three = 3 +const private val text = "${2 + three}" \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt new file mode 100644 index 00000000000..22277bde298 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt @@ -0,0 +1,7 @@ +// "Replace '@JvmField' with 'const'" "false" +// WITH_RUNTIME +// ERROR: JvmField has no effect on a private property +// ACTION: Add 'const' modifier +// ACTION: Specify type explicitly +val three = 3 +@JvmField private val text = "${2 + three}" \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt b/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt new file mode 100644 index 00000000000..f81ac0337ec --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt @@ -0,0 +1,3 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +@JvmField private val number: Int = 42 \ No newline at end of file diff --git a/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt.after b/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt.after new file mode 100644 index 00000000000..c655fde1c70 --- /dev/null +++ b/idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt.after @@ -0,0 +1,3 @@ +// "Replace '@JvmField' with 'const'" "true" +// WITH_RUNTIME +const private val number: Int = 42 \ 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 ac484a5fb9b..6e0df9d9bc9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -8187,6 +8187,69 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/replaceJvmFieldWithConst") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceJvmFieldWithConst extends AbstractQuickFixTest { + public void testAllFilesPresentInReplaceJvmFieldWithConst() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/replaceJvmFieldWithConst"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("class.kt") + public void testClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/class.kt"); + doTest(fileName); + } + + @TestMetadata("companionInInterface.kt") + public void testCompanionInInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt"); + doTest(fileName); + } + + @TestMetadata("getter.kt") + public void testGetter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt"); + doTest(fileName); + } + + @TestMetadata("nonConstantInitializer.kt") + public void testNonConstantInitializer() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt"); + doTest(fileName); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt"); + doTest(fileName); + } + + @TestMetadata("object.kt") + public void testObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/object.kt"); + doTest(fileName); + } + + @TestMetadata("stringTemplateWithConstants.kt") + public void testStringTemplateWithConstants() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt"); + doTest(fileName); + } + + @TestMetadata("stringTemplateWithVal.kt") + public void testStringTemplateWithVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt"); + doTest(fileName); + } + + @TestMetadata("toplevel.kt") + public void testToplevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/simplifyComparison") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)