Experimental fixes: introduce "Add @UseExperimental" #KT-22760 Fixed

This commit is contained in:
Mikhail Glukhikh
2018-05-08 14:59:01 +03:00
parent f404d6c97a
commit 51fba03253
10 changed files with 121 additions and 10 deletions
@@ -61,7 +61,7 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
companion object {
val EXPERIMENTAL_FQ_NAME = FqName("kotlin.Experimental")
internal val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
val USE_EXPERIMENTAL_FQ_NAME = FqName("kotlin.UseExperimental")
internal val WAS_EXPERIMENTAL_FQ_NAME = FqName("kotlin.WasExperimental")
internal val USE_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
internal val WAS_EXPERIMENTAL_ANNOTATION_CLASS = Name.identifier("markerClass")
@@ -15,13 +15,17 @@ import org.jetbrains.kotlin.psi.KtFile
open class AddAnnotationFix(
element: KtDeclaration,
private val annotationFqName: FqName,
private val suffix: String = ""
private val suffix: String = "",
private val argumentClassFqName: FqName? = null
) : KotlinQuickFixAction<KtDeclaration>(element) {
override fun getText(): String = "Add '@${annotationFqName.shortName()}' annotation$suffix"
override fun getText(): String {
val argumentsAsString = argumentClassFqName?.shortName()?.let { "($it::class)" } ?: ""
return "Add '@${annotationFqName.shortName()}$argumentsAsString' annotation$suffix"
}
override fun getFamilyName(): String = "Add annotation"
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
element?.addAnnotation(annotationFqName)
element?.addAnnotation(annotationFqName, annotationInnerText = argumentClassFqName?.let { "$it::class" })
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.module
object ExperimentalFixesFactory : KotlinIntentionActionsFactory() {
@@ -56,15 +57,30 @@ object ExperimentalFixesFactory : KotlinIntentionActionsFactory() {
}
val result = mutableListOf<IntentionAction>()
if (isApplicableTo(containingDeclaration, applicableTargets)) {
result.add(AddAnnotationFix(containingDeclaration, annotationFqName, " to '${containingDeclaration.name}'"))
run {
val suffix = " to '${containingDeclaration.name}'"
if (isApplicableTo(containingDeclaration, applicableTargets)) {
result.add(AddAnnotationFix(containingDeclaration, annotationFqName, suffix))
}
result.add(
AddAnnotationFix(
containingDeclaration, ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME, suffix, annotationFqName
)
)
}
if (containingDeclaration is KtCallableDeclaration) {
val containingClassOrObject = containingDeclaration.containingClassOrObject
if (containingClassOrObject != null && isApplicableTo(containingClassOrObject, applicableTargets)) {
result.add(
AddAnnotationFix(containingClassOrObject, annotationFqName, " to containing class '${containingClassOrObject.name}'")
)
if (containingClassOrObject != null) {
val suffix = " to containing class '${containingClassOrObject.name}'"
if (isApplicableTo(containingClassOrObject, applicableTargets)) {
result.add(AddAnnotationFix(containingClassOrObject, annotationFqName, suffix))
} else {
result.add(
AddAnnotationFix(
containingClassOrObject, ExperimentalUsageChecker.USE_EXPERIMENTAL_FQ_NAME, suffix, annotationFqName
)
)
}
}
}
@@ -2,6 +2,7 @@
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
// ACTION: Add '@MyExperimentalAPI' annotation to containing class 'Bar'
// ACTION: Add '@UseExperimental(MyExperimentalAPI::class)' annotation to 'bar'
// ERROR: This declaration is experimental and its usage must be marked with '@MyExperimentalAPI' or '@UseExperimental(MyExperimentalAPI::class)'
// ERROR: This declaration is experimental and its usage must be marked with '@MyExperimentalAPI' or '@UseExperimental(MyExperimentalAPI::class)'
@@ -0,0 +1,20 @@
// "Add '@UseExperimental(MyExperimentalAPI::class)' annotation to 'bar'" "true"
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
package a.b
@Experimental
@Target(AnnotationTarget.CLASS)
annotation class MyExperimentalAPI
@MyExperimentalAPI
class Some {
fun foo() {}
}
class Bar {
fun bar() {
Some().foo<caret>()
}
}
@@ -0,0 +1,21 @@
// "Add '@UseExperimental(MyExperimentalAPI::class)' annotation to 'bar'" "true"
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
package a.b
@Experimental
@Target(AnnotationTarget.CLASS)
annotation class MyExperimentalAPI
@MyExperimentalAPI
class Some {
fun foo() {}
}
class Bar {
@UseExperimental(MyExperimentalAPI::class)
fun bar() {
Some().foo()
}
}
@@ -0,0 +1,18 @@
// "Add '@UseExperimental(MyExperimentalAPI::class)' annotation to containing class 'Bar'" "true"
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
package a.b
@Experimental
@Target(AnnotationTarget.FUNCTION)
annotation class MyExperimentalAPI
@MyExperimentalAPI
fun foo() {}
class Bar {
fun bar() {
foo<caret>()
}
}
@@ -0,0 +1,19 @@
// "Add '@UseExperimental(MyExperimentalAPI::class)' annotation to containing class 'Bar'" "true"
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
package a.b
@Experimental
@Target(AnnotationTarget.FUNCTION)
annotation class MyExperimentalAPI
@MyExperimentalAPI
fun foo() {}
@UseExperimental(MyExperimentalAPI::class)
class Bar {
fun bar() {
foo()
}
}
+2
View File
@@ -2,6 +2,8 @@
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
// WITH_RUNTIME
// ACTION: Add '@MyExperimentalAPI' annotation to 'foo'
// ACTION: Add '@UseExperimental(MyExperimentalAPI::class)' annotation to 'foo'
// ACTION: Add '@UseExperimental(MyExperimentalAPI::class)' annotation to containing class 'Derived'
// ERROR: This declaration overrides experimental member of supertype 'Base' and must be annotated with '@MyExperimentalAPI'
@Experimental
@@ -6053,6 +6053,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/experimental/basicFunctionNotApplicable.kt");
}
@TestMetadata("basicUseExperimental.kt")
public void testBasicUseExperimental() throws Exception {
runTest("idea/testData/quickfix/experimental/basicUseExperimental.kt");
}
@TestMetadata("classUseExperimental.kt")
public void testClassUseExperimental() throws Exception {
runTest("idea/testData/quickfix/experimental/classUseExperimental.kt");
}
@TestMetadata("override.kt")
public void testOverride() throws Exception {
runTest("idea/testData/quickfix/experimental/override.kt");