"introduce backing property" intention

This commit is contained in:
Dmitry Jemerov
2015-09-23 22:00:36 +02:00
parent dadef12723
commit e1acc8744d
27 changed files with 337 additions and 4 deletions
@@ -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;
}
}
@@ -0,0 +1,8 @@
class Foo {
private var <spot>_x</spot> = ""
var x: String
get() = _x
set(value) {
_x = value
}
}
@@ -0,0 +1,3 @@
class Foo {
var <spot>x</spot> = ""
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention creates a backing property for the specified property.
</body>
</html>
+5
View File
@@ -1028,6 +1028,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.IntroduceBackingPropertyIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
groupName="Kotlin"
@@ -79,7 +79,6 @@ public class ConvertFunctionToPropertyIntention : JetSelfTargetingIntention<JetN
if (insertAfter != null) {
function.addAfter(psiFactory.createParameterList("()"), insertAfter)
function.addAfter(propertySample.getGetter()!!.getNamePlaceholder(), insertAfter)
function.addAfter(psiFactory.createNewLine(), insertAfter)
}
val property = originalFunction.replace(psiFactory.createProperty(function.getText())) as JetProperty
@@ -0,0 +1,178 @@
/*
* Copyright 2010-2015 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.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
import org.jetbrains.kotlin.resolve.BindingContext
class IntroduceBackingPropertyIntention(): JetSelfTargetingIntention<JetProperty>(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<JetClassOrObject>() ?: 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<JetClassOrObject>()!!
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<JetProperty>(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
}
}
}
@@ -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<JetCallableDeclaration>(javaClass(), "Specify type explicitly"), LowPriorityAction {
override fun isApplicableTo(element: JetCallableDeclaration, caretOffset: Int): Boolean {
@@ -85,7 +84,7 @@ public class SpecifyTypeExplicitlyIntention : JetSelfTargetingIntention<JetCalla
addTypeAnnotationWithTemplate(editor, declaration, exprType)
}
else {
declaration.setType(KotlinBuiltIns.getInstance().getAnyType())
declaration.setType(exprType)
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors.*
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.idea.inspections.AddReflectionQuickFix
import org.jetbrains.kotlin.idea.intentions.IntroduceBackingPropertyFix
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromCallWithConstructorCalleeActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromConstructorCallActionFactory
@@ -322,5 +323,6 @@ public class QuickFixRegistrar : QuickFixContributor {
BACKING_FIELD_SYNTAX_DEPRECATED.registerFactory(MigrateBackingFieldSyntaxFix)
BACKING_FIELD_USAGE_DEPRECATED.registerFactory(MigrateBackingFieldUsageFix)
BACKING_FIELD_USAGE_DEPRECATED.registerFactory(IntroduceBackingPropertyFix)
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.IntroduceBackingPropertyIntention
@@ -0,0 +1,9 @@
class Foo {
var <caret>x = ""
get() = $x + "!"
set(value) { $x = value + "!" }
fun foo(): String {
return $x
}
}
@@ -0,0 +1,10 @@
class Foo {
private var _x = ""
var x: String
get() = _x + "!"
set(value) { _x = value + "!" }
fun foo(): String {
return _x
}
}
@@ -0,0 +1,3 @@
class Foo {
val <caret>x = ""
}
@@ -0,0 +1,5 @@
class Foo {
private val _x = ""
val x: String
get() = _x
}
@@ -0,0 +1,3 @@
class Foo {
var <caret>x = ""
}
@@ -0,0 +1,8 @@
class Foo {
private var _x = ""
var x: String
get() = _x
set(value) {
_x = value
}
}
@@ -0,0 +1,4 @@
class Foo {
val <caret>x = ""
get() = field + "!"
}
@@ -0,0 +1,5 @@
class Foo {
private val _x = ""
val x: String
get() = _x + "!"
}
@@ -0,0 +1,5 @@
class Foo {
var <caret>x = ""
get() = field + "!"
set(value) { field = value + "!" }
}
@@ -0,0 +1,6 @@
class Foo {
private var _x = ""
var x: String
get() = _x + "!"
set(value) { _x = value + "!" }
}
@@ -0,0 +1,11 @@
// "Introduce backing property" "true"
class Foo {
var x = ""
get() = $x + "!"
set(value) { $x = value + "!" }
fun foo(): String {
return $<caret>x
}
}
@@ -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
}
}
@@ -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: <html>Return type is '(kotlin.Int) &rarr; kotlin.Int', which is not a subtype of overridden<br/><b>public</b> <b>abstract</b> <b>val</b> x: (kotlin.String) &rarr; kotlin.Int <i>defined in</i> A</html>
interface A {
val x: (String) -> Int
+1
View File
@@ -1,5 +1,6 @@
// "Specify type explicitly" "false"
// ACTION: Convert property to function
// ACTION: Introduce backing property
// ERROR: Unresolved reference: foo
class A() {
@@ -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: <html>Return type is '(kotlin.Int) &rarr; kotlin.Int', which is not a subtype of overridden<br/><b>public</b> <b>abstract</b> <b>val</b> x: (kotlin.String) &rarr; [ERROR : Ay] <i>defined in</i> A</html>
// ERROR: Unresolved reference: Ay
interface A {
@@ -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)
@@ -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");