From afc1e24571d3f6ce4102ec16d6aff3686e537217 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Sat, 19 Dec 2015 19:18:22 +0300 Subject: [PATCH] Quick fix for deprecated async syntax --- .../KotlinParenthesesSurrounder.java | 23 ++++--- .../inspections/KotlinCleanupInspection.kt | 3 +- .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 + .../idea/quickfix/UnsupportedAsyncFix.kt | 62 +++++++++++++++++++ idea/testData/inspections/cleanup/cleanup.kt | 20 ++++++ .../inspections/cleanup/cleanup.kt.after | 20 ++++++ .../asyncUnsupported/asyncInfixCall.kt | 6 ++ .../asyncUnsupported/asyncInfixCall.kt.after | 6 ++ .../asyncUnsupported/asyncInfixCallWithFun.kt | 6 ++ .../asyncInfixCallWithFun.kt.after | 6 ++ .../asyncUnsupported/asyncWithLambda.kt | 6 ++ .../asyncUnsupported/asyncWithLambda.kt.after | 6 ++ .../asyncWithLambdaAndComment.kt | 6 ++ .../asyncWithLambdaAndComment.kt.after | 6 ++ .../idea/quickfix/QuickFixTestGenerated.java | 33 ++++++++++ 15 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt.after create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt.after create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt.after create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt create mode 100644 idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/surroundWith/expression/KotlinParenthesesSurrounder.java b/idea/src/org/jetbrains/kotlin/idea/codeInsight/surroundWith/expression/KotlinParenthesesSurrounder.java index 916c7f2e7ab..88ee52fd71f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/surroundWith/expression/KotlinParenthesesSurrounder.java +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/surroundWith/expression/KotlinParenthesesSurrounder.java @@ -40,18 +40,25 @@ public class KotlinParenthesesSurrounder extends KotlinExpressionSurrounder { @Nullable @Override - public TextRange surroundExpression( @NotNull Project project, @NotNull Editor editor, @NotNull KtExpression expression) { - KtParenthesizedExpression parenthesizedExpression = (KtParenthesizedExpression) KtPsiFactoryKt - .KtPsiFactory(expression).createExpression("(a)"); - KtExpression expressionWithoutParentheses = parenthesizedExpression.getExpression(); - assert expressionWithoutParentheses != null : "JetExpression should exists for " + parenthesizedExpression.getText() + " expression"; - expressionWithoutParentheses.replace(expression); - - expression = (KtExpression) expression.replace(parenthesizedExpression); + public TextRange surroundExpression(@NotNull Project project, @NotNull Editor editor, @NotNull KtExpression expression) { + expression = surroundWithParentheses(expression); CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(expression); int offset = expression.getTextRange().getEndOffset(); return new TextRange(offset, offset); } + + @NotNull + public static KtExpression surroundWithParentheses(@NotNull KtExpression expression) { + KtParenthesizedExpression parenthesizedExpression = + (KtParenthesizedExpression) KtPsiFactoryKt.KtPsiFactory(expression).createExpression("(a)"); + + KtExpression expressionWithoutParentheses = parenthesizedExpression.getExpression(); + assert expressionWithoutParentheses != null : "JetExpression should exists for " + parenthesizedExpression.getText() + " expression"; + expressionWithoutParentheses.replace(expression); + + expression = (KtExpression) expression.replace(parenthesizedExpression); + return expression; + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt index 5d9279082dc..77a1111a384 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt @@ -98,7 +98,8 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe Errors.CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS, Errors.DEPRECATED_TYPE_PARAMETER_SYNTAX, Errors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, - Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT + Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, + Errors.UNSUPPORTED ) private fun Diagnostic.isObsoleteLabel(): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index a1778072d70..a7bf1604953 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -356,5 +356,7 @@ public class QuickFixRegistrar : QuickFixContributor { DEPRECATED_UNARY_PLUS_MINUS.registerFactory(DeprecatedFunctionConventionFix) COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.registerFactory(CommaInWhenConditionWithoutArgumentFix) + + UNSUPPORTED.registerFactory(UnsupportedAsyncFix) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt new file mode 100644 index 00000000000..458b9e6819f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt @@ -0,0 +1,62 @@ +/* + * 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.psi.PsiElement +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.codeInsight.surroundWith.expression.KotlinParenthesesSurrounder +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory + +public class UnsupportedAsyncFix(val psiElement: PsiElement): KotlinQuickFixAction(psiElement), CleanupFix { + override fun getFamilyName(): String = "Migrate unsupported async syntax" + override fun getText(): String = familyName + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + if (element is KtBinaryExpression && element.right != null) { + KotlinParenthesesSurrounder.surroundWithParentheses(element.right!!) + } + + if (element is KtCallExpression) { + val ktExpression = element.calleeExpression ?: return + + // Add after "async" reference in call + element.addAfter(KtPsiFactory(element).createCallArguments("()"), ktExpression) + } + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + if (diagnostic.psiElement.text != "async" || + !Errors.UNSUPPORTED.cast(diagnostic).a.startsWith("async block/lambda")) return null + + // Identifier -> Expression -> Call (normal call) or Identifier -> Operation Reference -> Binary Expression (for infix usage) + val grand = diagnostic.psiElement.parent.parent + if (grand is KtBinaryExpression || grand is KtCallExpression) { + return UnsupportedAsyncFix(grand) + } + + return null + } + } +} diff --git a/idea/testData/inspections/cleanup/cleanup.kt b/idea/testData/inspections/cleanup/cleanup.kt index 5abf82198a1..c3f30fe45c9 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt +++ b/idea/testData/inspections/cleanup/cleanup.kt @@ -76,3 +76,23 @@ val x = C() willBeInfix 1 fun infixTest() { arrayListOf(1, 2, 3) map { it } } + +fun async(f: () -> Unit) {} +infix fun Any.async(f: () -> Unit) {} + +fun test(foo: Any) { + async { } + async /**/ { } + foo async { } + + async() { } + + async({ }) + foo async ({ }) + + foo async fun () {} + foo async (fun () {}) + + async (fun () {}) +} + diff --git a/idea/testData/inspections/cleanup/cleanup.kt.after b/idea/testData/inspections/cleanup/cleanup.kt.after index 83d35fdcb9a..e8e02411504 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt.after +++ b/idea/testData/inspections/cleanup/cleanup.kt.after @@ -75,3 +75,23 @@ val x = C() willBeInfix 1 fun infixTest() { arrayListOf(1, 2, 3).map { it } } + +fun async(f: () -> Unit) {} +infix fun Any.async(f: () -> Unit) {} + +fun test(foo: Any) { + async() { } + async() /**/ { } + foo async ({ }) + + async() { } + + async({ }) + foo async ({ }) + + foo async (fun () {}) + foo async (fun () {}) + + async (fun () {}) +} + diff --git a/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt b/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt new file mode 100644 index 00000000000..0ed56d6c7ff --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +infix fun Any.async(f: () -> Unit) = f() + +fun test(foo: Any) { + foo async { } +} \ No newline at end of file diff --git a/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt.after b/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt.after new file mode 100644 index 00000000000..7aa4e629898 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt.after @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +infix fun Any.async(f: () -> Unit) = f() + +fun test(foo: Any) { + foo async ({ }) +} \ No newline at end of file diff --git a/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt b/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt new file mode 100644 index 00000000000..cc3c351be14 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +infix fun Any.async(f: () -> Unit) = f() + +fun test(foo: Any) { + foo async fun () {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt.after b/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt.after new file mode 100644 index 00000000000..171ad9cf6b0 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt.after @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +infix fun Any.async(f: () -> Unit) = f() + +fun test(foo: Any) { + foo async (fun () {}) +} \ No newline at end of file diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt b/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt new file mode 100644 index 00000000000..1cb7d6b57bf --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +fun async(f: () -> Unit) {} + +fun test() { + async { } +} diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt.after b/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt.after new file mode 100644 index 00000000000..b152f5674b4 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt.after @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +fun async(f: () -> Unit) {} + +fun test() { + async() { } +} diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt b/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt new file mode 100644 index 00000000000..fcb2b53286a --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +fun async(f: () -> Unit) {} + +fun test() { + async /**/ { } +} diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt.after b/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt.after new file mode 100644 index 00000000000..bed586cfc60 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt.after @@ -0,0 +1,6 @@ +// "Migrate unsupported async syntax" "true" +fun async(f: () -> Unit) {} + +fun test() { + async() /**/ { } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 54f5d18d1f8..945e12cb17d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -509,6 +509,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/asyncUnsupported") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AsyncUnsupported extends AbstractQuickFixTest { + public void testAllFilesPresentInAsyncUnsupported() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/asyncUnsupported"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("asyncInfixCall.kt") + public void testAsyncInfixCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncInfixCall.kt"); + doTest(fileName); + } + + @TestMetadata("asyncInfixCallWithFun.kt") + public void testAsyncInfixCallWithFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncInfixCallWithFun.kt"); + doTest(fileName); + } + + @TestMetadata("asyncWithLambda.kt") + public void testAsyncWithLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncWithLambda.kt"); + doTest(fileName); + } + + @TestMetadata("asyncWithLambdaAndComment.kt") + public void testAsyncWithLambdaAndComment() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/autoImports") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)