Extract psi- and descriptior-independent suppress cache logic
This commit is contained in:
+104
-89
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
@@ -40,44 +41,22 @@ interface DiagnosticSuppressor {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class KotlinSuppressCache {
|
||||
private val diagnosticSuppressors = ExtensionProvider.create(DiagnosticSuppressor.EP_NAME)
|
||||
|
||||
abstract class AbstractKotlinSuppressCache<Element> {
|
||||
// The cache is weak: we're OK with losing it
|
||||
private val suppressors = ContainerUtil.createConcurrentWeakValueMap<KtAnnotated, Suppressor>()
|
||||
protected val suppressors = ContainerUtil.createConcurrentWeakValueMap<Element, Suppressor<Element>>()
|
||||
|
||||
val filter: (Diagnostic) -> Boolean = { diagnostic: Diagnostic ->
|
||||
!isSuppressed(DiagnosticSuppressRequest(diagnostic))
|
||||
}
|
||||
fun isSuppressed(element: Element, suppressionKey: String, severity: Severity) =
|
||||
isSuppressed(StringSuppressRequest(element, severity, suppressionKey.lowercase()))
|
||||
|
||||
fun isSuppressed(psiElement: PsiElement, suppressionKey: String, severity: Severity) =
|
||||
isSuppressed(StringSuppressRequest(psiElement, severity, suppressionKey.lowercase()))
|
||||
protected open fun isSuppressed(request: SuppressRequest<Element>): Boolean {
|
||||
|
||||
private fun isSuppressed(request: SuppressRequest): Boolean {
|
||||
// If diagnostics are reported in a synthetic file generated by KtPsiFactory (dummy.kt),
|
||||
// there's no point to present such diagnostics to the user, because the user didn't write this code
|
||||
val element = request.element
|
||||
if (!element.isValid) return true
|
||||
|
||||
val file = element.containingFile
|
||||
if (file is KtFile) {
|
||||
if (file.doNotAnalyze != null) return true
|
||||
}
|
||||
|
||||
if (request is DiagnosticSuppressRequest) {
|
||||
for (suppressor in diagnosticSuppressors.get()) {
|
||||
if (isSuppressedByExtension(suppressor, request.diagnostic)) return true
|
||||
}
|
||||
}
|
||||
|
||||
val annotated = KtStubbedPsiUtil.getPsiOrStubParent(element, KtAnnotated::class.java, false) ?: return false
|
||||
val annotated = getParentAnnotatedElement(request.element, false) ?: return false
|
||||
|
||||
return isSuppressedByAnnotated(request.suppressKey, request.severity, annotated, 0)
|
||||
|
||||
}
|
||||
|
||||
protected open fun isSuppressedByExtension(suppressor: DiagnosticSuppressor, diagnostic: Diagnostic): Boolean {
|
||||
return suppressor.isSuppressed(diagnostic)
|
||||
}
|
||||
protected abstract fun getParentAnnotatedElement(element: Element, strict: Boolean): Element?
|
||||
|
||||
/*
|
||||
The cache is optimized for the case where no warnings are suppressed (most frequent one)
|
||||
@@ -108,16 +87,16 @@ abstract class KotlinSuppressCache {
|
||||
|
||||
This way we need no more lookups than the number of suppress() annotations from here to the root.
|
||||
*/
|
||||
private fun isSuppressedByAnnotated(
|
||||
protected open fun isSuppressedByAnnotated(
|
||||
suppressionKey: String,
|
||||
severity: Severity,
|
||||
annotated: KtAnnotated,
|
||||
annotated: Element,
|
||||
debugDepth: Int
|
||||
): Boolean {
|
||||
val suppressor = getOrCreateSuppressor(annotated)
|
||||
if (suppressor.isSuppressed(suppressionKey, severity)) return true
|
||||
|
||||
val annotatedAbove = KtStubbedPsiUtil.getPsiOrStubParent(suppressor.annotatedElement, KtAnnotated::class.java, true) ?: return false
|
||||
val annotatedAbove = getParentAnnotatedElement(suppressor.annotatedElement, true) ?: return false
|
||||
|
||||
val suppressed = isSuppressedByAnnotated(suppressionKey, severity, annotatedAbove, debugDepth + 1)
|
||||
val suppressorAbove = suppressors[annotatedAbove]
|
||||
@@ -128,7 +107,7 @@ abstract class KotlinSuppressCache {
|
||||
return suppressed
|
||||
}
|
||||
|
||||
private fun getOrCreateSuppressor(annotated: KtAnnotated): Suppressor =
|
||||
protected fun getOrCreateSuppressor(annotated: Element): Suppressor<Element> =
|
||||
suppressors.getOrPut(annotated) {
|
||||
val strings = getSuppressingStrings(annotated)
|
||||
when (strings.size) {
|
||||
@@ -138,9 +117,74 @@ abstract class KotlinSuppressCache {
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getSuppressionAnnotations(annotated: KtAnnotated): List<AnnotationDescriptor>
|
||||
protected abstract fun getSuppressingStrings(annotated: Element): Set<String>
|
||||
|
||||
private fun getSuppressingStrings(annotated: KtAnnotated): Set<String> {
|
||||
companion object {
|
||||
private fun isSuppressedByStrings(key: String, strings: Set<String>, severity: Severity): Boolean =
|
||||
severity == Severity.WARNING && "warnings" in strings || key.lowercase() in strings
|
||||
}
|
||||
|
||||
protected abstract class Suppressor<Element>(val annotatedElement: Element) {
|
||||
abstract fun isSuppressed(suppressionKey: String, severity: Severity): Boolean
|
||||
|
||||
// true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
|
||||
abstract fun dominates(other: Suppressor<Element>): Boolean
|
||||
}
|
||||
|
||||
private class EmptySuppressor<Element>(annotated: Element) : Suppressor<Element>(annotated) {
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean = false
|
||||
override fun dominates(other: Suppressor<Element>): Boolean = other is EmptySuppressor
|
||||
}
|
||||
|
||||
private class SingularSuppressor<Element>(annotated: Element, private val string: String) : Suppressor<Element>(annotated) {
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
|
||||
return isSuppressedByStrings(suppressionKey, ImmutableSet.of(string), severity)
|
||||
}
|
||||
|
||||
override fun dominates(other: Suppressor<Element>): Boolean {
|
||||
return other is EmptySuppressor || (other is SingularSuppressor && other.string == string)
|
||||
}
|
||||
}
|
||||
|
||||
private class MultiSuppressor<Element>(annotated: Element, private val strings: Set<String>) : Suppressor<Element>(annotated) {
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
|
||||
return isSuppressedByStrings(suppressionKey, strings, severity)
|
||||
}
|
||||
|
||||
override fun dominates(other: Suppressor<Element>): Boolean {
|
||||
// it's too costly to check set inclusion
|
||||
return other is EmptySuppressor
|
||||
}
|
||||
}
|
||||
|
||||
protected interface SuppressRequest<Element> {
|
||||
val element: Element
|
||||
val severity: Severity
|
||||
val suppressKey: String
|
||||
}
|
||||
|
||||
private class StringSuppressRequest<Element>(
|
||||
override val element: Element,
|
||||
override val severity: Severity,
|
||||
override val suppressKey: String
|
||||
) : SuppressRequest<Element>
|
||||
}
|
||||
|
||||
abstract class KotlinSuppressCache : AbstractKotlinSuppressCache<PsiElement>() {
|
||||
|
||||
private val diagnosticSuppressors = ExtensionProvider.create(DiagnosticSuppressor.EP_NAME)
|
||||
|
||||
val filter: (Diagnostic) -> Boolean = { diagnostic: Diagnostic ->
|
||||
!isSuppressed(DiagnosticSuppressRequest(diagnostic))
|
||||
}
|
||||
|
||||
protected open fun isSuppressedByExtension(suppressor: DiagnosticSuppressor, diagnostic: Diagnostic): Boolean {
|
||||
return suppressor.isSuppressed(diagnostic)
|
||||
}
|
||||
|
||||
abstract fun getSuppressionAnnotations(annotated: PsiElement): List<AnnotationDescriptor>
|
||||
|
||||
override fun getSuppressingStrings(annotated: PsiElement): Set<String> {
|
||||
val builder = ImmutableSet.builder<String>()
|
||||
|
||||
for (annotationDescriptor in getSuppressionAnnotations(annotated)) {
|
||||
@@ -165,76 +209,47 @@ abstract class KotlinSuppressCache {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getDiagnosticSuppressKey(diagnostic: Diagnostic): String =
|
||||
diagnostic.factory.name.lowercase()
|
||||
override fun isSuppressed(request: SuppressRequest<PsiElement>): Boolean {
|
||||
// If diagnostics are reported in a synthetic file generated by KtPsiFactory (dummy.kt),
|
||||
// there's no point to present such diagnostics to the user, because the user didn't write this code
|
||||
val element = request.element
|
||||
if (!element.isValid) return true
|
||||
|
||||
private fun isSuppressedByStrings(key: String, strings: Set<String>, severity: Severity): Boolean =
|
||||
severity == Severity.WARNING && "warnings" in strings || key in strings
|
||||
}
|
||||
|
||||
private abstract class Suppressor(val annotatedElement: KtAnnotated) {
|
||||
open fun isSuppressed(diagnostic: Diagnostic): Boolean =
|
||||
isSuppressed(getDiagnosticSuppressKey(diagnostic), diagnostic.severity)
|
||||
|
||||
abstract fun isSuppressed(suppressionKey: String, severity: Severity): Boolean
|
||||
|
||||
// true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
|
||||
abstract fun dominates(other: Suppressor): Boolean
|
||||
}
|
||||
|
||||
private class EmptySuppressor(annotated: KtAnnotated) : Suppressor(annotated) {
|
||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean = false
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean = false
|
||||
override fun dominates(other: Suppressor): Boolean = other is EmptySuppressor
|
||||
}
|
||||
|
||||
private class SingularSuppressor(annotated: KtAnnotated, private val string: String) : Suppressor(annotated) {
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
|
||||
return isSuppressedByStrings(suppressionKey, ImmutableSet.of(string), severity)
|
||||
val file = element.containingFile
|
||||
if (file is KtFile) {
|
||||
if (file.doNotAnalyze != null) return true
|
||||
}
|
||||
|
||||
override fun dominates(other: Suppressor): Boolean {
|
||||
return other is EmptySuppressor || (other is SingularSuppressor && other.string == string)
|
||||
if (request is DiagnosticSuppressRequest) {
|
||||
for (suppressor in diagnosticSuppressors.get()) {
|
||||
if (isSuppressedByExtension(suppressor, request.diagnostic)) return true
|
||||
}
|
||||
}
|
||||
return super.isSuppressed(request)
|
||||
}
|
||||
|
||||
private class MultiSuppressor(annotated: KtAnnotated, private val strings: Set<String>) : Suppressor(annotated) {
|
||||
override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
|
||||
return isSuppressedByStrings(suppressionKey, strings, severity)
|
||||
}
|
||||
override fun getParentAnnotatedElement(element: PsiElement, strict: Boolean) =
|
||||
KtStubbedPsiUtil.getPsiOrStubParent(element, KtAnnotated::class.java, strict)
|
||||
|
||||
override fun dominates(other: Suppressor): Boolean {
|
||||
// it's too costly to check set inclusion
|
||||
return other is EmptySuppressor
|
||||
}
|
||||
}
|
||||
|
||||
private interface SuppressRequest {
|
||||
val element: PsiElement
|
||||
val severity: Severity
|
||||
val suppressKey: String
|
||||
}
|
||||
|
||||
private class StringSuppressRequest(
|
||||
override val element: PsiElement,
|
||||
override val severity: Severity,
|
||||
override val suppressKey: String
|
||||
) : SuppressRequest
|
||||
|
||||
private class DiagnosticSuppressRequest(val diagnostic: Diagnostic) : SuppressRequest {
|
||||
protected class DiagnosticSuppressRequest(val diagnostic: Diagnostic) : SuppressRequest<PsiElement> {
|
||||
override val element: PsiElement get() = diagnostic.psiElement
|
||||
override val severity: Severity get() = diagnostic.severity
|
||||
override val suppressKey: String get() = getDiagnosticSuppressKey(diagnostic)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun getDiagnosticSuppressKey(diagnostic: Diagnostic): String =
|
||||
diagnostic.factory.name.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
class BindingContextSuppressCache(val context: BindingContext) : KotlinSuppressCache() {
|
||||
override fun getSuppressionAnnotations(annotated: KtAnnotated): List<AnnotationDescriptor> {
|
||||
override fun getSuppressionAnnotations(annotated: PsiElement): List<AnnotationDescriptor> {
|
||||
val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated)
|
||||
|
||||
return descriptor?.annotations?.toList()
|
||||
?: annotated.annotationEntries.mapNotNull { context.get(BindingContext.ANNOTATION, it) }
|
||||
?: (annotated as? KtAnnotated)?.annotationEntries?.mapNotNull { context.get(BindingContext.ANNOTATION, it) }
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override fun isSuppressedByExtension(suppressor: DiagnosticSuppressor, diagnostic: Diagnostic): Boolean {
|
||||
|
||||
+5
-4
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.resolve.diagnostics
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||
@@ -23,8 +24,8 @@ import org.jetbrains.kotlin.resolve.BindingContext
|
||||
* primary use case of this class is when that binding context is cleared and thus is useless after a certain point.
|
||||
*/
|
||||
class PrecomputedSuppressCache(context: BindingContext, files: List<KtFile>) : KotlinSuppressCache() {
|
||||
val storage: Map<KtAnnotated, List<AnnotationDescriptor>> =
|
||||
mutableMapOf<KtAnnotated, List<AnnotationDescriptor>>().also { storage ->
|
||||
val storage: Map<PsiElement, List<AnnotationDescriptor>> =
|
||||
mutableMapOf<PsiElement, List<AnnotationDescriptor>>().also { storage ->
|
||||
val visitor = PrecomputingVisitor(storage, BindingContextSuppressCache(context))
|
||||
for (file in files) {
|
||||
file.accept(visitor, null)
|
||||
@@ -32,7 +33,7 @@ class PrecomputedSuppressCache(context: BindingContext, files: List<KtFile>) : K
|
||||
}
|
||||
|
||||
private class PrecomputingVisitor(
|
||||
val storage: MutableMap<KtAnnotated, List<AnnotationDescriptor>>,
|
||||
val storage: MutableMap<PsiElement, List<AnnotationDescriptor>>,
|
||||
val computer: KotlinSuppressCache,
|
||||
) : KtTreeVisitorVoid() {
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
@@ -55,6 +56,6 @@ class PrecomputedSuppressCache(context: BindingContext, files: List<KtFile>) : K
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSuppressionAnnotations(annotated: KtAnnotated): List<AnnotationDescriptor> =
|
||||
override fun getSuppressionAnnotations(annotated: PsiElement): List<AnnotationDescriptor> =
|
||||
storage[annotated].orEmpty()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user