Experimental fixes: introduce "add annotation" (KT-22760)
This commit is contained in:
@@ -34,7 +34,7 @@ class AnnotationChecker(
|
||||
private val languageVersionSettings: LanguageVersionSettings
|
||||
) {
|
||||
fun check(annotated: KtAnnotated, trace: BindingTrace, descriptor: DeclarationDescriptor? = null) {
|
||||
val actualTargets = getActualTargetList(annotated, descriptor, trace)
|
||||
val actualTargets = getActualTargetList(annotated, descriptor, trace.bindingContext)
|
||||
checkEntries(annotated.annotationEntries, actualTargets, trace, annotated)
|
||||
if (annotated is KtCallableDeclaration) {
|
||||
annotated.typeReference?.let { check(it, trace) }
|
||||
@@ -64,7 +64,7 @@ class AnnotationChecker(
|
||||
}
|
||||
|
||||
fun checkExpression(expression: KtExpression, trace: BindingTrace) {
|
||||
checkEntries(expression.getAnnotationEntries(), getActualTargetList(expression, null, trace), trace)
|
||||
checkEntries(expression.getAnnotationEntries(), getActualTargetList(expression, null, trace.bindingContext), trace)
|
||||
if (expression is KtLambdaExpression) {
|
||||
for (parameter in expression.valueParameters) {
|
||||
parameter.typeReference?.let { check(it, trace) }
|
||||
@@ -197,15 +197,15 @@ class AnnotationChecker(
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
fun getDeclarationSiteActualTargetList(annotated: KtElement, descriptor: ClassDescriptor?, trace: BindingTrace):
|
||||
fun getDeclarationSiteActualTargetList(annotated: KtElement, descriptor: ClassDescriptor?, context: BindingContext):
|
||||
List<KotlinTarget> {
|
||||
return getActualTargetList(annotated, descriptor, trace).defaultTargets
|
||||
return getActualTargetList(annotated, descriptor, context).defaultTargets
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor?.hasBackingField(bindingTrace: BindingTrace) =
|
||||
(this as? PropertyDescriptor)?.let { bindingTrace.get(BindingContext.BACKING_FIELD_REQUIRED, it) } ?: false
|
||||
private fun DeclarationDescriptor?.hasBackingField(context: BindingContext) =
|
||||
(this as? PropertyDescriptor)?.let { context.get(BindingContext.BACKING_FIELD_REQUIRED, it) } ?: false
|
||||
|
||||
fun getActualTargetList(annotated: KtElement, descriptor: DeclarationDescriptor?, trace: BindingTrace): TargetList {
|
||||
fun getActualTargetList(annotated: KtElement, descriptor: DeclarationDescriptor?, context: BindingContext): TargetList {
|
||||
return when (annotated) {
|
||||
is KtClassOrObject ->
|
||||
(descriptor as? ClassDescriptor)?.let { TargetList(KotlinTarget.classActualTargets(it)) } ?: TargetLists.T_CLASSIFIER
|
||||
@@ -213,8 +213,8 @@ class AnnotationChecker(
|
||||
is KtProperty -> {
|
||||
when {
|
||||
annotated.isLocal -> TargetLists.T_LOCAL_VARIABLE
|
||||
annotated.isMember -> TargetLists.T_MEMBER_PROPERTY(descriptor.hasBackingField(trace), annotated.hasDelegate())
|
||||
else -> TargetLists.T_TOP_LEVEL_PROPERTY(descriptor.hasBackingField(trace), annotated.hasDelegate())
|
||||
annotated.isMember -> TargetLists.T_MEMBER_PROPERTY(descriptor.hasBackingField(context), annotated.hasDelegate())
|
||||
else -> TargetLists.T_TOP_LEVEL_PROPERTY(descriptor.hasBackingField(context), annotated.hasDelegate())
|
||||
}
|
||||
}
|
||||
is KtParameter -> {
|
||||
|
||||
@@ -446,7 +446,9 @@ object ModifierCheckerCore {
|
||||
}
|
||||
}
|
||||
}
|
||||
val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(listOwner, descriptor as? ClassDescriptor, trace)
|
||||
val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(
|
||||
listOwner, descriptor as? ClassDescriptor, trace.bindingContext
|
||||
)
|
||||
val list = listOwner.modifierList ?: return
|
||||
checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets, languageVersionSettings)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
open class AddAnnotationFix(
|
||||
element: KtDeclaration,
|
||||
private val annotationFqName: FqName,
|
||||
private val suffix: String = ""
|
||||
) : KotlinQuickFixAction<KtDeclaration>(element) {
|
||||
override fun getText(): String = "Add '@${annotationFqName.shortName()}' annotation$suffix"
|
||||
|
||||
override fun getFamilyName(): String = "Add annotation"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
element?.addAnnotation(annotationFqName)
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ private fun KtAnnotationEntry.getActualTargetList(): List<KotlinTarget> {
|
||||
?: getStrictParentOfType<KtFile>()
|
||||
?: return emptyList()
|
||||
|
||||
val targetList = AnnotationChecker.getActualTargetList(annotatedElement, null, BindingTraceContext())
|
||||
val targetList = AnnotationChecker.getActualTargetList(annotatedElement, null, BindingTraceContext().bindingContext)
|
||||
|
||||
val useSiteTarget = this.useSiteTarget ?: return targetList.defaultTargets
|
||||
val annotationUseSiteTarget = useSiteTarget.getAnnotationUseSiteTarget()
|
||||
|
||||
@@ -6,25 +6,15 @@
|
||||
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.util.addAnnotation
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.resolve.annotations.JVM_DEFAULT_FQ_NAME
|
||||
|
||||
class AddJvmDefaultAnnotation(declaration: KtCallableDeclaration) : KotlinQuickFixAction<KtCallableDeclaration>(declaration) {
|
||||
override fun getText(): String = "Add '@JvmDefault' annotation"
|
||||
|
||||
class AddJvmDefaultAnnotation(declaration: KtCallableDeclaration) : AddAnnotationFix(declaration, JVM_DEFAULT_FQ_NAME) {
|
||||
override fun getFamilyName(): String = text
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
element?.addAnnotation(JVM_DEFAULT_FQ_NAME)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val psiElement = diagnostic.psiElement
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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 org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.descriptors.resolveClassByFqName
|
||||
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.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
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.descriptorUtil.module
|
||||
|
||||
object ExperimentalFixesFactory : KotlinIntentionActionsFactory() {
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
val element = diagnostic.psiElement
|
||||
val containingDeclaration: KtDeclaration = element.getStrictParentOfType() ?: return emptyList()
|
||||
|
||||
val factory = diagnostic.factory
|
||||
val annotationFqName = when (factory) {
|
||||
EXPERIMENTAL_API_USAGE -> EXPERIMENTAL_API_USAGE.cast(diagnostic).a
|
||||
EXPERIMENTAL_API_USAGE_ERROR -> EXPERIMENTAL_API_USAGE_ERROR.cast(diagnostic).a
|
||||
EXPERIMENTAL_OVERRIDE -> EXPERIMENTAL_OVERRIDE.cast(diagnostic).a
|
||||
EXPERIMENTAL_OVERRIDE_ERROR -> EXPERIMENTAL_OVERRIDE_ERROR.cast(diagnostic).a
|
||||
else -> null
|
||||
} ?: return emptyList()
|
||||
|
||||
val moduleDescriptor = containingDeclaration.resolveToDescriptorIfAny()?.module ?: return emptyList()
|
||||
val annotationClassDescriptor = moduleDescriptor.resolveClassByFqName(
|
||||
annotationFqName, NoLookupLocation.FROM_IDE
|
||||
) ?: return emptyList()
|
||||
val applicableTargets = AnnotationChecker.applicableTargetSet(annotationClassDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
|
||||
|
||||
val context = when (element) {
|
||||
is KtElement -> element.analyze()
|
||||
else -> containingDeclaration.analyze()
|
||||
}
|
||||
|
||||
fun isApplicableTo(declaration: KtDeclaration, applicableTargets: Set<KotlinTarget>): Boolean {
|
||||
val actualTargetList = AnnotationChecker.getDeclarationSiteActualTargetList(
|
||||
declaration, declaration.toDescriptor() as? ClassDescriptor, context
|
||||
)
|
||||
return actualTargetList.any { it in applicableTargets }
|
||||
}
|
||||
|
||||
val result = mutableListOf<IntentionAction>()
|
||||
if (isApplicableTo(containingDeclaration, applicableTargets)) {
|
||||
result.add(AddAnnotationFix(containingDeclaration, annotationFqName, " to '${containingDeclaration.name}'"))
|
||||
}
|
||||
if (containingDeclaration is KtCallableDeclaration) {
|
||||
val containingClassOrObject = containingDeclaration.containingClassOrObject
|
||||
if (containingClassOrObject != null && isApplicableTo(containingClassOrObject, applicableTargets)) {
|
||||
result.add(
|
||||
AddAnnotationFix(containingClassOrObject, annotationFqName, " to containing class '${containingClassOrObject.name}'")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -546,5 +546,10 @@ class QuickFixRegistrar : QuickFixContributor {
|
||||
|
||||
JVM_DEFAULT_REQUIRED_FOR_OVERRIDE.registerFactory(AddJvmDefaultAnnotation)
|
||||
NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT.registerFactory(AddJvmDefaultAnnotation)
|
||||
|
||||
EXPERIMENTAL_API_USAGE.registerFactory(ExperimentalFixesFactory)
|
||||
EXPERIMENTAL_API_USAGE_ERROR.registerFactory(ExperimentalFixesFactory)
|
||||
EXPERIMENTAL_OVERRIDE.registerFactory(ExperimentalFixesFactory)
|
||||
EXPERIMENTAL_OVERRIDE_ERROR.registerFactory(ExperimentalFixesFactory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to 'bar'" "true"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Experimental
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
@MyExperimentalAPI
|
||||
fun foo() {}
|
||||
|
||||
class Bar {
|
||||
fun bar() {
|
||||
foo<caret>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to 'bar'" "true"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Experimental
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
@MyExperimentalAPI
|
||||
fun foo() {}
|
||||
|
||||
class Bar {
|
||||
@MyExperimentalAPI
|
||||
fun bar() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to containing class 'Bar'" "true"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Experimental
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
@MyExperimentalAPI
|
||||
fun foo() {}
|
||||
|
||||
class Bar {
|
||||
fun bar() {
|
||||
foo<caret>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to containing class 'Bar'" "true"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
|
||||
@Experimental
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
@MyExperimentalAPI
|
||||
fun foo() {}
|
||||
|
||||
@MyExperimentalAPI
|
||||
class Bar {
|
||||
fun bar() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to 'bar'" "false"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
// ACTION: Add '@MyExperimentalAPI' annotation to containing class '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)'
|
||||
|
||||
@Experimental
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
@MyExperimentalAPI
|
||||
class Some {
|
||||
fun foo() {}
|
||||
}
|
||||
|
||||
class Bar {
|
||||
fun bar() {
|
||||
Some().foo<caret>()
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// "Add '@MyExperimentalAPI' annotation to containing class 'Derived'" "false"
|
||||
// COMPILER_ARGUMENTS: -Xuse-experimental=kotlin.Experimental
|
||||
// WITH_RUNTIME
|
||||
// ACTION: Add '@MyExperimentalAPI' annotation to 'foo'
|
||||
// ERROR: This declaration overrides experimental member of supertype 'Base' and must be annotated with '@MyExperimentalAPI'
|
||||
|
||||
@Experimental
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class MyExperimentalAPI
|
||||
|
||||
open class Base {
|
||||
@MyExperimentalAPI
|
||||
open fun foo() {}
|
||||
}
|
||||
|
||||
class Derived : Base() {
|
||||
override fun foo<caret>() {}
|
||||
}
|
||||
+13
@@ -2455,6 +2455,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/experimental")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Experimental extends AbstractQuickFixMultiFileTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInExperimental() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/experimental"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/expressions")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -6026,6 +6026,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/experimental")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Experimental extends AbstractQuickFixTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInExperimental() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/experimental"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("basicFunction.kt")
|
||||
public void testBasicFunction() throws Exception {
|
||||
runTest("idea/testData/quickfix/experimental/basicFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("basicFunctionContainingClass.kt")
|
||||
public void testBasicFunctionContainingClass() throws Exception {
|
||||
runTest("idea/testData/quickfix/experimental/basicFunctionContainingClass.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("basicFunctionNotApplicable.kt")
|
||||
public void testBasicFunctionNotApplicable() throws Exception {
|
||||
runTest("idea/testData/quickfix/experimental/basicFunctionNotApplicable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("override.kt")
|
||||
public void testOverride() throws Exception {
|
||||
runTest("idea/testData/quickfix/experimental/override.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/expressions")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user