Add quick fix to add required target to annotation #KT-20484 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
204d9e3423
commit
9ea9ea1008
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 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.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
|
import org.jetbrains.kotlin.resolve.AnnotationChecker
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
class AddAnnotationTargetFix(annotationEntry: KtAnnotationEntry) : KotlinQuickFixAction<KtAnnotationEntry>(annotationEntry) {
|
||||||
|
|
||||||
|
override fun getText() = "Add annotation target"
|
||||||
|
|
||||||
|
override fun getFamilyName() = text
|
||||||
|
|
||||||
|
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||||
|
val annotationEntry = element ?: return
|
||||||
|
|
||||||
|
val annotationClass = annotationEntry.toAnnotationClass() ?: return
|
||||||
|
|
||||||
|
val requiredAnnotationTargets = annotationEntry.getRequiredAnnotationTargets(annotationClass, project)
|
||||||
|
if (requiredAnnotationTargets.isEmpty()) return
|
||||||
|
|
||||||
|
val psiFactory = KtPsiFactory(annotationEntry)
|
||||||
|
annotationClass.addAnnotationTargets(requiredAnnotationTargets, psiFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : KotlinSingleIntentionActionFactory() {
|
||||||
|
private fun KtAnnotationEntry.toAnnotationClass(): KtClass? {
|
||||||
|
val context = analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val annotationDescriptor = context[BindingContext.ANNOTATION, this] ?: return null
|
||||||
|
val annotationTypeDescriptor = annotationDescriptor.type.constructor.declarationDescriptor ?: return null
|
||||||
|
return (DescriptorToSourceUtils.descriptorToDeclaration(annotationTypeDescriptor) as? KtClass)?.takeIf {
|
||||||
|
it.isAnnotation() && it.isWritable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtAnnotationEntry>? {
|
||||||
|
val entry = diagnostic.psiElement as? KtAnnotationEntry ?: return null
|
||||||
|
if (entry.toAnnotationClass() == null) return null
|
||||||
|
|
||||||
|
return AddAnnotationTargetFix(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnnotationEntry.getRequiredAnnotationTargets(annotationClass: KtClass, project: Project): List<KotlinTarget> {
|
||||||
|
val requiredTargets = getActualTargetList()
|
||||||
|
if (requiredTargets.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val searchScope = GlobalSearchScope.allScope(project).restrictToKotlinSources()
|
||||||
|
val otherReferenceRequiredTargets = ReferencesSearch.search(annotationClass, searchScope).mapNotNull { reference ->
|
||||||
|
reference.element.getNonStrictParentOfType<KtAnnotationEntry>()?.takeIf { it != this }?.getActualTargetList()
|
||||||
|
}.flatten().toSet()
|
||||||
|
|
||||||
|
val annotationTargetValueNames = AnnotationTarget.values().map { it.name }
|
||||||
|
return (requiredTargets + otherReferenceRequiredTargets).filter { it.name in annotationTargetValueNames }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnnotationEntry.getActualTargetList(): List<KotlinTarget> {
|
||||||
|
val annotatedElement = getStrictParentOfType<KtModifierList>()?.owner as? KtElement ?: return emptyList()
|
||||||
|
return AnnotationChecker.getDeclarationSiteActualTargetList(annotatedElement, null, BindingTraceContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtClass.addAnnotationTargets(annotationTargets: List<KotlinTarget>, psiFactory: KtPsiFactory) {
|
||||||
|
val targetAnnotationName = KotlinBuiltIns.FQ_NAMES.target.shortName().asString()
|
||||||
|
|
||||||
|
val targetAnnotationEntry = annotationEntries.find { it.typeReference?.text == targetAnnotationName } ?: run {
|
||||||
|
val text = "@$targetAnnotationName${annotationTargets.toArgumentListString()}"
|
||||||
|
addAnnotationEntry(psiFactory.createAnnotationEntry(text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val valueArgumentList = targetAnnotationEntry.valueArgumentList
|
||||||
|
if (valueArgumentList == null) {
|
||||||
|
val text = annotationTargets.toArgumentListString()
|
||||||
|
targetAnnotationEntry.add(psiFactory.createCallArguments(text))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val arguments = targetAnnotationEntry.valueArguments.mapNotNull { it.getArgumentExpression()?.text }
|
||||||
|
for (target in annotationTargets) {
|
||||||
|
val text = target.asNameString()
|
||||||
|
if (text !in arguments) valueArgumentList.addArgument(psiFactory.createArgument(text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<KotlinTarget>.toArgumentListString() =
|
||||||
|
joinToString(separator = ", ", prefix = "(", postfix = ")") { it.asNameString() }
|
||||||
|
|
||||||
|
private fun KotlinTarget.asNameString() =
|
||||||
|
"${KotlinBuiltIns.FQ_NAMES.annotationTarget.shortName().asString()}.$name"
|
||||||
@@ -514,6 +514,7 @@ class QuickFixRegistrar : QuickFixContributor {
|
|||||||
|
|
||||||
RETURN_NOT_ALLOWED.registerFactory(ChangeToLabeledReturnFix)
|
RETURN_NOT_ALLOWED.registerFactory(ChangeToLabeledReturnFix)
|
||||||
|
|
||||||
|
WRONG_ANNOTATION_TARGET.registerFactory(AddAnnotationTargetFix)
|
||||||
WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET.registerFactory(MoveReceiverAnnotationFix)
|
WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET.registerFactory(MoveReceiverAnnotationFix)
|
||||||
|
|
||||||
NO_CONSTRUCTOR.registerFactory(RemoveNoConstructorFix)
|
NO_CONSTRUCTOR.registerFactory(RemoveNoConstructorFix)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
fun foo(): <caret>@Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE) annotation class Foo
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
fun foo(): @Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
<caret>@Foo
|
||||||
|
class Test
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target()
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
<caret>@Foo
|
||||||
|
class Test
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test {
|
||||||
|
@Foo
|
||||||
|
fun foo(): <caret>@Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test {
|
||||||
|
@Foo
|
||||||
|
fun foo(): @Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// "Add annotation target" "false"
|
||||||
|
// WITH_RUNTIME
|
||||||
|
// ACTION: Make internal
|
||||||
|
// ACTION: Make private
|
||||||
|
// ACTION: Specify type explicitly
|
||||||
|
// ERROR: This annotation is not applicable to target 'top level property without backing field or delegate'
|
||||||
|
|
||||||
|
<caret>@JvmField
|
||||||
|
val x get() = 42
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
fun foo(): <caret>@Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
fun foo(): @Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test {
|
||||||
|
@Foo
|
||||||
|
fun foo(): <caret>@Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||||
|
annotation class Foo
|
||||||
|
|
||||||
|
@Foo
|
||||||
|
class Test {
|
||||||
|
@Foo
|
||||||
|
fun foo(): @Foo Int = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.FIELD)
|
||||||
|
annotation class FieldAnn
|
||||||
|
|
||||||
|
<caret>@FieldAnn
|
||||||
|
val x get() = 42
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add annotation target" "true"
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY)
|
||||||
|
annotation class FieldAnn
|
||||||
|
|
||||||
|
@FieldAnn
|
||||||
|
val x get() = 42
|
||||||
@@ -237,6 +237,63 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/quickfix/addAnnotationTarget")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class AddAnnotationTarget extends AbstractQuickFixTest {
|
||||||
|
public void testAllFilesPresentInAddAnnotationTarget() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addAnnotationTarget"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basic1.kt")
|
||||||
|
public void testBasic1() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/basic1.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basic2.kt")
|
||||||
|
public void testBasic2() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/basic2.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basic3.kt")
|
||||||
|
public void testBasic3() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/basic3.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("basic4.kt")
|
||||||
|
public void testBasic4() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/basic4.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("fromLib.kt")
|
||||||
|
public void testFromLib() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/fromLib.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hasAnnotationTarget1.kt")
|
||||||
|
public void testHasAnnotationTarget1() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/hasAnnotationTarget1.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hasAnnotationTarget2.kt")
|
||||||
|
public void testHasAnnotationTarget2() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/hasAnnotationTarget2.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noBackingField.kt")
|
||||||
|
public void testNoBackingField() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addAnnotationTarget/noBackingField.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/quickfix/addCrossinline")
|
@TestMetadata("idea/testData/quickfix/addCrossinline")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user