Add quickfix for adding CancellableException to @Throws suspend fun

This commit is contained in:
Svyatoslav Scherbina
2020-05-25 15:04:42 +03:00
parent 290a824107
commit b7a08494ae
14 changed files with 176 additions and 1 deletions
@@ -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
redundant.qualifier.unnecessary.non.direct.parent.class.qualifier=Unnecessary non-direct parent classes qualifiers
fix.add.exception.to.throws=Add ''{0}''
@@ -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<KtAnnotationEntry>(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)
}
}
}
@@ -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)
}
}
@@ -0,0 +1,3 @@
package kotlin.coroutines.cancellation
public open class CancellationException() : IllegalStateException()
@@ -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<out Throwable>)
public open class Exception : Throwable()
public open class RuntimeException : Exception()
public open class IllegalStateException : RuntimeException()
public open class Error : Throwable()
@@ -0,0 +1,4 @@
// "Add 'CancellationException::class'" "true"
<caret>@Throws(Error::class)
suspend fun addCE() {}
@@ -0,0 +1,6 @@
import kotlin.coroutines.cancellation.CancellationException
// "Add 'CancellationException::class'" "true"
@Throws(Error::class, CancellationException::class)
suspend fun addCE() {}
@@ -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<out Throwable>)
@@ -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:
<caret>@Throws(exceptionClasses = [MyException::class])
suspend fun addCE() {}
@@ -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<out Throwable>)
@@ -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.
<caret>@Throws()
suspend fun emptyThrows() {}
@@ -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<out Throwable>)
@@ -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.
<caret>@Throws
suspend fun emptyThrows() {}
@@ -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)