From b7a08494ae4a8e70756339416aedd6af03d7fef4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 25 May 2020 15:04:42 +0300 Subject: [PATCH] Add quickfix for adding CancellableException to `@Throws suspend fun` --- .../messages/KotlinBundle.properties | 3 +- .../idea/quickfix/AddExceptionToThrowsFix.kt | 63 +++++++++++++++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 3 + .../native/CancellationException.kt | 3 + .../native/kotlin.kt | 15 +++++ .../addCancellationException1/native/test.kt | 4 ++ .../native/test.kt.after | 6 ++ .../native/kotlin.kt | 7 +++ .../addCancellationException2/native/test.kt | 10 +++ .../native/kotlin.kt | 7 +++ .../addCancellationException3/native/test.kt | 8 +++ .../native/kotlin.kt | 7 +++ .../addCancellationException4/native/test.kt | 8 +++ .../QuickFixMultiModuleTestGenerated.java | 33 ++++++++++ 14 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/AddExceptionToThrowsFix.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/CancellationException.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/kotlin.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt.after create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/kotlin.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/test.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/kotlin.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/test.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/kotlin.kt create mode 100644 idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/test.kt diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties index 7a89201c394..3d22b64fb2f 100644 --- a/idea/resources-en/messages/KotlinBundle.properties +++ b/idea/resources-en/messages/KotlinBundle.properties @@ -2229,4 +2229,5 @@ listbox.import.package=Package listbox.import.with.subpackages=With Subpackages title.import.layout=Import Layout title.packages.to.use.import.with=Packages to Use Import with '*' -redundant.qualifier.unnecessary.non.direct.parent.class.qualifier=Unnecessary non-direct parent classes qualifiers \ No newline at end of file +redundant.qualifier.unnecessary.non.direct.parent.class.qualifier=Unnecessary non-direct parent classes qualifiers +fix.add.exception.to.throws=Add ''{0}'' diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExceptionToThrowsFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExceptionToThrowsFix.kt new file mode 100644 index 00000000000..5c85e5ec06c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExceptionToThrowsFix.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +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 org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.konan.diagnostics.ErrorsNative.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND + +class AddExceptionToThrowsFix( + element: KtAnnotationEntry, + private val argumentClassFqName: FqName +) : KotlinQuickFixAction(element) { + + override fun getText(): String = KotlinBundle.message( + "fix.add.exception.to.throws", + "${argumentClassFqName.shortName().asString()}::class" + ) + + override fun getFamilyName() = this.text + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val annotationEntry = element ?: return + val psiFactory = KtPsiFactory(annotationEntry) + + val argumentText = "${argumentClassFqName.asString()}::class" + val added = annotationEntry.valueArgumentList?.addArgument(psiFactory.createArgument(argumentText)) + + if (added != null) ShortenReferences.DEFAULT.process(added) + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val annotationEntry = diagnostic.psiElement as? KtAnnotationEntry ?: return null + val valueArgumentsList = annotationEntry.valueArgumentList + ?: return null // Note: shouldn't reach here, frontend doesn't report MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND in this case. + + if (valueArgumentsList.arguments.firstOrNull()?.getArgumentName() != null) { + // E.g. @Throws(exceptionClasses = [Foo::class]). + // Unsupported at the moment: + return null + // Note: AddThrowsAnnotationIntention has the code to support this case, but a refactoring is required to use it. + } + + val missingExceptionFqName = when (diagnostic.factory) { + MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND -> MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND.cast(diagnostic).a + else -> return null + } + + return AddExceptionToThrowsFix(annotationEntry, missingExceptionFqName) + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 50d8b3b03f3..55c338a1994 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -50,6 +50,7 @@ import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.* +import org.jetbrains.kotlin.resolve.konan.diagnostics.ErrorsNative.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND class QuickFixRegistrar : QuickFixContributor { override fun registerQuickFixes(quickFixes: QuickFixes) { @@ -651,5 +652,7 @@ class QuickFixRegistrar : QuickFixContributor { TOPLEVEL_TYPEALIASES_ONLY.registerFactory(MoveTypeAliasToTopLevelFix) CONFLICTING_JVM_DECLARATIONS.registerFactory(AddJvmNameAnnotationFix) + + MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND.registerFactory(AddExceptionToThrowsFix) } } diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/CancellationException.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/CancellationException.kt new file mode 100644 index 00000000000..133d124b0b1 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/CancellationException.kt @@ -0,0 +1,3 @@ +package kotlin.coroutines.cancellation + +public open class CancellationException() : IllegalStateException() \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/kotlin.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/kotlin.kt new file mode 100644 index 00000000000..3bf6c44ec73 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/kotlin.kt @@ -0,0 +1,15 @@ +package kotlin + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +public annotation class Throws(vararg val exceptionClasses: KClass) + +public open class Exception : Throwable() + +public open class RuntimeException : Exception() + +public open class IllegalStateException : RuntimeException() + +public open class Error : Throwable() diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt new file mode 100644 index 00000000000..d46aa5783ac --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt @@ -0,0 +1,4 @@ +// "Add 'CancellationException::class'" "true" + +@Throws(Error::class) +suspend fun addCE() {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt.after b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt.after new file mode 100644 index 00000000000..2a8b45f5a91 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/native/test.kt.after @@ -0,0 +1,6 @@ +import kotlin.coroutines.cancellation.CancellationException + +// "Add 'CancellationException::class'" "true" + +@Throws(Error::class, CancellationException::class) +suspend fun addCE() {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/kotlin.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/kotlin.kt new file mode 100644 index 00000000000..2741c5635a4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/kotlin.kt @@ -0,0 +1,7 @@ +package kotlin + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +public annotation class Throws(vararg val exceptionClasses: KClass) diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/test.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/test.kt new file mode 100644 index 00000000000..ed46ddbb565 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/native/test.kt @@ -0,0 +1,10 @@ +// "Add 'CancellationException::class'" "false" +// ERROR: @Throws on suspend declaration must have CancellationException (or any of its superclasses) listed +// ACTION: Make internal +// ACTION: Make private + +class MyException : Throwable() + +// Quickfix doesn't support this case: +@Throws(exceptionClasses = [MyException::class]) +suspend fun addCE() {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/kotlin.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/kotlin.kt new file mode 100644 index 00000000000..2741c5635a4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/kotlin.kt @@ -0,0 +1,7 @@ +package kotlin + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +public annotation class Throws(vararg val exceptionClasses: KClass) diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/test.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/test.kt new file mode 100644 index 00000000000..e54b717ccdf --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/native/test.kt @@ -0,0 +1,8 @@ +// "Add 'CancellationException::class'" "false" +// ERROR: @Throws must have non-empty class list +// ACTION: Make internal +// ACTION: Make private + +// No compilation error => no quickfix. +@Throws() +suspend fun emptyThrows() {} diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/kotlin.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/kotlin.kt new file mode 100644 index 00000000000..2741c5635a4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/kotlin.kt @@ -0,0 +1,7 @@ +package kotlin + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +public annotation class Throws(vararg val exceptionClasses: KClass) diff --git a/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/test.kt b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/test.kt new file mode 100644 index 00000000000..69cb7010e98 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/native/test.kt @@ -0,0 +1,8 @@ +// "Add 'CancellationException::class'" "false" +// ERROR: @Throws must have non-empty class list +// ACTION: Make internal +// ACTION: Make private + +// No compilation error => no quickfix. +@Throws +suspend fun emptyThrows() {} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index 69ad067d138..69557005dd9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -794,6 +794,39 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul } } + @TestMetadata("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FixNativeThrowsErrors extends AbstractQuickFixMultiModuleTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("addCancellationException1") + public void testAddCancellationException1() throws Exception { + runTest("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException1/"); + } + + @TestMetadata("addCancellationException2") + public void testAddCancellationException2() throws Exception { + runTest("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException2/"); + } + + @TestMetadata("addCancellationException3") + public void testAddCancellationException3() throws Exception { + runTest("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException3/"); + } + + @TestMetadata("addCancellationException4") + public void testAddCancellationException4() throws Exception { + runTest("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors/addCancellationException4/"); + } + + public void testAllFilesPresentInFixNativeThrowsErrors() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/multiModuleQuickFix/fixNativeThrowsErrors"), Pattern.compile("^([^\\.]+)$"), null, true); + } + } + @TestMetadata("idea/testData/multiModuleQuickFix/makeOverridenMemberOpen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)