Add quickfix for INAPPLICABLE_JVM_FIELD

replaces with 'const' when possible

#KT-10981 Fixed
This commit is contained in:
Vyacheslav Gerasimov
2017-04-19 15:04:20 +03:00
parent 199bff7e70
commit dcae66a727
16 changed files with 207 additions and 0 deletions
@@ -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)
}
}
@@ -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<KtAnnotationEntry>(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<KtProperty>(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<KtProperty>(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
}
}
@@ -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 {
<caret>@JvmField private val a = "Lorem ipsum"
}
@@ -0,0 +1,7 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
interface IFace {
companion object {
<caret>@JvmField val a = "Lorem ipsum"
}
}
@@ -0,0 +1,7 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
interface IFace {
companion object {
const val a = "Lorem ipsum"
}
}
@@ -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
<caret>@JvmField val number: Int
get() = 42
@@ -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() = ""
<caret>@JvmField private val text: String = getText()
@@ -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
<caret>@JvmField private val number: Int? = 42
@@ -0,0 +1,5 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
object Foo {
<caret>@JvmField private val a = "Lorem ipsum"
}
@@ -0,0 +1,5 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
object Foo {
const private val a = "Lorem ipsum"
}
@@ -0,0 +1,4 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
const val three = 3
<caret>@JvmField private val text = "${2 + three}"
@@ -0,0 +1,4 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
const val three = 3
const private val text = "${2 + three}"
@@ -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
<caret>@JvmField private val text = "${2 + three}"
@@ -0,0 +1,3 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
<caret>@JvmField private val number: Int = 42
@@ -0,0 +1,3 @@
// "Replace '@JvmField' with 'const'" "true"
// WITH_RUNTIME
const private val number: Int = 42
@@ -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)