All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2015 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.resolve.diagnostics

import com.google.common.collect.ImmutableSet
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
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.KtFile
import org.jetbrains.kotlin.psi.KtStubbedPsiUtil
import org.jetbrains.kotlin.psi.doNotAnalyze
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.util.ExtensionProvider

interface SuppressStringProvider {
    operator fun get(annotationDescriptor: AnnotationDescriptor): List

    companion object {
        val EP_NAME: ExtensionPointName =
                ExtensionPointName.create("org.jetbrains.kotlin.suppressStringProvider")
    }
}

interface DiagnosticSuppressor {
    fun isSuppressed(diagnostic: Diagnostic): Boolean

    companion object {
        val EP_NAME: ExtensionPointName =
                ExtensionPointName.create("org.jetbrains.kotlin.diagnosticSuppressor")
    }
}

abstract class KotlinSuppressCache {
    private val LOG = Logger.getInstance(DiagnosticsWithSuppression::class.java)

    private val ADDITIONAL_SUPPRESS_STRING_PROVIDERS = ExtensionProvider.create(SuppressStringProvider.EP_NAME)
    private val DIAGNOSTIC_SUPPRESSORS = ExtensionProvider.create(DiagnosticSuppressor.EP_NAME)

    // The cache is weak: we're OK with losing it
    private val suppressors =  ContainerUtil.createConcurrentWeakValueMap()

    val filter: (Diagnostic) -> Boolean = { diagnostic: Diagnostic -> !isSuppressed(diagnostic) }

    fun isSuppressed(psiElement: PsiElement, suppressionKey: String, severity: Severity) =
            isSuppressed(StringSuppressRequest(psiElement, severity, suppressionKey.toLowerCase()))

    fun isSuppressed(diagnostic: Diagnostic): Boolean = isSuppressed(DiagnosticSuppressRequest(diagnostic))

    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 DIAGNOSTIC_SUPPRESSORS.get()) {
                if (suppressor.isSuppressed(request.diagnostic)) return true
            }
        }

        val annotated = KtStubbedPsiUtil.getPsiOrStubParent(element, KtAnnotated::class.java, false) ?: return false

        return isSuppressedByAnnotated(request.suppressKey, request.severity, annotated, 0)
    }


    /*
       The cache is optimized for the case where no warnings are suppressed (most frequent one)

       trait Root {
         suppress("X")
         trait A {
           trait B {
             suppress("Y")
             trait C {
               fun foo() = warning
             }
           }
         }
       }

       Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated
       all the suppressors down, so now we have:

          foo  - suppress(Y) from C
          C    - suppress(Y) from C
          B    - suppress(X) from A
          A    - suppress(X) from A
          Root - suppress() from Root

       Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty
       suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point.

       This way we need no more lookups than the number of suppress() annotations from here to the root.
     */
    protected fun isSuppressedByAnnotated(suppressionKey: String, severity: Severity, annotated: KtAnnotated, debugDepth: Int): Boolean {
        if (LOG.isDebugEnabled) {
            LOG.debug("Annotated: ", annotated.name)
            LOG.debug("Depth: ", debugDepth)
            LOG.debug("Cache size: ", suppressors.size, "\n")
        }

        val suppressor = getOrCreateSuppressor(annotated)
        if (suppressor.isSuppressed(suppressionKey, severity)) return true

        val annotatedAbove = KtStubbedPsiUtil.getPsiOrStubParent(suppressor.annotatedElement, KtAnnotated::class.java, true) ?: return false

        val suppressed = isSuppressedByAnnotated(suppressionKey, severity, annotatedAbove, debugDepth + 1)
        val suppressorAbove = suppressors[annotatedAbove]
        if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
            suppressors.put(annotated, suppressorAbove)
        }

        return suppressed
    }

    private fun getOrCreateSuppressor(annotated: KtAnnotated): Suppressor {
        var suppressor: Suppressor? = suppressors[annotated]
        if (suppressor == null) {
            val strings = getSuppressingStrings(annotated)
            if (strings.isEmpty()) {
                suppressor = EmptySuppressor(annotated)
            }
            else if (strings.size == 1) {
                suppressor = SingularSuppressor(annotated, strings.iterator().next())
            }
            else {
                suppressor = MultiSuppressor(annotated, strings)
            }
            suppressors.put(annotated, suppressor)
        }
        return suppressor
    }

    abstract fun getSuppressionAnnotations(annotated: KtAnnotated): List

    private fun getSuppressingStrings(annotated: KtAnnotated): Set {
        val builder = ImmutableSet.builder()
        for (annotationDescriptor in getSuppressionAnnotations(annotated)) {
            processAnnotation(builder, annotationDescriptor)
        }

        return builder.build()
    }

    private fun processAnnotation(builder: ImmutableSet.Builder, annotationDescriptor: AnnotationDescriptor) {
        for (suppressStringProvider in ADDITIONAL_SUPPRESS_STRING_PROVIDERS.get()) {
            builder.addAll(suppressStringProvider[annotationDescriptor])
        }

        if (!KotlinBuiltIns.isSuppressAnnotation(annotationDescriptor)) return

        // We only add strings and skip other values to facilitate recovery in presence of erroneous code
        for (arrayValue in annotationDescriptor.allValueArguments.values) {
            if ((arrayValue is ArrayValue)) {
                for (value in arrayValue.value) {
                    if (value is StringValue) {
                        builder.add(value.value.toLowerCase())
                    }
                }
            }
        }
    }

    companion object {
        fun getDiagnosticSuppressKey(diagnostic: Diagnostic): String {
            return diagnostic.factory.name.toLowerCase()
        }

        fun isSuppressedByStrings(key: String, strings: Set, severity: Severity): Boolean {
            if (strings.contains("warnings") && severity == Severity.WARNING) return true

            return strings.contains(key)
        }
    }

    private abstract class Suppressor protected constructor(val annotatedElement: KtAnnotated) {
        abstract fun isSuppressed(diagnostic: Diagnostic): Boolean
        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(diagnostic: Diagnostic): Boolean {
            return isSuppressed(getDiagnosticSuppressKey(diagnostic), diagnostic.severity)
        }

        override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
            return isSuppressedByStrings(suppressionKey, ImmutableSet.of(string), severity)
        }

        override fun dominates(other: Suppressor): Boolean {
            return other is EmptySuppressor || (other is SingularSuppressor && other.string == string)
        }
    }

    private class MultiSuppressor(annotated: KtAnnotated, private val strings: Set) : Suppressor(annotated) {
        override fun isSuppressed(diagnostic: Diagnostic): Boolean {
            return isSuppressed(getDiagnosticSuppressKey(diagnostic), diagnostic.severity)
        }

        override fun isSuppressed(suppressionKey: String, severity: Severity): Boolean {
            return isSuppressedByStrings(suppressionKey, strings, severity)
        }

        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 {
        override val element: PsiElement get() =  diagnostic.psiElement
        override val severity: Severity get() = diagnostic.severity
        override val suppressKey: String get() = getDiagnosticSuppressKey(diagnostic)
    }
}

class BindingContextSuppressCache(val context: BindingContext) : KotlinSuppressCache() {
    override fun getSuppressionAnnotations(annotated: KtAnnotated): List {
        val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated)

        if (descriptor != null) {
            return descriptor.annotations.toList()
        }
        else {
            return annotated.annotationEntries.mapNotNull { context.get(BindingContext.ANNOTATION, it) }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy