Permalink
Browse files

Quick Fixes: Implement "Create label" quick fix

 #KT-8855 Fixed
  • Loading branch information...
1 parent 534a773 commit 23ec8f0813a3f8cfcf06a8f3e1c3ae571a7727f9 @asedunov asedunov committed Dec 23, 2016
View
@@ -477,6 +477,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-15068`](https://youtrack.jetbrains.com/issue/KT-15068) Implement intention which rename file according to the top-level class name
- Implement quickfix which enables/disables coroutine support in module or project
- [`KT-15056`](https://youtrack.jetbrains.com/issue/KT-15056) Implement intention which converts object literal to class
+- [`KT-8855`](https://youtrack.jetbrains.com/issue/KT-8855) Implement "Create label" quick fix
## 1.0.6
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2016 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.quickfix
+
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import org.jetbrains.kotlin.diagnostics.Diagnostic
+import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
+import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.parents
+
+sealed class CreateLabelFix(
+ expression: KtLabelReferenceExpression
+) : KotlinQuickFixAction<KtLabelReferenceExpression>(expression) {
+ class ForLoop(expression: KtLabelReferenceExpression) : CreateLabelFix(expression) {
+ override val chooserTitle = "Select loop statement to label"
+
+ override fun getCandidateExpressions(labelReferenceExpression: KtLabelReferenceExpression) =
+ labelReferenceExpression.getContainingLoops().toList()
+ }
+
+ class ForLambda(expression: KtLabelReferenceExpression) : CreateLabelFix(expression) {
+ override val chooserTitle = "Select lambda to label"
+
+ override fun getCandidateExpressions(labelReferenceExpression: KtLabelReferenceExpression) =
+ labelReferenceExpression.getContainingLambdas().toList()
+ }
+
+ override fun getFamilyName() = "Create label"
+
+ override fun getText() = "Create label ${element?.getReferencedName() ?: ""}@"
+
+ abstract val chooserTitle: String
+
+ abstract fun getCandidateExpressions(labelReferenceExpression: KtLabelReferenceExpression): List<KtExpression>
+
+ override fun startInWriteAction() = false
+
+ private fun doCreateLabel(expression: KtLabelReferenceExpression, it: KtExpression, project: Project) {
+ project.executeWriteCommand(text) {
+ it.replace(KtPsiFactory(project).createExpressionByPattern("${expression.getReferencedName()}@ $0", it))
+ }
+ }
+
+ override fun invoke(project: Project, editor: Editor?, file: KtFile) {
+ val expression = element ?: return
+ if (editor == null) return
+
+ val containers = getCandidateExpressions(expression)
+
+ if (ApplicationManager.getApplication().isUnitTestMode) {
+ return doCreateLabel(expression, containers.last(), project)
+ }
+
+ chooseContainerElementIfNecessary(
+ containers,
+ editor,
+ chooserTitle,
+ true,
+ { it },
+ {
+ doCreateLabel(expression, it, project)
+ }
+ )
+ }
+
+ companion object : KotlinSingleIntentionActionFactory() {
+ private fun KtLabelReferenceExpression.getContainingLoops(): Sequence<KtLoopExpression> {
+ return parents
+ .takeWhile { !(it is KtDeclarationWithBody || it is KtClassBody || it is KtFile) }
+ .filterIsInstance<KtLoopExpression>()
+ }
+
+ private fun KtLabelReferenceExpression.getContainingLambdas(): Sequence<KtLambdaExpression> {
+ return parents
+ .takeWhile { !(it is KtDeclarationWithBody && it !is KtFunctionLiteral || it is KtClassBody || it is KtFile) }
+ .filterIsInstance<KtLambdaExpression>()
+ }
+
+ override fun createAction(diagnostic: Diagnostic): IntentionAction? {
+ val labelReferenceExpression = diagnostic.psiElement as? KtLabelReferenceExpression ?: return null
+ val parentExpression = (labelReferenceExpression.parent as? KtContainerNode)?.parent
+ return when (parentExpression) {
+ is KtBreakExpression, is KtContinueExpression -> {
+ if (labelReferenceExpression.getContainingLoops().any()) CreateLabelFix.ForLoop(labelReferenceExpression) else null
+ }
+ is KtReturnExpression -> {
+ if (labelReferenceExpression.getContainingLambdas().any()) CreateLabelFix.ForLambda(labelReferenceExpression) else null
+ }
+ else -> null
+ }
+ }
+ }
+}
@@ -472,5 +472,7 @@ class QuickFixRegistrar : QuickFixContributor {
EXPERIMENTAL_FEATURE_ERROR.registerFactory(ChangeCoroutineSupportFix)
EXPERIMENTAL_FEATURE_WARNING.registerFactory(ChangeCoroutineSupportFix)
+
+ UNRESOLVED_REFERENCE.registerFactory(CreateLabelFix)
}
}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+fun test() {
+ while (true) {
+ break@<caret>foo
+ }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+fun test() {
+ foo@ while (true) {
+ <caret>break@foo
+ }
+}
@@ -0,0 +1,9 @@
+// "Create label foo@" "true"
+
+fun test() {
+ while (true) {
+ while (true) {
+ break@<caret>foo
+ }
+ }
+}
@@ -0,0 +1,9 @@
+// "Create label foo@" "true"
+
+fun test() {
+ foo@ while (true) {
+ while (true) {
+ <caret> break@foo
+ }
+ }
+}
@@ -0,0 +1,11 @@
+// "Create label foo@" "false"
+// ERROR: The label '@foo' does not denote a loop
+// ERROR: Unresolved reference: @foo
+
+fun bar(f: () -> Unit) { }
+
+fun test() {
+ while (true) {
+ bar { break@<caret>foo }
+ }
+}
@@ -0,0 +1,8 @@
+// "Create label foo@" "false"
+// ACTION: Convert to expression body
+// ERROR: The label '@foo' does not denote a loop
+// ERROR: Unresolved reference: @foo
+
+fun test() {
+ break@<caret>foo
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+fun test() {
+ while (true) {
+ continue@<caret>foo
+ }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+fun test() {
+ foo@ while (true) {
+ <caret>continue@foo
+ }
+}
@@ -0,0 +1,8 @@
+// "Create label foo@" "false"
+// ACTION: Convert to expression body
+// ERROR: The label '@foo' does not denote a loop
+// ERROR: Unresolved reference: @foo
+
+fun test() {
+ continue@<caret>foo
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+inline fun Int.bar(f: (Int) -> Unit) { }
+
+fun test() {
+ 1.bar { if (it == 2) return@<caret>foo }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+inline fun Int.bar(f: (Int) -> Unit) { }
+
+fun test() {
+ 1.bar foo@ { if (it == 2) return@<caret>foo }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+inline fun Int.bar(f: (Int) -> Unit) { }
+
+fun test() {
+ 1.bar { 2.bar { if (it == 2) return@<caret>foo } }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "true"
+
+inline fun Int.bar(f: (Int) -> Unit) { }
+
+fun test() {
+ 1.bar foo@ { 2.bar { if (it == 2) return@<caret>foo } }
+}
@@ -0,0 +1,7 @@
+// "Create label foo@" "false"
+// ACTION: Convert to expression body
+// ERROR: Unresolved reference: @foo
+
+fun test(): Int {
+ return@<caret>foo 1
+}
@@ -4151,6 +4151,69 @@ public void testOtherExplicitReceiver() throws Exception {
}
}
+ @TestMetadata("idea/testData/quickfix/createLabel")
+ @TestDataPath("$PROJECT_ROOT")
+ @RunWith(JUnit3RunnerWithInners.class)
+ public static class CreateLabel extends AbstractQuickFixTest {
+ public void testAllFilesPresentInCreateLabel() throws Exception {
+ KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/createLabel"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
+ }
+
+ @TestMetadata("breakInLoop.kt")
+ public void testBreakInLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/breakInLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("breakInOuterLoop.kt")
+ public void testBreakInOuterLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/breakInOuterLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("breakInlambdaBeforeLoop.kt")
+ public void testBreakInlambdaBeforeLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/breakInlambdaBeforeLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("breakNoLoop.kt")
+ public void testBreakNoLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/breakNoLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("continueInLoop.kt")
+ public void testContinueInLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/continueInLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("continueNoLoop.kt")
+ public void testContinueNoLoop() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/continueNoLoop.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("returnInLambda.kt")
+ public void testReturnInLambda() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/returnInLambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("returnInOuterLambda.kt")
+ public void testReturnInOuterLambda() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/returnInOuterLambda.kt");
+ doTest(fileName);
+ }
+
+ @TestMetadata("returnNoLambda.kt")
+ public void testReturnNoLambda() throws Exception {
+ String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createLabel/returnNoLambda.kt");
+ doTest(fileName);
+ }
+ }
+
@TestMetadata("idea/testData/quickfix/decreaseVisibility")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)

0 comments on commit 23ec8f0

Please sign in to comment.