Allow to suppress warnings at file level

This commit is contained in:
Nikolay Krasko
2015-12-07 21:11:26 +03:00
committed by Nikolay Krasko
parent 712d2bdec0
commit aff83087a3
24 changed files with 192 additions and 22 deletions
@@ -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
@@ -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
}
}
@@ -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
@@ -0,0 +1,3 @@
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
public fun foo(): String?<caret>? = null
@@ -0,0 +1,5 @@
@file:Suppress("REDUNDANT_NULLABLE")
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
public fun foo(): String?<caret>? = null
@@ -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
@@ -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
@@ -0,0 +1,6 @@
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
/** Some Comment **/
package test
public fun foo(): String?<caret>? = null
@@ -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
@@ -0,0 +1,4 @@
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
@file:Suppress("unused")
public fun foo(): String?<caret>? = null
@@ -0,0 +1,4 @@
// "Suppress 'REDUNDANT_NULLABLE' for file ${file}" "true"
@file:Suppress("unused", "REDUNDANT_NULLABLE")
public fun foo(): String?<caret>? = null
@@ -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,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>?) {}
@@ -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,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,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");