"introduce backing property" intention
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
class Foo {
|
||||
private var <spot>_x</spot> = ""
|
||||
var x: String
|
||||
get() = _x
|
||||
set(value) {
|
||||
_x = value
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class Foo {
|
||||
var <spot>x</spot> = ""
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention creates a backing property for the specified property.
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+12
@@ -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
@@ -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) → kotlin.Int', which is not a subtype of overridden<br/><b>public</b> <b>abstract</b> <b>val</b> x: (kotlin.String) → kotlin.Int <i>defined in</i> A</html>
|
||||
interface A {
|
||||
val x: (String) -> Int
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// "Specify type explicitly" "false"
|
||||
// ACTION: Convert property to function
|
||||
// ACTION: Introduce backing property
|
||||
// ERROR: Unresolved reference: foo
|
||||
|
||||
class A() {
|
||||
|
||||
+1
@@ -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) → kotlin.Int', which is not a subtype of overridden<br/><b>public</b> <b>abstract</b> <b>val</b> x: (kotlin.String) → [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");
|
||||
|
||||
Reference in New Issue
Block a user