Add inspection and quick-fix to remove empty parentheses in annotation entries

This commit is contained in:
Aleksei Semin
2018-11-10 19:01:36 +01:00
committed by Mikhail Glukhikh
parent e903b2f92a
commit 793cac02e8
18 changed files with 209 additions and 0 deletions
@@ -335,6 +335,13 @@ fun referenceExpressionRecursiveVisitor(block: (KtReferenceExpression) -> Unit)
}
}
fun valueArgumentListVisitor(block: (KtValueArgumentList) -> Unit) =
object : KtVisitorVoid() {
override fun visitValueArgumentList(list: KtValueArgumentList) {
block(list)
}
}
fun valueArgumentVisitor(block: (KtValueArgument) -> Unit) =
object : KtVisitorVoid() {
override fun visitArgument(valueArgument: KtValueArgument) {
@@ -0,0 +1,14 @@
<html>
<body>
This inspection reports unnecessary parentheses in annotation entries. For example:
<br /><br />
<pre>
annotation class MyAnnotation
@MyAnnotation<b>()</b> // There parentheses can be omitted
fun test() {
}
</pre>
</body>
</html>
+9
View File
@@ -3030,6 +3030,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.RemoveEmptyParenthesesFromAnnotationEntryInspection"
displayName="Remove unnecessary parentheses"
groupPath="Kotlin"
groupName="Style issues"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.OptionalExpectationInspection"
displayName="Optionally expected annotation has no actual annotation"
groupPath="Kotlin"
@@ -0,0 +1,65 @@
/*
* 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.inspections
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.jetbrains.kotlin.psi.valueArgumentListVisitor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class RemoveEmptyParenthesesFromAnnotationEntryInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
valueArgumentListVisitor(fun(list) {
if (list.arguments.isNotEmpty()) return
val annotationEntry = list.parent as? KtAnnotationEntry ?: return
if (annotationEntry.typeArguments.isNotEmpty()) return
val annotationClassDescriptor = annotationEntry.getAnnotationClassDescriptor() ?: return
// if all annotation constructors must receive at least one argument
// then parentheses *are* necessary and inspection should not trigger
if (annotationClassDescriptor.constructors.all { it.hasParametersWithoutDefault() }) return
holder.registerProblem(
list,
"Parentheses should be removed",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
RemoveEmptyParenthesesFromAnnotationEntryFix()
)
})
private fun KtAnnotationEntry.getAnnotationClassDescriptor(): ClassDescriptor? {
val context = analyze(BodyResolveMode.PARTIAL)
return context[BindingContext.ANNOTATION, this]?.annotationClass
}
private fun ClassConstructorDescriptor.hasParametersWithoutDefault() = valueParameters.any { !it.declaresDefaultValue() }
}
private class RemoveEmptyParenthesesFromAnnotationEntryFix : LocalQuickFix {
override fun getName() = "Remove unnecessary parentheses"
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
(descriptor.psiElement as? KtValueArgumentList)?.delete()
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.RemoveEmptyParenthesesFromAnnotationEntryInspection
@@ -0,0 +1,6 @@
annotation class MyAnnotation(val x: Int = 10)
@MyAnnotation(<caret>)
fun test() {
}
@@ -0,0 +1,6 @@
annotation class MyAnnotation(val x: Int = 10)
@MyAnnotation
fun test() {
}
@@ -0,0 +1,7 @@
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation
fun test() {
val x = @MyAnnotation(<caret>) 5
}
@@ -0,0 +1,7 @@
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class MyAnnotation
fun test() {
val x = @MyAnnotation 5
}
@@ -0,0 +1,8 @@
annotation class MyAnnotation
@MyAnnotation(
<caret>
)
fun test() {
}
@@ -0,0 +1,6 @@
annotation class MyAnnotation
@MyAnnotation
fun test() {
}
@@ -0,0 +1,9 @@
// PROBLEM: none
// ERROR: No value passed for parameter 'x'
annotation class MyAnnotation(val x: Int)
// there is an error but the inspection is not triggered because parentheses are needed in the end
@MyAnnotation(<caret>)
fun test() {
}
@@ -0,0 +1,6 @@
annotation class MyAnnotation
@MyAnnotation(<caret>)
fun test() {
}
@@ -0,0 +1,6 @@
annotation class MyAnnotation
@MyAnnotation
fun test() {
}
@@ -0,0 +1 @@
public @interface MyJavaAnnotation {}
@@ -0,0 +1,4 @@
@MyJavaAnnotation(<caret>)
fun test() {
}
@@ -0,0 +1,4 @@
@MyJavaAnnotation
fun test() {
}
@@ -4944,6 +4944,49 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
}
}
@TestMetadata("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class RemoveEmptyParenthesesFromAnnotationEntry extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInRemoveEmptyParenthesesFromAnnotationEntry() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("allParameterHaveDefaults.kt")
public void testAllParameterHaveDefaults() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/allParameterHaveDefaults.kt");
}
@TestMetadata("annotatedExpr.kt")
public void testAnnotatedExpr() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/annotatedExpr.kt");
}
@TestMetadata("parenthesesWithWhitespace.kt")
public void testParenthesesWithWhitespace() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/parenthesesWithWhitespace.kt");
}
@TestMetadata("requiresArguments.kt")
public void testRequiresArguments() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/requiresArguments.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/simple.kt");
}
@TestMetadata("usingJavaAnnotation.kt")
public void testUsingJavaAnnotation() throws Exception {
runTest("idea/testData/inspectionsLocal/removeEmptyParenthesesFromAnnotationEntry/usingJavaAnnotation.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/removeRedundantBackticks")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)