Add quickfix for deprecated labels syntax
This commit is contained in:
@@ -536,6 +536,16 @@ inline fun <reified T : JetElement, R> flatMapDescendantsOfTypeVisitor(
|
||||
inline fun <reified T : JetElement> PsiElement.forEachDescendantsOfType(noinline block: (T) -> Unit) =
|
||||
accept(forEachDescendantOfTypeVisitor(block))
|
||||
|
||||
inline fun <reified T : JetElement> PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean): Boolean {
|
||||
var result = false
|
||||
accept(forEachDescendantOfTypeVisitor<T> {
|
||||
if (!result && predicate(it)) {
|
||||
result = true
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
public fun PsiFile.getFqNameByDirectory(): FqName {
|
||||
val qualifiedNameByDirectory = getParent()?.getPackage()?.getQualifiedName()
|
||||
return qualifiedNameByDirectory?.let { FqName(it) } ?: FqName.ROOT
|
||||
|
||||
@@ -340,5 +340,8 @@ public class QuickFixRegistrar {
|
||||
|
||||
QuickFixes.factories.put(FUNCTION_EXPRESSION_WITH_NAME, RemoveNameFromFunctionExpressionFix.Companion);
|
||||
QuickFixes.factories.put(FUNCTION_EXPRESSION_WITH_NAME, RemoveNameFromFunctionExpressionFix.Companion.createWholeProjectFixFactory());
|
||||
|
||||
QuickFixes.factories.put(UNRESOLVED_REFERENCE, ReplaceObsoleteLabelSyntaxFix.Companion);
|
||||
QuickFixes.factories.put(UNRESOLVED_REFERENCE, ReplaceObsoleteLabelSyntaxFix.Companion.createWholeProjectFixFactory());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
public class ReplaceObsoleteLabelSyntaxFix(element: JetAnnotationEntry?) : JetIntentionAction<JetAnnotationEntry>(element) {
|
||||
override fun getFamilyName(): String = "Update obsolete label syntax"
|
||||
override fun getText(): String = "Replace with label ${element.getCalleeExpression()?.getText() ?: ""}@"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: JetFile) = replaceWithLabel(element)
|
||||
|
||||
companion object : JetSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val annotationEntry = diagnostic.getPsiElement().getNonStrictParentOfType<JetAnnotationEntry>() ?: return null
|
||||
|
||||
if (!annotationEntry.looksLikeObsoleteLabel()) return null
|
||||
|
||||
return ReplaceObsoleteLabelSyntaxFix(annotationEntry)
|
||||
}
|
||||
|
||||
public fun createWholeProjectFixFactory(): JetSingleIntentionActionFactory = createIntentionFactory factory@ {
|
||||
diagnostic ->
|
||||
|
||||
if (!(diagnostic.getPsiElement().getNonStrictParentOfType<JetAnnotationEntry>()?.looksLikeObsoleteLabelWithReferencesInCode()
|
||||
?: false)) return@factory null
|
||||
|
||||
JetWholeProjectForEachElementOfTypeFix.createForMultiTask<JetAnnotatedExpression, JetAnnotationEntry>(
|
||||
tasksFactory = { collectTasks(it) },
|
||||
tasksProcessor ={ it.forEach { ann -> replaceWithLabel(ann) } },
|
||||
modalTitle = "Replacing labels with obsolete syntax",
|
||||
name = "Update obsolete label syntax in whole project",
|
||||
familyName = "Update obsolete label syntax in whole project"
|
||||
)
|
||||
}
|
||||
|
||||
private fun collectTasks(expression: JetAnnotatedExpression) =
|
||||
expression.getAnnotationEntries().filter { it.looksLikeObsoleteLabelWithReferencesInCode() }
|
||||
|
||||
private fun JetAnnotationEntry.looksLikeObsoleteLabelWithReferencesInCode(): Boolean {
|
||||
if (!looksLikeObsoleteLabel()) return false
|
||||
|
||||
val baseExpression = (getParent() as? JetAnnotatedExpression)?.getBaseExpression() ?: return false
|
||||
|
||||
val nameExpression = getCalleeExpression().getConstructorReferenceExpression() ?: return false
|
||||
val labelName = nameExpression.getReferencedName()
|
||||
|
||||
return baseExpression.anyDescendantOfType<JetExpressionWithLabel> {
|
||||
(it is JetBreakExpression || it is JetContinueExpression || it is JetReturnExpression) &&
|
||||
it.getLabelName() == labelName &&
|
||||
it.getTargetLabel()?.analyze()?.get(BindingContext.LABEL_TARGET, it.getTargetLabel()) == null
|
||||
} && analyze().getDiagnostics().forElement(nameExpression).any { it.getFactory() == Errors.UNRESOLVED_REFERENCE }
|
||||
}
|
||||
|
||||
private fun JetAnnotationEntry.looksLikeObsoleteLabel() =
|
||||
getAtSymbol() != null &&
|
||||
getParent() is JetAnnotatedExpression &&
|
||||
(getParent() as JetAnnotatedExpression).getAnnotationEntries().size() == 1 &&
|
||||
getValueArgumentList() == null &&
|
||||
getCalleeExpression()?.getConstructorReferenceExpression()?.getIdentifier() != null
|
||||
|
||||
private fun replaceWithLabel(annotation: JetAnnotationEntry) {
|
||||
val labelName = annotation.getCalleeExpression()?.getConstructorReferenceExpression()?.getReferencedName() ?: return
|
||||
val annotatedExpression = annotation.getParent() as? JetAnnotatedExpression ?: return
|
||||
val expression = annotatedExpression.getBaseExpression() ?: return
|
||||
|
||||
if (annotatedExpression.getAnnotationEntries().size() != 1) return
|
||||
|
||||
val baseExpressionStart = expression.getTextRange().getStartOffset()
|
||||
|
||||
val textRangeToRetain = TextRange(annotation.getTextRange().getEndOffset(), baseExpressionStart)
|
||||
val textToRetain = textRangeToRetain.substring(annotation.getContainingFile().getText())
|
||||
|
||||
val labeledExpression = JetPsiFactory(annotation).createExpressionByPattern("$labelName@$0$1", textToRetain, expression)
|
||||
|
||||
annotatedExpression.replace(labeledExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Replace with label label@" "true"
|
||||
|
||||
fun run(block: () -> Unit) = block()
|
||||
|
||||
fun foo() {
|
||||
run @label<caret> {
|
||||
return@label
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Replace with label label@" "true"
|
||||
|
||||
fun run(block: () -> Unit) = block()
|
||||
|
||||
fun foo() {
|
||||
run label@ {
|
||||
return@label
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace with label loop@" "true"
|
||||
|
||||
fun foo() {
|
||||
@loop<caret> for (i in 1..100) {
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Replace with label loop@" "true"
|
||||
|
||||
fun foo() {
|
||||
loop@ for (i in 1..100) {
|
||||
break@loop
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun bar() {
|
||||
l@ while (true) {
|
||||
break@l
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// "Update obsolete label syntax in whole project" "true"
|
||||
// ERROR: Unresolved reference: @abc
|
||||
// ERROR: Unresolved reference: @cde
|
||||
// ERROR: Unresolved reference: @data
|
||||
// ERROR: Unresolved reference: @labeled
|
||||
// ERROR: Unresolved reference: @loop
|
||||
// ERROR: Unresolved reference: @loop2
|
||||
// ERROR: Unresolved reference: @loop3
|
||||
// ERROR: Unresolved reference: @loop4
|
||||
// ERROR: Unresolved reference: @loop5
|
||||
// ERROR: Unresolved reference: abc
|
||||
// ERROR: Unresolved reference: cde
|
||||
// ERROR: Unresolved reference: labeled
|
||||
// ERROR: Unresolved reference: loop
|
||||
// ERROR: Unresolved reference: loop1
|
||||
// ERROR: Unresolved reference: loop2
|
||||
// ERROR: Unresolved reference: loop2
|
||||
// ERROR: Unresolved reference: loop3
|
||||
// ERROR: Unresolved reference: loop4
|
||||
// ERROR: Unresolved reference: loop5
|
||||
// ERROR: Unresolved reference: noreferences
|
||||
// ERROR: Unresolved reference: notLabelAnnotation
|
||||
// ERROR: Unresolved reference: notLoop
|
||||
// ERROR: The label '@abc' does not denote a loop
|
||||
// ERROR: The label '@cde' does not denote a loop
|
||||
// ERROR: The label '@data' does not denote a loop
|
||||
// ERROR: The label '@loop' does not denote a loop
|
||||
// ERROR: The label '@loop2' does not denote a loop
|
||||
// ERROR: The label '@loop3' does not denote a loop
|
||||
// ERROR: The label '@loop4' does not denote a loop
|
||||
// ERROR: The label '@loop5' does not denote a loop
|
||||
|
||||
fun run(block: () -> Unit) = block()
|
||||
|
||||
notLabelAnnotation class A {
|
||||
fun foo() {
|
||||
loop@
|
||||
for (i in 1..100) {
|
||||
/* comment */
|
||||
continue@loop
|
||||
}
|
||||
|
||||
@noreferences for (i in 1..100) {
|
||||
val x = 1
|
||||
}
|
||||
|
||||
@loop1 for (i in 1..100) {
|
||||
loop1@ for (j in 1..100) {
|
||||
continue@loop1
|
||||
}
|
||||
}
|
||||
|
||||
loop2@ for (i in 1..100) {
|
||||
loop2@ for (j in 1..100) {
|
||||
break@loop2
|
||||
}
|
||||
}
|
||||
|
||||
@[loop3] for (i in 1..100) {
|
||||
continue@loop3
|
||||
}
|
||||
|
||||
run() labeled@ {
|
||||
return@labeled
|
||||
}
|
||||
|
||||
@data for (i in 1..100) {
|
||||
continue@data
|
||||
}
|
||||
|
||||
@ for (i in 1..100) {
|
||||
break@
|
||||
}
|
||||
|
||||
@loop4() for (i in 1..100) {
|
||||
continue@loop4
|
||||
}
|
||||
|
||||
@notLoop class Local
|
||||
|
||||
@abc @cde for (i in 1..100) {
|
||||
break@cde
|
||||
break@abc
|
||||
}
|
||||
|
||||
/* 123 */ loop5@ /* 456 */
|
||||
|
||||
for (i in 1..100) {
|
||||
break@loop5
|
||||
} /* 789 */
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// "Update obsolete label syntax in whole project" "true"
|
||||
// ERROR: Unresolved reference: @abc
|
||||
// ERROR: Unresolved reference: @cde
|
||||
// ERROR: Unresolved reference: @data
|
||||
// ERROR: Unresolved reference: @labeled
|
||||
// ERROR: Unresolved reference: @loop
|
||||
// ERROR: Unresolved reference: @loop2
|
||||
// ERROR: Unresolved reference: @loop3
|
||||
// ERROR: Unresolved reference: @loop4
|
||||
// ERROR: Unresolved reference: @loop5
|
||||
// ERROR: Unresolved reference: abc
|
||||
// ERROR: Unresolved reference: cde
|
||||
// ERROR: Unresolved reference: labeled
|
||||
// ERROR: Unresolved reference: loop
|
||||
// ERROR: Unresolved reference: loop1
|
||||
// ERROR: Unresolved reference: loop2
|
||||
// ERROR: Unresolved reference: loop2
|
||||
// ERROR: Unresolved reference: loop3
|
||||
// ERROR: Unresolved reference: loop4
|
||||
// ERROR: Unresolved reference: loop5
|
||||
// ERROR: Unresolved reference: noreferences
|
||||
// ERROR: Unresolved reference: notLabelAnnotation
|
||||
// ERROR: Unresolved reference: notLoop
|
||||
// ERROR: The label '@abc' does not denote a loop
|
||||
// ERROR: The label '@cde' does not denote a loop
|
||||
// ERROR: The label '@data' does not denote a loop
|
||||
// ERROR: The label '@loop' does not denote a loop
|
||||
// ERROR: The label '@loop2' does not denote a loop
|
||||
// ERROR: The label '@loop3' does not denote a loop
|
||||
// ERROR: The label '@loop4' does not denote a loop
|
||||
// ERROR: The label '@loop5' does not denote a loop
|
||||
|
||||
fun run(block: () -> Unit) = block()
|
||||
|
||||
notLabelAnnotation class A {
|
||||
fun foo() {
|
||||
@loop<caret>
|
||||
for (i in 1..100) {
|
||||
/* comment */
|
||||
continue@loop
|
||||
}
|
||||
|
||||
@noreferences for (i in 1..100) {
|
||||
val x = 1
|
||||
}
|
||||
|
||||
@loop1 for (i in 1..100) {
|
||||
loop1@ for (j in 1..100) {
|
||||
continue@loop1
|
||||
}
|
||||
}
|
||||
|
||||
@loop2 for (i in 1..100) {
|
||||
@loop2 for (j in 1..100) {
|
||||
break@loop2
|
||||
}
|
||||
}
|
||||
|
||||
@[loop3] for (i in 1..100) {
|
||||
continue@loop3
|
||||
}
|
||||
|
||||
run() @labeled {
|
||||
return@labeled
|
||||
}
|
||||
|
||||
@data for (i in 1..100) {
|
||||
continue@data
|
||||
}
|
||||
|
||||
@ for (i in 1..100) {
|
||||
break@
|
||||
}
|
||||
|
||||
@loop4() for (i in 1..100) {
|
||||
continue@loop4
|
||||
}
|
||||
|
||||
@notLoop class Local
|
||||
|
||||
@abc @cde for (i in 1..100) {
|
||||
break@cde
|
||||
break@abc
|
||||
}
|
||||
|
||||
/* 123 */ @loop5 /* 456 */
|
||||
|
||||
for (i in 1..100) {
|
||||
break@loop5
|
||||
} /* 789 */
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun bar() {
|
||||
@l while (true) {
|
||||
break@l
|
||||
}
|
||||
}
|
||||
@@ -918,6 +918,21 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ObsoleteLabelSyntax extends AbstractQuickFixMultiFileTest {
|
||||
public void testAllFilesPresentInObsoleteLabelSyntax() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/obsoleteLabelSyntax"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("manyFilesMuitliple.before.Main.kt")
|
||||
public void testManyFilesMuitliple() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax/manyFilesMuitliple.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/removeNameFromFunctionExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -3295,6 +3295,27 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ObsoleteLabelSyntax extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInObsoleteLabelSyntax() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/obsoleteLabelSyntax"), Pattern.compile("^(\\w+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("lambda.kt")
|
||||
public void testLambda() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax/lambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("loop.kt")
|
||||
public void testLoop() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax/loop.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/removeNameFromFunctionExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user