Skip to content

Commit dcae66a

Browse files
committed
Add quickfix for INAPPLICABLE_JVM_FIELD
replaces with 'const' when possible #KT-10981 Fixed
1 parent 199bff7 commit dcae66a

File tree

16 files changed

+207
-0
lines changed

16 files changed

+207
-0
lines changed

idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,5 +483,7 @@ class QuickFixRegistrar : QuickFixContributor {
483483
INVALID_TYPE_OF_ANNOTATION_MEMBER.registerFactory(TypeOfAnnotationMemberFix)
484484

485485
ILLEGAL_INLINE_PARAMETER_MODIFIER.registerFactory(AddInlineToFunctionFix)
486+
487+
INAPPLICABLE_JVM_FIELD.registerFactory(ReplaceJvmFieldWithConstFix)
486488
}
487489
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.quickfix
18+
19+
import com.intellij.codeInsight.intention.IntentionAction
20+
import com.intellij.openapi.editor.Editor
21+
import com.intellij.openapi.project.Project
22+
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
23+
import org.jetbrains.kotlin.diagnostics.Diagnostic
24+
import org.jetbrains.kotlin.idea.caches.resolve.analyze
25+
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
26+
import org.jetbrains.kotlin.lexer.KtTokens
27+
import org.jetbrains.kotlin.psi.KtAnnotationEntry
28+
import org.jetbrains.kotlin.psi.KtExpression
29+
import org.jetbrains.kotlin.psi.KtFile
30+
import org.jetbrains.kotlin.psi.KtProperty
31+
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
32+
import org.jetbrains.kotlin.resolve.checkers.ConstModifierChecker
33+
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
34+
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
35+
36+
37+
class ReplaceJvmFieldWithConstFix(annotation: KtAnnotationEntry) : KotlinQuickFixAction<KtAnnotationEntry>(annotation) {
38+
override fun getText(): String = "Replace '@JvmField' with 'const'"
39+
40+
override fun getFamilyName(): String = text
41+
42+
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
43+
val property = element?.getParentOfType<KtProperty>(false) ?: return
44+
element?.delete()
45+
property.addModifier(KtTokens.CONST_KEYWORD)
46+
}
47+
48+
companion object : KotlinSingleIntentionActionFactory() {
49+
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
50+
val annotation = diagnostic.psiElement as? KtAnnotationEntry ?: return null
51+
val property = annotation.getParentOfType<KtProperty>(false) ?: return null
52+
val propertyDescriptor = property.descriptor as? PropertyDescriptor ?: return null
53+
if (!ConstModifierChecker.canBeConst(property, property, propertyDescriptor)) {
54+
return null
55+
}
56+
57+
val initializer = property.initializer ?: return null
58+
if (!initializer.isConstantExpression()) {
59+
return null
60+
}
61+
62+
return ReplaceJvmFieldWithConstFix(annotation)
63+
}
64+
65+
private fun KtExpression.isConstantExpression() =
66+
ConstantExpressionEvaluator.getConstant(this, analyze(BodyResolveMode.PARTIAL))?.let {
67+
!it.usesNonConstValAsConstant
68+
} ?: false
69+
}
70+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// "Replace '@JvmField' with 'const'" "false"
2+
// WITH_RUNTIME
3+
// ERROR: JvmField has no effect on a private property
4+
// ACTION: Move to constructor
5+
// ACTION: Specify type explicitly
6+
class Foo {
7+
<caret>@JvmField private val a = "Lorem ipsum"
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
interface IFace {
4+
companion object {
5+
<caret>@JvmField val a = "Lorem ipsum"
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
interface IFace {
4+
companion object {
5+
const val a = "Lorem ipsum"
6+
}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// "Replace '@JvmField' with 'const'" "false"
2+
// WITH_RUNTIME
3+
// ERROR: This annotation is not applicable to target 'top level property without backing field or delegate'
4+
// ACTION: Make internal
5+
// ACTION: Make private
6+
// ACTION: Remove explicit type specification
7+
<caret>@JvmField val number: Int
8+
get() = 42
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace '@JvmField' with 'const'" "false"
2+
// WITH_RUNTIME
3+
// ERROR: JvmField has no effect on a private property
4+
// ACTION: Remove explicit type specification
5+
fun getText() = ""
6+
<caret>@JvmField private val text: String = getText()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace '@JvmField' with 'const'" "false"
2+
// WITH_RUNTIME
3+
// ERROR: JvmField has no effect on a private property
4+
// ACTION: Remove explicit type specification
5+
<caret>@JvmField private val number: Int? = 42
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
object Foo {
4+
<caret>@JvmField private val a = "Lorem ipsum"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
object Foo {
4+
const private val a = "Lorem ipsum"
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
const val three = 3
4+
<caret>@JvmField private val text = "${2 + three}"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
const val three = 3
4+
const private val text = "${2 + three}"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace '@JvmField' with 'const'" "false"
2+
// WITH_RUNTIME
3+
// ERROR: JvmField has no effect on a private property
4+
// ACTION: Add 'const' modifier
5+
// ACTION: Specify type explicitly
6+
val three = 3
7+
<caret>@JvmField private val text = "${2 + three}"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
<caret>@JvmField private val number: Int = 42
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// "Replace '@JvmField' with 'const'" "true"
2+
// WITH_RUNTIME
3+
const private val number: Int = 42

idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8187,6 +8187,69 @@ public void testUnqualifiedPropertyRefWithPackageError() throws Exception {
81878187
}
81888188
}
81898189

8190+
@TestMetadata("idea/testData/quickfix/replaceJvmFieldWithConst")
8191+
@TestDataPath("$PROJECT_ROOT")
8192+
@RunWith(JUnit3RunnerWithInners.class)
8193+
public static class ReplaceJvmFieldWithConst extends AbstractQuickFixTest {
8194+
public void testAllFilesPresentInReplaceJvmFieldWithConst() throws Exception {
8195+
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/replaceJvmFieldWithConst"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
8196+
}
8197+
8198+
@TestMetadata("class.kt")
8199+
public void testClass() throws Exception {
8200+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/class.kt");
8201+
doTest(fileName);
8202+
}
8203+
8204+
@TestMetadata("companionInInterface.kt")
8205+
public void testCompanionInInterface() throws Exception {
8206+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/companionInInterface.kt");
8207+
doTest(fileName);
8208+
}
8209+
8210+
@TestMetadata("getter.kt")
8211+
public void testGetter() throws Exception {
8212+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/getter.kt");
8213+
doTest(fileName);
8214+
}
8215+
8216+
@TestMetadata("nonConstantInitializer.kt")
8217+
public void testNonConstantInitializer() throws Exception {
8218+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/nonConstantInitializer.kt");
8219+
doTest(fileName);
8220+
}
8221+
8222+
@TestMetadata("nullable.kt")
8223+
public void testNullable() throws Exception {
8224+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/nullable.kt");
8225+
doTest(fileName);
8226+
}
8227+
8228+
@TestMetadata("object.kt")
8229+
public void testObject() throws Exception {
8230+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/object.kt");
8231+
doTest(fileName);
8232+
}
8233+
8234+
@TestMetadata("stringTemplateWithConstants.kt")
8235+
public void testStringTemplateWithConstants() throws Exception {
8236+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithConstants.kt");
8237+
doTest(fileName);
8238+
}
8239+
8240+
@TestMetadata("stringTemplateWithVal.kt")
8241+
public void testStringTemplateWithVal() throws Exception {
8242+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/stringTemplateWithVal.kt");
8243+
doTest(fileName);
8244+
}
8245+
8246+
@TestMetadata("toplevel.kt")
8247+
public void testToplevel() throws Exception {
8248+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceJvmFieldWithConst/toplevel.kt");
8249+
doTest(fileName);
8250+
}
8251+
}
8252+
81908253
@TestMetadata("idea/testData/quickfix/simplifyComparison")
81918254
@TestDataPath("$PROJECT_ROOT")
81928255
@RunWith(JUnit3RunnerWithInners.class)

0 commit comments

Comments
 (0)