Add "Add getter/setter" quick fix for uninitialized property
#KT-30078 Fixed
This commit is contained in:
committed by
Dmitry Gridin
parent
efcc6f0967
commit
f861b10798
@@ -255,7 +255,10 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createPropertyGetter(expression: KtExpression): KtPropertyAccessor {
|
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 getter = property.getter!!
|
||||||
val bodyExpression = getter.bodyExpression!!
|
val bodyExpression = getter.bodyExpression!!
|
||||||
|
|
||||||
|
|||||||
@@ -16,18 +16,21 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.intentions
|
package org.jetbrains.kotlin.idea.intentions
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.intention.IntentionAction
|
||||||
import com.intellij.codeInsight.intention.LowPriorityAction
|
import com.intellij.codeInsight.intention.LowPriorityAction
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
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.caches.resolve.resolveToDescriptorIfAny
|
||||||
|
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||||
import org.jetbrains.kotlin.idea.refactoring.isAbstract
|
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.idea.util.hasJvmFieldAnnotation
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
|
||||||
abstract class AbstractAddAccessorsIntention(
|
abstract class AbstractAddAccessorsIntention(
|
||||||
private val addGetter: Boolean,
|
private val addGetter: Boolean,
|
||||||
@@ -45,25 +48,56 @@ abstract class AbstractAddAccessorsIntention(
|
|||||||
val descriptor = element.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return null
|
val descriptor = element.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return null
|
||||||
if (descriptor.isExpect) 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 (addSetter && (!element.isVar || element.setter != null)) return null
|
||||||
if (addGetter && ((element.typeReference == null && element.initializer == null) || element.getter != null)) return null
|
if (addGetter && element.getter != null) return null
|
||||||
return element.nameIdentifier?.textRange
|
return if (hasInitializer) element.nameIdentifier?.textRange else element.textRange
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyTo(element: KtProperty, editor: Editor?) {
|
override fun applyTo(element: KtProperty, editor: Editor?) {
|
||||||
|
val hasInitializer = element.hasInitializer()
|
||||||
val psiFactory = KtPsiFactory(element)
|
val psiFactory = KtPsiFactory(element)
|
||||||
if (addGetter) {
|
if (addGetter) {
|
||||||
val expression = psiFactory.createExpression("field")
|
val expression = if (hasInitializer) psiFactory.createExpression("field") else psiFactory.createBlock("TODO()")
|
||||||
val getter = psiFactory.createPropertyGetter(expression)
|
val getter = psiFactory.createPropertyGetter(expression)
|
||||||
if (element.setter != null)
|
val added = if (element.setter != null) {
|
||||||
element.addBefore(getter, element.setter)
|
element.addBefore(getter, element.setter)
|
||||||
else
|
} else {
|
||||||
element.add(getter)
|
element.add(getter)
|
||||||
|
}
|
||||||
|
if (!hasInitializer) {
|
||||||
|
(added as? KtPropertyAccessor)?.bodyBlockExpression?.statements?.firstOrNull()?.let {
|
||||||
|
editor?.caretModel?.moveToOffset(it.startOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (addSetter) {
|
if (addSetter) {
|
||||||
val expression = psiFactory.createBlock("field = value")
|
val expression = if (hasInitializer) psiFactory.createBlock("field = value") else psiFactory.createEmptyBody()
|
||||||
val setter = psiFactory.createPropertySetter(expression)
|
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.ImplementAsConstructorParameter
|
||||||
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
|
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
|
||||||
import org.jetbrains.kotlin.idea.inspections.*
|
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.AddValVarToConstructorParameterAction
|
||||||
import org.jetbrains.kotlin.idea.intentions.ConvertPropertyInitializerToGetterIntention
|
import org.jetbrains.kotlin.idea.intentions.ConvertPropertyInitializerToGetterIntention
|
||||||
import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
|
import org.jetbrains.kotlin.idea.intentions.MoveMemberToCompanionObjectIntention
|
||||||
@@ -603,5 +604,8 @@ class QuickFixRegistrar : QuickFixContributor {
|
|||||||
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix)
|
CONSTANT_EXPECTED_TYPE_MISMATCH.registerFactory(SurroundWithLambdaFix)
|
||||||
|
|
||||||
NO_SET_METHOD.registerFactory(ChangeToMutableCollectionFix)
|
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_BEFORE
|
||||||
// SKIP_ERRORS_AFTER
|
// SKIP_ERRORS_AFTER
|
||||||
var x<caret>
|
var x<caret>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// SKIP_ERRORS_BEFORE
|
|
||||||
// SKIP_ERRORS_AFTER
|
|
||||||
var x
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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: Make 'a' abstract
|
||||||
// ACTION: Move to constructor parameters
|
// ACTION: Move to constructor parameters
|
||||||
// ACTION: Move to constructor
|
// ACTION: Move to constructor
|
||||||
|
// ACTION: Add getter
|
||||||
|
// ACTION: Add getter and setter
|
||||||
|
// ACTION: Add setter
|
||||||
// ERROR: Property must be initialized or be abstract
|
// ERROR: Property must be initialized or be abstract
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
// ACTION: Make 'a' abstract
|
// ACTION: Make 'a' abstract
|
||||||
// ACTION: Move to constructor parameters
|
// ACTION: Move to constructor parameters
|
||||||
// ACTION: Move to constructor
|
// ACTION: Move to constructor
|
||||||
|
// ACTION: Add getter
|
||||||
|
// ACTION: Add getter and setter
|
||||||
|
// ACTION: Add setter
|
||||||
// ERROR: Property must be initialized or be abstract
|
// ERROR: Property must be initialized or be abstract
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
|
|||||||
+13
@@ -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")
|
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("idea/testData/quickfix/addPropertyToSupertype")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user