diff --git a/compiler/frontend/src/org/jetbrains/kotlin/parsing/JetParserDefinition.java b/compiler/frontend/src/org/jetbrains/kotlin/parsing/JetParserDefinition.java
index f03dbb9dd49..71c408f870d 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/parsing/JetParserDefinition.java
+++ b/compiler/frontend/src/org/jetbrains/kotlin/parsing/JetParserDefinition.java
@@ -123,6 +123,10 @@ public class JetParserDefinition implements ParserDefinition {
@Override
public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode astNode, ASTNode astNode1) {
+ IElementType rightTokenType = astNode1.getElementType();
+ if (rightTokenType == JetTokens.GET_KEYWORD || rightTokenType == JetTokens.SET_KEYWORD) {
+ return SpaceRequirements.MUST_LINE_BREAK;
+ }
return SpaceRequirements.MAY;
}
}
diff --git a/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/after.kt.template b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/after.kt.template
new file mode 100644
index 00000000000..96534460ac3
--- /dev/null
+++ b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/after.kt.template
@@ -0,0 +1,8 @@
+class Foo {
+ private var _x = ""
+ var x: String
+ get() = _x
+ set(value) {
+ _x = value
+ }
+}
diff --git a/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/before.kt.template b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/before.kt.template
new file mode 100644
index 00000000000..959c40828dc
--- /dev/null
+++ b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/before.kt.template
@@ -0,0 +1,3 @@
+class Foo {
+ var x = ""
+}
diff --git a/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/description.html b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/description.html
new file mode 100644
index 00000000000..ee39b27aa57
--- /dev/null
+++ b/idea/resources/intentionDescriptions/IntroduceBackingPropertyIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention creates a backing property for the specified property.
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 73595400b8a..d58a66d7675 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -1028,6 +1028,11 @@
Kotlin
+
+ org.jetbrains.kotlin.idea.intentions.IntroduceBackingPropertyIntention
+ Kotlin
+
+
(javaClass(), "Introduce backing property") {
+ override fun isApplicableTo(element: JetProperty, caretOffset: Int): Boolean {
+ if (!canIntroduceBackingProperty(element)) return false
+ var elementAtCaret = element.containingFile.findElementAt(caretOffset)
+ if (elementAtCaret is PsiWhiteSpace) {
+ elementAtCaret = element.containingFile.findElementAt(caretOffset - 1)
+ }
+ return elementAtCaret == element.nameIdentifier || elementAtCaret == element.valOrVarKeyword
+ }
+
+ override fun applyTo(element: JetProperty, editor: Editor) {
+ introduceBackingProperty(element)
+ }
+
+ companion object {
+ public fun canIntroduceBackingProperty(element: JetProperty): Boolean {
+ val name = element.name ?: return false
+ if (name.startsWith('_')) return false
+
+ if (element.hasDelegate() || element.receiverTypeReference != null) return false
+
+ val containingClass = element.getStrictParentOfType() ?: return false
+ return containingClass.declarations.none { it is JetProperty && it.name == "_" + name }
+ }
+
+ fun introduceBackingProperty(element: JetProperty) {
+ createBackingProperty(element)
+
+ val type = SpecifyTypeExplicitlyIntention.getTypeForDeclaration(element)
+ SpecifyTypeExplicitlyIntention.addTypeAnnotation(null, element, type)
+
+ val getter = element.getter
+ if (getter == null) {
+ createGetter(element)
+ }
+ else {
+ replaceFieldReferences(getter, element.name!!)
+ }
+
+ if (element.isVar) {
+ val setter = element.setter
+ if (setter == null) {
+ createSetter(element)
+ }
+ else {
+ replaceFieldReferences(setter, element.name!!)
+ }
+ }
+
+ element.removeInitializer()
+
+ replaceBackingFieldReferences(element)
+ }
+
+ private fun createGetter(element: JetProperty) {
+ val body = "get() = _${element.name}"
+ val newGetter = JetPsiFactory(element).createProperty("val x $body").getter!!
+ element.add(newGetter)
+ }
+
+ private fun createSetter(element: JetProperty) {
+ val body = "set(value) { _${element.name} = value }"
+ val newSetter = JetPsiFactory(element).createProperty("val x $body").setter!!
+ element.add(newSetter)
+ }
+
+ private fun JetProperty.removeInitializer() {
+ val initializer = initializer
+ if (initializer != null) {
+ val eq = initializer.prevLeaf { it.node.elementType == JetTokens.EQ }
+ if (eq != null) {
+ initializer.parent.deleteChildRange(eq, initializer)
+ }
+ }
+ }
+
+ private fun createBackingProperty(element: JetProperty) {
+ val backingPropertyText = StringBuilder {
+ append("private ")
+ append(element.valOrVarKeyword.text)
+ append(" _")
+ append(element.name)
+ val typeRef = element.typeReference
+ if (typeRef != null) {
+ append(": ")
+ append(typeRef.text)
+ }
+
+ val initializer = element.initializer
+ if (initializer != null) {
+ append(" = ")
+ append(initializer.text)
+ }
+ }.toString()
+
+ val backingProp = JetPsiFactory(element).createProperty(backingPropertyText)
+ element.parent.addBefore(backingProp, element)
+ }
+
+ private fun replaceFieldReferences(element: JetElement, propertyName: String) {
+ val bindingContext = element.analyze()
+ element.acceptChildren(object : JetTreeVisitorVoid() {
+ override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
+ val target = bindingContext.get(BindingContext.REFERENCE_TARGET, expression)
+ if (target is SyntheticFieldDescriptor) {
+ expression.replace(JetPsiFactory(element).createSimpleName("_$propertyName"))
+ }
+ }
+ })
+ }
+
+ private fun replaceBackingFieldReferences(prop: JetProperty) {
+ val containingClass = prop.getStrictParentOfType()!!
+ ReferencesSearch.search(prop, LocalSearchScope(containingClass)).forEach {
+ val element = it.element as? JetNameReferenceExpression
+ if (element != null && element.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER) {
+ element.replace(JetPsiFactory(element).createSimpleName("_${prop.name}"))
+ }
+ }
+ }
+ }
+}
+
+class IntroduceBackingPropertyFix(prop: JetProperty): JetIntentionAction(prop) {
+ override fun getText(): String = "Introduce backing property"
+ override fun getFamilyName(): String = getText()
+
+ override fun invoke(project: Project, editor: Editor?, file: JetFile) {
+ IntroduceBackingPropertyIntention.introduceBackingProperty(element)
+ }
+
+ companion object : JetSingleIntentionActionFactory() {
+ override fun createAction(diagnostic: Diagnostic): IntentionAction? {
+ val resolveResult = diagnostic.psiElement.reference?.resolve()
+ if (resolveResult is JetProperty &&
+ IntroduceBackingPropertyIntention.canIntroduceBackingProperty(resolveResult)) {
+ return IntroduceBackingPropertyFix(resolveResult)
+ }
+ return null
+ }
+ }
+}
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt
index 7263b7757a1..584046d2c1d 100644
--- a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyIntention.kt
@@ -22,7 +22,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
-import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
@@ -33,7 +32,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
-import java.util.ArrayList
+import java.util.*
public class SpecifyTypeExplicitlyIntention : JetSelfTargetingIntention(javaClass(), "Specify type explicitly"), LowPriorityAction {
override fun isApplicableTo(element: JetCallableDeclaration, caretOffset: Int): Boolean {
@@ -85,7 +84,7 @@ public class SpecifyTypeExplicitlyIntention : JetSelfTargetingIntentionx = ""
+ get() = $x + "!"
+ set(value) { $x = value + "!" }
+
+ fun foo(): String {
+ return $x
+ }
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/backingFieldRef.kt.after b/idea/testData/intentions/introduceBackingProperty/backingFieldRef.kt.after
new file mode 100644
index 00000000000..0c0039698e4
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/backingFieldRef.kt.after
@@ -0,0 +1,10 @@
+class Foo {
+ private var _x = ""
+ var x: String
+ get() = _x + "!"
+ set(value) { _x = value + "!" }
+
+ fun foo(): String {
+ return _x
+ }
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/simpleVal.kt b/idea/testData/intentions/introduceBackingProperty/simpleVal.kt
new file mode 100644
index 00000000000..cb20184c3fe
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/simpleVal.kt
@@ -0,0 +1,3 @@
+class Foo {
+ val x = ""
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/simpleVal.kt.after b/idea/testData/intentions/introduceBackingProperty/simpleVal.kt.after
new file mode 100644
index 00000000000..0ba7432c705
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/simpleVal.kt.after
@@ -0,0 +1,5 @@
+class Foo {
+ private val _x = ""
+ val x: String
+ get() = _x
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/simpleVar.kt b/idea/testData/intentions/introduceBackingProperty/simpleVar.kt
new file mode 100644
index 00000000000..8f5c84dc172
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/simpleVar.kt
@@ -0,0 +1,3 @@
+class Foo {
+ var x = ""
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/simpleVar.kt.after b/idea/testData/intentions/introduceBackingProperty/simpleVar.kt.after
new file mode 100644
index 00000000000..c73aa83d421
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/simpleVar.kt.after
@@ -0,0 +1,8 @@
+class Foo {
+ private var _x = ""
+ var x: String
+ get() = _x
+ set(value) {
+ _x = value
+ }
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt b/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt
new file mode 100644
index 00000000000..63d51a508fc
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt
@@ -0,0 +1,4 @@
+class Foo {
+ val x = ""
+ get() = field + "!"
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt.after b/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt.after
new file mode 100644
index 00000000000..588abd381e4
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt.after
@@ -0,0 +1,5 @@
+class Foo {
+ private val _x = ""
+ val x: String
+ get() = _x + "!"
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt b/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt
new file mode 100644
index 00000000000..2b9ca2dd3bf
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt
@@ -0,0 +1,5 @@
+class Foo {
+ var x = ""
+ get() = field + "!"
+ set(value) { field = value + "!" }
+}
diff --git a/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt.after b/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt.after
new file mode 100644
index 00000000000..606adbb751c
--- /dev/null
+++ b/idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt.after
@@ -0,0 +1,6 @@
+class Foo {
+ private var _x = ""
+ var x: String
+ get() = _x + "!"
+ set(value) { _x = value + "!" }
+}
diff --git a/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt b/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt
new file mode 100644
index 00000000000..9531d94def6
--- /dev/null
+++ b/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt
@@ -0,0 +1,11 @@
+// "Introduce backing property" "true"
+
+class Foo {
+ var x = ""
+ get() = $x + "!"
+ set(value) { $x = value + "!" }
+
+ fun foo(): String {
+ return $x
+ }
+}
diff --git a/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt.after b/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt.after
new file mode 100644
index 00000000000..64c7fcb1bc4
--- /dev/null
+++ b/idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt.after
@@ -0,0 +1,12 @@
+// "Introduce backing property" "true"
+
+class Foo {
+ private var _x = ""
+ var x: String
+ get() = _x + "!"
+ set(value) { _x = value + "!" }
+
+ fun foo(): String {
+ return _x
+ }
+}
diff --git a/idea/testData/quickfix/override/typeMismatchOnOverride/cantChangeMultipleOverriddenPropertiesTypes.kt b/idea/testData/quickfix/override/typeMismatchOnOverride/cantChangeMultipleOverriddenPropertiesTypes.kt
index 9ade4150f35..5e92b8cdb87 100644
--- a/idea/testData/quickfix/override/typeMismatchOnOverride/cantChangeMultipleOverriddenPropertiesTypes.kt
+++ b/idea/testData/quickfix/override/typeMismatchOnOverride/cantChangeMultipleOverriddenPropertiesTypes.kt
@@ -1,5 +1,6 @@
// "Change 'A.x' type to '(Int) -> Int'" "false"
// ACTION: Change 'C.x' type to '(String) -> Int'
+// ACTION: Introduce backing property
// ERROR: Return type is '(kotlin.Int) → kotlin.Int', which is not a subtype of overridden
public abstract val x: (kotlin.String) → kotlin.Int defined in A
interface A {
val x: (String) -> Int
diff --git a/idea/testData/quickfix/typeAddition/noAddErrorType.kt b/idea/testData/quickfix/typeAddition/noAddErrorType.kt
index 642c383c0f9..13de5200f32 100644
--- a/idea/testData/quickfix/typeAddition/noAddErrorType.kt
+++ b/idea/testData/quickfix/typeAddition/noAddErrorType.kt
@@ -1,5 +1,6 @@
// "Specify type explicitly" "false"
// ACTION: Convert property to function
+// ACTION: Introduce backing property
// ERROR: Unresolved reference: foo
class A() {
diff --git a/idea/testData/quickfix/typeMismatch/dontChangeOverriddenPropertyTypeToErrorType.kt b/idea/testData/quickfix/typeMismatch/dontChangeOverriddenPropertyTypeToErrorType.kt
index 753ae956121..46bba04c05b 100644
--- a/idea/testData/quickfix/typeMismatch/dontChangeOverriddenPropertyTypeToErrorType.kt
+++ b/idea/testData/quickfix/typeMismatch/dontChangeOverriddenPropertyTypeToErrorType.kt
@@ -1,5 +1,6 @@
// "Change 'B.x' type to '(String) -> [ERROR : Ay]'" "false"
// ACTION: Change 'A.x' type to '(Int) -> Int'
+// ACTION: Introduce backing propertty
// ERROR: Return type is '(kotlin.Int) → kotlin.Int', which is not a subtype of overridden
public abstract val x: (kotlin.String) → [ERROR : Ay] defined in A
// ERROR: Unresolved reference: Ay
interface A {
diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
index 1b4283b0ead..c589899115b 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java
@@ -5173,6 +5173,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
+ @TestMetadata("idea/testData/intentions/introduceBackingProperty")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class IntroduceBackingProperty extends AbstractIntentionTest {
+ public void testAllFilesPresentInIntroduceBackingProperty() throws Exception {
+ JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/introduceBackingProperty"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
+ }
+
+ @TestMetadata("backingFieldRef.kt")
+ public void testBackingFieldRef() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/introduceBackingProperty/backingFieldRef.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simpleVal.kt")
+ public void testSimpleVal() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/introduceBackingProperty/simpleVal.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("simpleVar.kt")
+ public void testSimpleVar() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/introduceBackingProperty/simpleVar.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("valWithAccessor.kt")
+ public void testValWithAccessor() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/introduceBackingProperty/valWithAccessor.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("varWithAccessor.kt")
+ public void testVarWithAccessor() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/introduceBackingProperty/varWithAccessor.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/intentions/invertIfCondition")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
index c576996b70f..a58b13ce1e7 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java
@@ -4045,6 +4045,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/backingFieldSyntax"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}
+ @TestMetadata("introduceBackingField.kt")
+ public void testIntroduceBackingField() throws Exception {
+ String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/backingFieldSyntax/introduceBackingField.kt");
+ doTest(fileName);
+ }
+
@TestMetadata("usage.kt")
public void testUsage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/backingFieldSyntax/usage.kt");