Allow to suppress warnings at file level
This commit is contained in:
committed by
Nikolay Krasko
parent
712d2bdec0
commit
aff83087a3
@@ -155,6 +155,14 @@ public class KtPsiFactory(private val project: Project) {
|
||||
return createClass("class A {\n companion object{\n}\n}").getCompanionObjects().first()
|
||||
}
|
||||
|
||||
public fun createFileAnnotation(annotationText: String): KtAnnotationEntry {
|
||||
return createFileAnnotationListWithAnnotation(annotationText).annotationEntries.first()
|
||||
}
|
||||
|
||||
public fun createFileAnnotationListWithAnnotation(annotationText: String) : KtFileAnnotationList {
|
||||
return createFile("@file:${annotationText}").fileAnnotationList!!
|
||||
}
|
||||
|
||||
public fun createFile(text: String): KtFile {
|
||||
return createFile("dummy.kt", text)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import com.intellij.psi.search.PsiSearchScopeUtil
|
||||
import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtFileAnnotationList
|
||||
import java.util.*
|
||||
|
||||
// NOTE: in this file we collect only LANGUAGE INDEPENDENT methods working with PSI and not modifying it
|
||||
@@ -312,6 +314,29 @@ public fun PsiElement.getElementTextWithContext(): String {
|
||||
|
||||
public fun PsiElement.getTextWithLocation(): String = "'${this.getText()}' at ${DiagnosticUtils.atLocation(this)}"
|
||||
|
||||
fun replaceFileAnnotationList(file: KtFile, annotationList: KtFileAnnotationList): KtFileAnnotationList {
|
||||
if (file.fileAnnotationList != null) {
|
||||
return file.fileAnnotationList!!.replace(annotationList) as KtFileAnnotationList
|
||||
}
|
||||
|
||||
val beforeAnchor : PsiElement? = when {
|
||||
file.packageDirective?.packageKeyword != null -> file.packageDirective!!
|
||||
file.importList != null -> file.importList!!
|
||||
file.declarations.firstOrNull() != null -> file.declarations.first()
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (beforeAnchor != null) {
|
||||
return file.addBefore(annotationList, beforeAnchor) as KtFileAnnotationList
|
||||
}
|
||||
|
||||
if (file.lastChild == null) {
|
||||
return file.add(annotationList) as KtFileAnnotationList
|
||||
}
|
||||
|
||||
return file.addAfter(annotationList, file.lastChild) as KtFileAnnotationList
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
operator fun SearchScope.contains(element: PsiElement): Boolean = PsiSearchScopeUtil.isInScope(this, element)
|
||||
@@ -320,3 +345,4 @@ public fun <E : PsiElement> E.createSmartPointer(): SmartPsiElementPointer<E> =
|
||||
SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(this)
|
||||
|
||||
public fun PsiElement.before(element: PsiElement) = textRange.endOffset <= element.textRange.startOffset
|
||||
|
||||
|
||||
+21
-12
@@ -57,20 +57,29 @@ fun createSuppressWarningActions(element: PsiElement, severity: Severity, suppre
|
||||
var current: PsiElement? = element
|
||||
var suppressAtStatementAllowed = true
|
||||
while (current != null) {
|
||||
if (current is KtDeclaration && current !is KtDestructuringDeclaration) {
|
||||
val declaration = current
|
||||
val kind = DeclarationKindDetector.detect(declaration)
|
||||
if (kind != null) {
|
||||
actions.add(KotlinSuppressIntentionAction(declaration, suppressionKey, kind))
|
||||
when {
|
||||
current is KtDeclaration && current !is KtDestructuringDeclaration -> {
|
||||
val declaration = current
|
||||
val kind = DeclarationKindDetector.detect(declaration)
|
||||
if (kind != null) {
|
||||
actions.add(KotlinSuppressIntentionAction(declaration, suppressionKey, kind))
|
||||
}
|
||||
suppressAtStatementAllowed = false
|
||||
}
|
||||
suppressAtStatementAllowed = false
|
||||
}
|
||||
else if (current is KtExpression && suppressAtStatementAllowed) {
|
||||
// Add suppress action at first statement
|
||||
if (current.parent is KtBlockExpression || current.parent is KtDestructuringDeclaration) {
|
||||
val kind = if (current.parent is KtBlockExpression) "statement" else "initializer"
|
||||
|
||||
current is KtExpression && suppressAtStatementAllowed -> {
|
||||
// Add suppress action at first statement
|
||||
if (current.parent is KtBlockExpression || current.parent is KtDestructuringDeclaration) {
|
||||
val kind = if (current.parent is KtBlockExpression) "statement" else "initializer"
|
||||
actions.add(KotlinSuppressIntentionAction(current, suppressionKey,
|
||||
AnnotationHostKind(kind, "", true)))
|
||||
suppressAtStatementAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
current is KtFile -> {
|
||||
actions.add(KotlinSuppressIntentionAction(current, suppressionKey,
|
||||
AnnotationHostKind(kind, "", true)))
|
||||
AnnotationHostKind("file", current.name, true)))
|
||||
suppressAtStatementAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
+55
-10
@@ -25,13 +25,21 @@ import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.util.PsiPrecedences
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaceFileAnnotationList
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
public class KotlinSuppressIntentionAction(
|
||||
private val suppressAt: KtExpression,
|
||||
class KotlinSuppressIntentionAction private constructor(
|
||||
private val suppressAt: PsiElement,
|
||||
private val suppressKey: String,
|
||||
private val kind: AnnotationHostKind
|
||||
) : SuppressIntentionAction() {
|
||||
constructor(suppressAt: KtExpression,
|
||||
suppressKey: String,
|
||||
kind: AnnotationHostKind) : this(suppressAt as PsiElement, suppressKey, kind)
|
||||
|
||||
constructor(suppressAt: KtFile,
|
||||
suppressKey: String,
|
||||
kind: AnnotationHostKind) : this(suppressAt as PsiElement, suppressKey, kind)
|
||||
|
||||
override fun getFamilyName() = KotlinBundle.message("suppress.warnings.family")
|
||||
override fun getText() = KotlinBundle.message("suppress.warning.for", suppressKey, kind.kind, kind.name)
|
||||
@@ -40,15 +48,43 @@ public class KotlinSuppressIntentionAction(
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
|
||||
val id = "\"$suppressKey\""
|
||||
if (suppressAt is KtModifierListOwner) {
|
||||
suppressAtModifierListOwner(suppressAt, id)
|
||||
when (suppressAt) {
|
||||
is KtModifierListOwner ->
|
||||
suppressAtModifierListOwner(suppressAt, id)
|
||||
|
||||
is KtAnnotatedExpression ->
|
||||
suppressAtAnnotatedExpression(CaretBox(suppressAt, editor), id)
|
||||
|
||||
is KtExpression ->
|
||||
suppressAtExpression(CaretBox(suppressAt, editor), id)
|
||||
|
||||
is KtFile ->
|
||||
suppressAtFile(suppressAt, id)
|
||||
}
|
||||
else if (suppressAt is KtAnnotatedExpression) {
|
||||
suppressAtAnnotatedExpression(CaretBox(suppressAt, editor), id)
|
||||
}
|
||||
|
||||
private fun suppressAtFile(ktFile: KtFile, id: String) {
|
||||
val psiFactory = KtPsiFactory(suppressAt)
|
||||
|
||||
val fileAnnotationList: KtFileAnnotationList? = ktFile.fileAnnotationList
|
||||
if (fileAnnotationList == null) {
|
||||
val newAnnotationList = psiFactory.createFileAnnotationListWithAnnotation(suppressAnnotationText(id, false))
|
||||
val createAnnotationList = replaceFileAnnotationList(ktFile, newAnnotationList)
|
||||
ktFile.addAfter(psiFactory.createWhiteSpace(kind), createAnnotationList)
|
||||
|
||||
return
|
||||
}
|
||||
else if (suppressAt is KtExpression) {
|
||||
suppressAtExpression(CaretBox(suppressAt, editor), id)
|
||||
|
||||
val suppressAnnotation: KtAnnotationEntry? = findSuppressAnnotation(fileAnnotationList)
|
||||
if (suppressAnnotation == null) {
|
||||
val newSuppressAnnotation = psiFactory.createFileAnnotation(suppressAnnotationText(id, false))
|
||||
fileAnnotationList.add(psiFactory.createWhiteSpace(kind))
|
||||
fileAnnotationList.add(newSuppressAnnotation) as KtAnnotationEntry
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
addArgumentToSuppressAnnotation(suppressAnnotation, id)
|
||||
}
|
||||
|
||||
private fun suppressAtModifierListOwner(suppressAt: KtModifierListOwner, id: String) {
|
||||
@@ -125,11 +161,20 @@ public class KotlinSuppressIntentionAction(
|
||||
}
|
||||
}
|
||||
|
||||
private fun suppressAnnotationText(id: String) = "@Suppress($id)"
|
||||
private fun suppressAnnotationText(id: String, withAt: Boolean = true) = "${if (withAt) "@" else ""}${KotlinBuiltIns.FQ_NAMES.suppress.shortName()}($id)"
|
||||
|
||||
private fun findSuppressAnnotation(annotated: KtAnnotated): KtAnnotationEntry? {
|
||||
val context = annotated.analyze()
|
||||
for (entry in annotated.getAnnotationEntries()) {
|
||||
return findSuppressAnnotation(context, annotated.getAnnotationEntries())
|
||||
}
|
||||
|
||||
private fun findSuppressAnnotation(annotationList: KtFileAnnotationList): KtAnnotationEntry? {
|
||||
val context = annotationList.analyze()
|
||||
return findSuppressAnnotation(context, annotationList.annotationEntries)
|
||||
}
|
||||
|
||||
private fun findSuppressAnnotation(context: BindingContext, annotationEntries: List<KtAnnotationEntry>): KtAnnotationEntry? {
|
||||
for (entry in annotationEntries) {
|
||||
val annotationDescriptor = context.get(BindingContext.ANNOTATION, entry)
|
||||
if (annotationDescriptor != null && KotlinBuiltIns.isSuppressAnnotation(annotationDescriptor)) {
|
||||
return entry
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
@file:Suppress("REDUNDANT_NULLABLE")
|
||||
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
// ERROR: This annotation is not applicable to target 'file' and use site target '@file'
|
||||
|
||||
@file:Deprecated("Some")
|
||||
|
||||
package test
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
// ERROR: This annotation is not applicable to target 'file' and use site target '@file'
|
||||
|
||||
@file:Deprecated("Some")
|
||||
@file:Suppress("REDUNDANT_NULLABLE")
|
||||
|
||||
package test
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
/** Some Comment **/
|
||||
|
||||
package test
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
/** Some Comment **/
|
||||
|
||||
@file:Suppress("REDUNDANT_NULLABLE")
|
||||
|
||||
package test
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
@file:Suppress("unused")
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
|
||||
@file:Suppress("unused", "REDUNDANT_NULLABLE")
|
||||
|
||||
public fun foo(): String?<caret>? = null
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'INTEGER_OVERFLOW' for fun foo
|
||||
// ACTION: Suppress 'INTEGER_OVERFLOW' for file inAnnotationArgument.kt
|
||||
|
||||
@Ann(Integer.MAX_VALUE<caret> + 1)
|
||||
fun foo() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for class Child
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file inClassHeader.kt
|
||||
|
||||
open class Base(s: String)
|
||||
class Child: Base(""<caret>!!)
|
||||
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for fun foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for parameter s
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file inDefaultArgument.kt
|
||||
|
||||
fun foo(s: String = ""<caret>!!) {}
|
||||
@@ -1,4 +1,5 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for fun foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file inExpressionBody.kt
|
||||
|
||||
fun foo() = ""<caret>!!
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for fun foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for val bar
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file inLocalValInitializer.kt
|
||||
|
||||
fun foo() {
|
||||
val bar = ""<caret>!!
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for fun foo
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for parameter s
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for file inParameterType.kt
|
||||
|
||||
fun foo(s: String?<caret>?) {}
|
||||
Vendored
+1
@@ -1,6 +1,7 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for fun foo
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for parameter x
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for file inParameterTypeInFunctionLiteral.kt
|
||||
|
||||
fun foo() {
|
||||
any {
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for val foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file inPropertyInitializer.kt
|
||||
|
||||
val foo = ""<caret>!!
|
||||
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for fun foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file objectLiteral.kt
|
||||
|
||||
fun foo() {
|
||||
object : Base(""<caret>!!) {
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for fun foo
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for val a
|
||||
// ACTION: Suppress 'UNNECESSARY_NOT_NULL_ASSERTION' for file objectLiteralInsideExpression.kt
|
||||
|
||||
fun foo() {
|
||||
val a = object : Base(""<caret>!!) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// "class com.intellij.codeInspection.SuppressIntentionAction" "false"
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for class Child
|
||||
// ACTION: Suppress 'REDUNDANT_NULLABLE' for file supretype.kt
|
||||
|
||||
open class Base<T>
|
||||
class Child: Base<String?<caret>?>()
|
||||
@@ -6101,6 +6101,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelFunctionSuppressOnFile.kt")
|
||||
public void testTopLevelFunctionSuppressOnFile() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/suppress/annotationPosition/topLevelFunctionSuppressOnFile.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelFunctionSuppressOnFileOtherAnnotation.kt")
|
||||
public void testTopLevelFunctionSuppressOnFileOtherAnnotation() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/suppress/annotationPosition/topLevelFunctionSuppressOnFileOtherAnnotation.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelFunctionSuppressOnFileWithPackage.kt")
|
||||
public void testTopLevelFunctionSuppressOnFileWithPackage() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/suppress/annotationPosition/topLevelFunctionSuppressOnFileWithPackage.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelFunctionSuppressOnFileWithSuppress.kt")
|
||||
public void testTopLevelFunctionSuppressOnFileWithSuppress() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/suppress/annotationPosition/topLevelFunctionSuppressOnFileWithSuppress.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("topLevelFunctionUnrelatedAnnotation.kt")
|
||||
public void testTopLevelFunctionUnrelatedAnnotation() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/suppress/annotationPosition/topLevelFunctionUnrelatedAnnotation.kt");
|
||||
|
||||
Reference in New Issue
Block a user