From 4e7236529c79f25d80f1c0ed962e2bb9e74a32c1 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 24 Sep 2015 19:39:54 +0200 Subject: [PATCH] "move assignment to initializer" quickfix for assigning to properties in constructors/init blocks --- .../jetbrains/kotlin/psi/psiUtil/psiUtils.kt | 14 ++- .../after.kt.template | 3 + .../before.kt.template | 7 ++ .../description.html | 5 ++ idea/src/META-INF/plugin.xml | 5 ++ .../IntroduceBackingPropertyIntention.kt | 13 +-- .../MoveAssignmentToInitializerIntention.kt | 86 +++++++++++++++++++ .../moveAssignmentToInitializer/.intention | 1 + .../backingField.kt | 9 ++ .../backingField.kt.after | 8 ++ .../moveAssignmentToInitializer/comment.kt | 8 ++ .../comment.kt.after | 7 ++ .../deleteInitBlock.kt | 7 ++ .../deleteInitBlock.kt.after | 4 + .../moveAssignmentToInitializer/simple.kt | 9 ++ .../simple.kt.after | 8 ++ .../moveAssignmentToInitializer/usedLocal.kt | 9 ++ .../intentions/IntentionTestGenerated.java | 39 +++++++++ 18 files changed, 226 insertions(+), 16 deletions(-) create mode 100644 idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/description.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/MoveAssignmentToInitializerIntention.kt create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/.intention create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/backingField.kt create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/backingField.kt.after create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/comment.kt create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/comment.kt.after create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt.after create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/simple.kt create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/simple.kt.after create mode 100644 idea/testData/intentions/moveAssignmentToInitializer/usedLocal.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index 38d2aebabb9..81efbdee1dc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -185,12 +185,11 @@ public inline fun PsiElement.findDescendantOfType(noinl public inline fun PsiElement.findDescendantOfType(crossinline canGoInside: (PsiElement) -> Boolean, noinline predicate: (T) -> Boolean = { true }): T? { var result: T? = null - this.accept(object : PsiRecursiveElementVisitor() { + this.accept(object : PsiRecursiveElementWalkingVisitor() { override fun visitElement(element: PsiElement) { - if (result != null) return - if (element is T && predicate(element)) { result = element + stopWalking() return } @@ -310,4 +309,11 @@ public fun PsiElement.getTextWithLocation(): String = "'${this.getText()}' at ${ public fun SearchScope.contains(element: PsiElement): Boolean = PsiSearchScopeUtil.isInScope(this, element) public fun E.createSmartPointer(): SmartPsiElementPointer = - SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(this) \ No newline at end of file + SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(this) + +fun PsiElement.resolveAllReferences(): Sequence { + return PsiReferenceService.getService().getReferences(this, PsiReferenceService.Hints.NO_HINTS) + .asSequence() + .map { it.resolve() } +} + diff --git a/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/after.kt.template b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/after.kt.template new file mode 100644 index 00000000000..1a0052864f5 --- /dev/null +++ b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/after.kt.template @@ -0,0 +1,3 @@ +class A { + var i: Int = 5 +} diff --git a/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/before.kt.template b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/before.kt.template new file mode 100644 index 00000000000..920911ebcb9 --- /dev/null +++ b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/before.kt.template @@ -0,0 +1,7 @@ +class A { + var i: Int + + init { + i = 5 + } +} diff --git a/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/description.html b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/description.html new file mode 100644 index 00000000000..cc169d2277d --- /dev/null +++ b/idea/resources/intentionDescriptions/MoveAssignmentToInitializerIntention/description.html @@ -0,0 +1,5 @@ + + +This intention moves an assignment to a property into the initializer of that property. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d58a66d7675..57c219702b1 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1033,6 +1033,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.MoveAssignmentToInitializerIntention + Kotlin + + (javaClass(), "Introduce backing property") { @@ -82,7 +81,7 @@ class IntroduceBackingPropertyIntention(): JetSelfTargetingIntention(javaClass(), "Move assignment to initializer") { + + override fun isApplicableTo(element: JetBinaryExpression, caretOffset: Int): Boolean { + if (element.operationToken != JetTokens.EQ) { + return false + } + val rhs = element.right ?: return false + + val block = PsiTreeUtil.getParentOfType(element, + JetClassInitializer::class.java, + JetSecondaryConstructor::class.java) ?: return false + + val target = findTargetProperty(element) + return target != null && target.initializer == null && target.receiverTypeReference == null && + target.getNonStrictParentOfType() == element.getNonStrictParentOfType() && + hasNoLocalDependencies(rhs, block) + + } + + override fun applyTo(element: JetBinaryExpression, editor: Editor) { + val prop = findTargetProperty(element) ?: return + val initializer = element.right ?: return + prop.setInitializer(initializer) + + val initializerBlock = element.getStrictParentOfType() + element.delete() + if (initializerBlock != null && (initializerBlock.body as? JetBlockExpression)?.isEmpty() == true) { + initializerBlock.delete() + } + } + + private fun findTargetProperty(expr: JetBinaryExpression): JetProperty? { + val lhs = expr.left as? JetSimpleNameExpression ?: return null + return lhs.resolveAllReferences().firstIsInstanceOrNull() + } + + fun JetBlockExpression.isEmpty(): Boolean { + var node = node?.firstChildNode + while (node != null) { + if (node.elementType != TokenType.WHITE_SPACE && + node.elementType != JetTokens.LBRACE && + node.elementType != JetTokens.RBRACE) { + return false + } + node = node.treeNext + } + return true + } + + private fun hasNoLocalDependencies(element: JetElement, localContext: PsiElement): Boolean { + return !element.anyDescendantOfType { child -> + child.resolveAllReferences().any { it != null && PsiTreeUtil.isAncestor(localContext, it, false) } + } + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/.intention b/idea/testData/intentions/moveAssignmentToInitializer/.intention new file mode 100644 index 00000000000..56aa1921251 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.MoveAssignmentToInitializerIntention \ No newline at end of file diff --git a/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt b/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt new file mode 100644 index 00000000000..693211ee245 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt @@ -0,0 +1,9 @@ +class A { + var a: Int + var b: Int + + init { + $a = 1 + b = 2 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt.after b/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt.after new file mode 100644 index 00000000000..d830c8219d4 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/backingField.kt.after @@ -0,0 +1,8 @@ +class A { + var a: Int = 1 + var b: Int + + init { + b = 2 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/comment.kt b/idea/testData/intentions/moveAssignmentToInitializer/comment.kt new file mode 100644 index 00000000000..ffccaacf78e --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/comment.kt @@ -0,0 +1,8 @@ +class A { + var a: Int + + init { + // Initialize a + a = 1 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/comment.kt.after b/idea/testData/intentions/moveAssignmentToInitializer/comment.kt.after new file mode 100644 index 00000000000..05a7323cfd6 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/comment.kt.after @@ -0,0 +1,7 @@ +class A { + var a: Int = 1 + + init { + // Initialize a + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt b/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt new file mode 100644 index 00000000000..bf15b61352d --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt @@ -0,0 +1,7 @@ +class A { + var a: Int + + init { + a = 1 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt.after b/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt.after new file mode 100644 index 00000000000..b50f37dfffb --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt.after @@ -0,0 +1,4 @@ +class A { + var a: Int = 1 + +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/simple.kt b/idea/testData/intentions/moveAssignmentToInitializer/simple.kt new file mode 100644 index 00000000000..3d8bf8249b7 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/simple.kt @@ -0,0 +1,9 @@ +class A { + var a: Int + var b: Int + + init { + a = 1 + b = 2 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/simple.kt.after b/idea/testData/intentions/moveAssignmentToInitializer/simple.kt.after new file mode 100644 index 00000000000..d830c8219d4 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/simple.kt.after @@ -0,0 +1,8 @@ +class A { + var a: Int = 1 + var b: Int + + init { + b = 2 + } +} diff --git a/idea/testData/intentions/moveAssignmentToInitializer/usedLocal.kt b/idea/testData/intentions/moveAssignmentToInitializer/usedLocal.kt new file mode 100644 index 00000000000..04a5419b546 --- /dev/null +++ b/idea/testData/intentions/moveAssignmentToInitializer/usedLocal.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: false +class A { + var b: Int + + init { + val i = 0 + b = i + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index c589899115b..887b9da3c06 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -5416,6 +5416,45 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/moveAssignmentToInitializer") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MoveAssignmentToInitializer extends AbstractIntentionTest { + public void testAllFilesPresentInMoveAssignmentToInitializer() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/moveAssignmentToInitializer"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("backingField.kt") + public void testBackingField() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveAssignmentToInitializer/backingField.kt"); + doTest(fileName); + } + + @TestMetadata("comment.kt") + public void testComment() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveAssignmentToInitializer/comment.kt"); + doTest(fileName); + } + + @TestMetadata("deleteInitBlock.kt") + public void testDeleteInitBlock() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveAssignmentToInitializer/deleteInitBlock.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveAssignmentToInitializer/simple.kt"); + doTest(fileName); + } + + @TestMetadata("usedLocal.kt") + public void testUsedLocal() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/moveAssignmentToInitializer/usedLocal.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/moveLambdaInsideParentheses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)