Add "Add getter/setter" quick fix for uninitialized property

#KT-30078 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-03-19 17:18:20 +09:00
committed by Dmitry Gridin
parent efcc6f0967
commit f861b10798
17 changed files with 161 additions and 16 deletions
@@ -255,7 +255,10 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
}
fun createPropertyGetter(expression: KtExpression): KtPropertyAccessor {
val property = createProperty("val x get() = 1")
val property = if (expression is KtBlockExpression)
createProperty("val x get() {\nreturn 1\n}")
else
createProperty("val x get() = 1")
val getter = property.getter!!
val bodyExpression = getter.bodyExpression!!
@@ -16,18 +16,21 @@
package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.refactoring.isAbstract
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.idea.util.hasJvmFieldAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.startOffset
abstract class AbstractAddAccessorsIntention(
private val addGetter: Boolean,
@@ -45,25 +48,56 @@ abstract class AbstractAddAccessorsIntention(
val descriptor = element.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return null
if (descriptor.isExpect) return null
val hasInitializer = element.hasInitializer()
if (element.typeReference == null && !hasInitializer) return null
if (addSetter && (!element.isVar || element.setter != null)) return null
if (addGetter && ((element.typeReference == null && element.initializer == null) || element.getter != null)) return null
return element.nameIdentifier?.textRange
if (addGetter && element.getter != null) return null
return if (hasInitializer) element.nameIdentifier?.textRange else element.textRange
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val hasInitializer = element.hasInitializer()
val psiFactory = KtPsiFactory(element)
if (addGetter) {
val expression = psiFactory.createExpression("field")
val expression = if (hasInitializer) psiFactory.createExpression("field") else psiFactory.createBlock("TODO()")
val getter = psiFactory.createPropertyGetter(expression)
if (element.setter != null)
val added = if (element.setter != null) {
element.addBefore(getter, element.setter)
else
} else {
element.add(getter)
}
if (!hasInitializer) {
(added as? KtPropertyAccessor)?.bodyBlockExpression?.statements?.firstOrNull()?.let {
editor?.caretModel?.moveToOffset(it.startOffset)
}
}
}
if (addSetter) {
val expression = psiFactory.createBlock("field = value")
val expression = if (hasInitializer) psiFactory.createBlock("field = value") else psiFactory.createEmptyBody()
val setter = psiFactory.createPropertySetter(expression)
element.add(setter)
val added = element.add(setter)
if (!hasInitializer && !addGetter) {
(added as? KtPropertyAccessor)?.bodyBlockExpression?.lBrace?.let {
editor?.caretModel?.moveToOffset(it.startOffset + 1)
}
}
}
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val property = diagnostic.psiElement as? KtProperty ?: return null
return if (property.isVar) {
val getter = property.getter
val setter = property.setter
when {
getter == null && setter == null -> AddPropertyAccessorsIntention()
getter == null -> AddPropertyGetterIntention()
else -> AddPropertySetterIntention()
}
} else {
AddPropertyGetterIntention()
}
}
}
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.diagnostics.Errors.*
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementAsConstructorParameter
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.idea.inspections.*
import org.jetbrains.kotlin.idea.intentions.AbstractAddAccessorsIntention
import org.jetbrains.kotlin.idea.intentions.AddValVarToConstructorParameterAction
import org.jetbrains.kotlin.idea.intentions.ConvertPropertyInitializerToGetterIntention
import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
@@ -603,5 +604,8 @@ class QuickFixRegistrar : QuickFixContributor {
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix)
NO_SET_METHOD.registerFactory(ChangeToMutableCollectionFix)
MUST_BE_INITIALIZED_OR_BE_ABSTRACT.registerFactory(AbstractAddAccessorsIntention)
MUST_BE_INITIALIZED.registerFactory(AbstractAddAccessorsIntention)
}
}
@@ -1,3 +1,4 @@
// IS_APPLICABLE: false
// SKIP_ERRORS_BEFORE
// SKIP_ERRORS_AFTER
var x<caret>
@@ -1,6 +0,0 @@
// SKIP_ERRORS_BEFORE
// SKIP_ERRORS_AFTER
var x
set(value) {
field = value
}
+5
View File
@@ -0,0 +1,5 @@
// "Add getter" "true"
// WITH_RUNTIME
class Test {
val x: Int<caret>
}
@@ -0,0 +1,8 @@
// "Add getter" "true"
// WITH_RUNTIME
class Test {
val x: Int
get() {
<caret>TODO()
}
}
+5
View File
@@ -0,0 +1,5 @@
// "Add getter and setter" "true"
// WITH_RUNTIME
class Test {
var x: Int<caret>
}
@@ -0,0 +1,9 @@
// "Add getter and setter" "true"
// WITH_RUNTIME
class Test {
var x: Int
get() {
<caret>TODO()
}
set(value) {}
}
@@ -0,0 +1,7 @@
// "Add setter" "true"
class Test {
var x: Int<caret>
get() {
return 1
}
}
@@ -0,0 +1,8 @@
// "Add setter" "true"
class Test {
var x: Int
get() {
return 1
}
set(value) {<caret>}
}
@@ -0,0 +1,6 @@
// "Add getter" "true"
// WITH_RUNTIME
class Test {
var x: Int<caret>
set(value) {}
}
@@ -0,0 +1,9 @@
// "Add getter" "true"
// WITH_RUNTIME
class Test {
var x: Int
get() {
<caret>TODO()
}
set(value) {}
}
@@ -3,6 +3,9 @@
// ACTION: Make 'a' abstract
// ACTION: Move to constructor parameters
// ACTION: Move to constructor
// ACTION: Add getter
// ACTION: Add getter and setter
// ACTION: Add setter
// ERROR: Property must be initialized or be abstract
class A {
@@ -4,6 +4,9 @@
// ACTION: Make 'a' abstract
// ACTION: Move to constructor parameters
// ACTION: Move to constructor
// ACTION: Add getter
// ACTION: Add getter and setter
// ACTION: Add setter
// ERROR: Property must be initialized or be abstract
class A {
@@ -221,6 +221,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/addPropertyAccessors")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddPropertyAccessors extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInAddPropertyAccessors() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addPropertyAccessors"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -846,6 +846,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/addPropertyAccessors")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class AddPropertyAccessors extends AbstractQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInAddPropertyAccessors() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addPropertyAccessors"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("val.kt")
public void testVal() throws Exception {
runTest("idea/testData/quickfix/addPropertyAccessors/val.kt");
}
@TestMetadata("var.kt")
public void testVar() throws Exception {
runTest("idea/testData/quickfix/addPropertyAccessors/var.kt");
}
@TestMetadata("varHasGetter.kt")
public void testVarHasGetter() throws Exception {
runTest("idea/testData/quickfix/addPropertyAccessors/varHasGetter.kt");
}
@TestMetadata("varHasSetter.kt")
public void testVarHasSetter() throws Exception {
runTest("idea/testData/quickfix/addPropertyAccessors/varHasSetter.kt");
}
}
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)