Refactoring: extract separate class that can store suppressions
This commit is contained in:
committed by
Nikolay Krasko
parent
e562b73eff
commit
20379028e8
+8
-264
@@ -16,73 +16,28 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.diagnostics;
|
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.openapi.util.Condition;
|
import com.intellij.openapi.util.Condition;
|
||||||
import com.intellij.openapi.util.ModificationTracker;
|
import com.intellij.openapi.util.ModificationTracker;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
|
||||||
import com.intellij.util.containers.ConcurrentWeakValueHashMap;
|
|
||||||
import com.intellij.util.containers.FilteringIterator;
|
import com.intellij.util.containers.FilteringIterator;
|
||||||
import kotlin.CollectionsKt;
|
import kotlin.CollectionsKt;
|
||||||
import kotlin.jvm.functions.Function1;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.TestOnly;
|
import org.jetbrains.annotations.TestOnly;
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||||
import org.jetbrains.kotlin.diagnostics.Severity;
|
|
||||||
import org.jetbrains.kotlin.psi.*;
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue;
|
|
||||||
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
|
|
||||||
import org.jetbrains.kotlin.resolve.constants.StringValue;
|
|
||||||
import org.jetbrains.kotlin.util.ExtensionProvider;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class DiagnosticsWithSuppression implements Diagnostics {
|
public class DiagnosticsWithSuppression implements Diagnostics {
|
||||||
|
private final SuppressionManager suppressionManager;
|
||||||
public interface SuppressStringProvider {
|
|
||||||
ExtensionPointName<SuppressStringProvider> EP_NAME = ExtensionPointName.create("org.jetbrains.kotlin.suppressStringProvider");
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
List<String> get(@NotNull AnnotationDescriptor annotationDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DiagnosticSuppressor {
|
|
||||||
ExtensionPointName<DiagnosticSuppressor> EP_NAME = ExtensionPointName.create("org.jetbrains.kotlin.diagnosticSuppressor");
|
|
||||||
|
|
||||||
boolean isSuppressed(@NotNull Diagnostic diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
|
|
||||||
|
|
||||||
private static final ExtensionProvider<SuppressStringProvider> ADDITIONAL_SUPPRESS_STRING_PROVIDERS
|
|
||||||
= ExtensionProvider.create(SuppressStringProvider.EP_NAME);
|
|
||||||
private static final ExtensionProvider<DiagnosticSuppressor> DIAGNOSTIC_SUPPRESSORS
|
|
||||||
= ExtensionProvider.create(DiagnosticSuppressor.EP_NAME);
|
|
||||||
|
|
||||||
private final BindingContext context;
|
|
||||||
private final Collection<Diagnostic> diagnostics;
|
private final Collection<Diagnostic> diagnostics;
|
||||||
|
private final DiagnosticsElementsCache elementsCache;
|
||||||
// The cache is weak: we're OK with losing it
|
|
||||||
private final Map<KtAnnotated, Suppressor> suppressors = new ConcurrentWeakValueHashMap<KtAnnotated, Suppressor>();
|
|
||||||
|
|
||||||
private final Function1<Diagnostic, Boolean> filter = new Function1<Diagnostic, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean invoke(Diagnostic diagnostic) {
|
|
||||||
return !isSuppressed(diagnostic);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final DiagnosticsElementsCache elementsCache = new DiagnosticsElementsCache(this, filter);
|
|
||||||
|
|
||||||
public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
|
public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
|
||||||
this.context = context;
|
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
|
this.suppressionManager = new SuppressionManager(context);
|
||||||
|
this.elementsCache = new DiagnosticsElementsCache(this, suppressionManager.getFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -97,7 +52,7 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
|||||||
return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), new Condition<Diagnostic>() {
|
return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), new Condition<Diagnostic>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean value(Diagnostic diagnostic) {
|
public boolean value(Diagnostic diagnostic) {
|
||||||
return filter.invoke(diagnostic);
|
return suppressionManager.getFilter().invoke(diagnostic);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -105,7 +60,7 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Collection<Diagnostic> all() {
|
public Collection<Diagnostic> all() {
|
||||||
return CollectionsKt.filter(diagnostics, filter);
|
return CollectionsKt.filter(diagnostics, suppressionManager.getFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -119,223 +74,12 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
|||||||
return all().isEmpty();
|
return all().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
|
||||||
PsiElement element = diagnostic.getPsiElement();
|
|
||||||
|
|
||||||
// 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
|
|
||||||
PsiFile file = element.getContainingFile();
|
|
||||||
if (file instanceof KtFile) {
|
|
||||||
if (KtPsiFactoryKt.getDoNotAnalyze((KtFile) file) != null) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DiagnosticSuppressor suppressor : DIAGNOSTIC_SUPPRESSORS.get()) {
|
|
||||||
if (suppressor.isSuppressed(diagnostic)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
KtAnnotated annotated = KtStubbedPsiUtil.getPsiOrStubParent(element, KtAnnotated.class, false);
|
|
||||||
if (annotated == null) return false;
|
|
||||||
|
|
||||||
return isSuppressedByAnnotated(diagnostic, 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.
|
|
||||||
*/
|
|
||||||
private boolean isSuppressedByAnnotated(@NotNull Diagnostic diagnostic, @NotNull KtAnnotated annotated, int debugDepth) {
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
LOG.debug("Annotated: ", annotated.getName());
|
|
||||||
LOG.debug("Depth: ", debugDepth);
|
|
||||||
LOG.debug("Cache size: ", suppressors.size(), "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
Suppressor suppressor = getOrCreateSuppressor(annotated);
|
|
||||||
if (suppressor.isSuppressed(diagnostic)) return true;
|
|
||||||
|
|
||||||
KtAnnotated annotatedAbove = KtStubbedPsiUtil.getPsiOrStubParent(suppressor.getAnnotatedElement(), KtAnnotated.class, true);
|
|
||||||
if (annotatedAbove == null) return false;
|
|
||||||
|
|
||||||
boolean suppressed = isSuppressedByAnnotated(diagnostic, annotatedAbove, debugDepth + 1);
|
|
||||||
Suppressor suppressorAbove = suppressors.get(annotatedAbove);
|
|
||||||
if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
|
|
||||||
suppressors.put(annotated, suppressorAbove);
|
|
||||||
}
|
|
||||||
|
|
||||||
return suppressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Suppressor getOrCreateSuppressor(@NotNull KtAnnotated annotated) {
|
|
||||||
Suppressor suppressor = suppressors.get(annotated);
|
|
||||||
if (suppressor == null) {
|
|
||||||
Set<String> strings = getSuppressingStrings(annotated);
|
|
||||||
if (strings.isEmpty()) {
|
|
||||||
suppressor = new EmptySuppressor(annotated);
|
|
||||||
}
|
|
||||||
else if (strings.size() == 1) {
|
|
||||||
suppressor = new SingularSuppressor(annotated, strings.iterator().next());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
suppressor = new MultiSuppressor(annotated, strings);
|
|
||||||
}
|
|
||||||
suppressors.put(annotated, suppressor);
|
|
||||||
}
|
|
||||||
return suppressor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getSuppressingStrings(@NotNull KtAnnotated annotated) {
|
|
||||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
|
||||||
|
|
||||||
DeclarationDescriptor descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated);
|
|
||||||
|
|
||||||
if (descriptor != null) {
|
|
||||||
for (AnnotationDescriptor annotationDescriptor : descriptor.getAnnotations()) {
|
|
||||||
processAnnotation(builder, annotationDescriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (KtAnnotationEntry annotationEntry : annotated.getAnnotationEntries()) {
|
|
||||||
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
|
|
||||||
processAnnotation(builder, annotationDescriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processAnnotation(ImmutableSet.Builder<String> builder, AnnotationDescriptor annotationDescriptor) {
|
|
||||||
if (annotationDescriptor == null) return;
|
|
||||||
|
|
||||||
for (SuppressStringProvider suppressStringProvider : ADDITIONAL_SUPPRESS_STRING_PROVIDERS.get()) {
|
|
||||||
builder.addAll(suppressStringProvider.get(annotationDescriptor));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!KotlinBuiltIns.isSuppressAnnotation(annotationDescriptor)) return;
|
|
||||||
|
|
||||||
// We only add strings and skip other values to facilitate recovery in presence of erroneous code
|
|
||||||
for (ConstantValue<?> arrayValue : annotationDescriptor.getAllValueArguments().values()) {
|
|
||||||
if ((arrayValue instanceof ArrayValue)) {
|
|
||||||
for (ConstantValue<?> value : ((ArrayValue) arrayValue).getValue()) {
|
|
||||||
if (value instanceof StringValue) {
|
|
||||||
builder.add(String.valueOf(((StringValue) value).getValue()).toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
|
|
||||||
if (strings.contains("warnings") && diagnostic.getSeverity() == Severity.WARNING) return true;
|
|
||||||
|
|
||||||
return strings.contains(diagnostic.getFactory().getName().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public ModificationTracker getModificationTracker() {
|
public ModificationTracker getModificationTracker() {
|
||||||
throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression.");
|
throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static abstract class Suppressor {
|
|
||||||
private final KtAnnotated annotated;
|
|
||||||
|
|
||||||
protected Suppressor(@NotNull KtAnnotated annotated) {
|
|
||||||
this.annotated = annotated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public KtAnnotated getAnnotatedElement() {
|
|
||||||
return annotated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean isSuppressed(@NotNull Diagnostic diagnostic);
|
|
||||||
|
|
||||||
// true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
|
|
||||||
public abstract boolean dominates(@NotNull Suppressor other);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EmptySuppressor extends Suppressor {
|
|
||||||
|
|
||||||
private EmptySuppressor(@NotNull KtAnnotated annotated) {
|
|
||||||
super(annotated);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dominates(@NotNull Suppressor other) {
|
|
||||||
return other instanceof EmptySuppressor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SingularSuppressor extends Suppressor {
|
|
||||||
private final String string;
|
|
||||||
|
|
||||||
private SingularSuppressor(@NotNull KtAnnotated annotated, @NotNull String string) {
|
|
||||||
super(annotated);
|
|
||||||
this.string = string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
|
||||||
return isSuppressedByStrings(diagnostic, ImmutableSet.of(string));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dominates(@NotNull Suppressor other) {
|
|
||||||
return other instanceof EmptySuppressor
|
|
||||||
|| (other instanceof SingularSuppressor && ((SingularSuppressor) other).string.equals(string));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MultiSuppressor extends Suppressor {
|
|
||||||
private final Set<String> strings;
|
|
||||||
|
|
||||||
private MultiSuppressor(@NotNull KtAnnotated annotated, @NotNull Set<String> strings) {
|
|
||||||
super(annotated);
|
|
||||||
this.strings = strings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
|
|
||||||
return isSuppressedByStrings(diagnostic, strings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dominates(@NotNull Suppressor other) {
|
|
||||||
// it's too costly to check set inclusion
|
|
||||||
return other instanceof EmptySuppressor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
@NotNull
|
@NotNull
|
||||||
public Collection<Diagnostic> getDiagnostics() {
|
public Collection<Diagnostic> getDiagnostics() {
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ public val PROPERTY_NOT_INITIALIZED_ERRORS: List<DiagnosticFactory0<KtProperty>>
|
|||||||
public abstract class SuppressDiagnosticsByAnnotations(
|
public abstract class SuppressDiagnosticsByAnnotations(
|
||||||
diagnosticsToSuppress: List<DiagnosticFactory<out Diagnostic>>,
|
diagnosticsToSuppress: List<DiagnosticFactory<out Diagnostic>>,
|
||||||
vararg annotationsFqName: FqName
|
vararg annotationsFqName: FqName
|
||||||
) : DiagnosticsWithSuppression.SuppressStringProvider {
|
) : SuppressStringProvider {
|
||||||
|
|
||||||
private val annotationsFqName = annotationsFqName
|
private val annotationsFqName = annotationsFqName
|
||||||
private val stringsToSuppress = diagnosticsToSuppress.map { it.getName().toLowerCase() }
|
private val stringsToSuppress = diagnosticsToSuppress.map { it.getName().toLowerCase() }
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.containers.ConcurrentWeakValueHashMap
|
||||||
|
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<String>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EP_NAME = ExtensionPointName.create<SuppressStringProvider>("org.jetbrains.kotlin.suppressStringProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiagnosticSuppressor {
|
||||||
|
fun isSuppressed(diagnostic: Diagnostic): Boolean
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EP_NAME = ExtensionPointName.create<DiagnosticSuppressor>("org.jetbrains.kotlin.diagnosticSuppressor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SuppressionManager(val context: BindingContext) {
|
||||||
|
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 = ConcurrentWeakValueHashMap<KtAnnotated, Suppressor>()
|
||||||
|
|
||||||
|
public val filter: (Diagnostic) -> Boolean = { diagnostic: Diagnostic -> !isSuppressed(diagnostic) }
|
||||||
|
|
||||||
|
private fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||||
|
val element = diagnostic.psiElement
|
||||||
|
|
||||||
|
// 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 file = element.containingFile
|
||||||
|
if (file is KtFile) {
|
||||||
|
if (file.doNotAnalyze != null) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (suppressor in DIAGNOSTIC_SUPPRESSORS.get()) {
|
||||||
|
if (suppressor.isSuppressed(diagnostic)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotated = KtStubbedPsiUtil.getPsiOrStubParent(element, KtAnnotated::class.java, false) ?: return false
|
||||||
|
|
||||||
|
return isSuppressedByAnnotated(diagnostic, 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.
|
||||||
|
*/
|
||||||
|
private fun isSuppressedByAnnotated(diagnostic: Diagnostic, 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(diagnostic)) return true
|
||||||
|
|
||||||
|
val annotatedAbove = KtStubbedPsiUtil.getPsiOrStubParent(suppressor.annotatedElement, KtAnnotated::class.java, true) ?: return false
|
||||||
|
|
||||||
|
val suppressed = isSuppressedByAnnotated(diagnostic, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSuppressingStrings(annotated: KtAnnotated): Set<String> {
|
||||||
|
val builder = ImmutableSet.builder<String>()
|
||||||
|
|
||||||
|
val descriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated)
|
||||||
|
|
||||||
|
if (descriptor != null) {
|
||||||
|
for (annotationDescriptor in descriptor.annotations) {
|
||||||
|
processAnnotation(builder, annotationDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (annotationEntry in annotated.annotationEntries) {
|
||||||
|
val annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry)
|
||||||
|
processAnnotation(builder, annotationDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processAnnotation(builder: ImmutableSet.Builder<String>, annotationDescriptor: AnnotationDescriptor?) {
|
||||||
|
if (annotationDescriptor == null) return
|
||||||
|
|
||||||
|
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.toString().toLowerCase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getDiagnosticSuppressKey(diagnostic: Diagnostic): String {
|
||||||
|
return diagnostic.factory.name.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSuppressedByStrings(key: String, strings: Set<String>, 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<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, strings, severity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dominates(other: Suppressor): Boolean {
|
||||||
|
// it's too costly to check set inclusion
|
||||||
|
return other is EmptySuppressor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
<extensionPoint name="defaultErrorMessages"
|
<extensionPoint name="defaultErrorMessages"
|
||||||
interface="org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages$Extension"/>
|
interface="org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages$Extension"/>
|
||||||
<extensionPoint name="suppressStringProvider"
|
<extensionPoint name="suppressStringProvider"
|
||||||
interface="org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsWithSuppression$SuppressStringProvider"/>
|
interface="org.jetbrains.kotlin.resolve.diagnostics.SuppressStringProvider"/>
|
||||||
<extensionPoint name="diagnosticSuppressor"
|
<extensionPoint name="diagnosticSuppressor"
|
||||||
interface="org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsWithSuppression$DiagnosticSuppressor"/>
|
interface="org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor"/>
|
||||||
<extensionPoint name="externalDeclarationsProvider"
|
<extensionPoint name="externalDeclarationsProvider"
|
||||||
interface="org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider"
|
interface="org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider"
|
||||||
area="IDEA_PROJECT"/>
|
area="IDEA_PROJECT"/>
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
|||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsWithSuppression
|
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor
|
||||||
|
|
||||||
public class DiagnosticSuppressorForDebugger : DiagnosticsWithSuppression.DiagnosticSuppressor {
|
public class DiagnosticSuppressorForDebugger : DiagnosticSuppressor {
|
||||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||||
val element = diagnostic.getPsiElement()
|
val element = diagnostic.getPsiElement()
|
||||||
val containingFile = element.getContainingFile()
|
val containingFile = element.getContainingFile()
|
||||||
|
|||||||
@@ -16,20 +16,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.analyze
|
package org.jetbrains.kotlin.js.analyze
|
||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
|
||||||
import org.jetbrains.kotlin.js.PredefinedAnnotation.*
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Severity
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.js.config.LibrarySourcesConfig
|
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1
|
import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
|
import org.jetbrains.kotlin.js.PredefinedAnnotation.*
|
||||||
|
import org.jetbrains.kotlin.js.config.LibrarySourcesConfig
|
||||||
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsWithSuppression
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.SuppressDiagnosticsByAnnotations
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.FUNCTION_NO_BODY_ERRORS
|
import org.jetbrains.kotlin.resolve.diagnostics.FUNCTION_NO_BODY_ERRORS
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.PROPERTY_NOT_INITIALIZED_ERRORS
|
import org.jetbrains.kotlin.resolve.diagnostics.PROPERTY_NOT_INITIALIZED_ERRORS
|
||||||
|
import org.jetbrains.kotlin.resolve.diagnostics.SuppressDiagnosticsByAnnotations
|
||||||
|
|
||||||
private val NATIVE_ANNOTATIONS = arrayOf(NATIVE.fqName, NATIVE_INVOKE.fqName, NATIVE_GETTER.fqName, NATIVE_SETTER.fqName)
|
private val NATIVE_ANNOTATIONS = arrayOf(NATIVE.fqName, NATIVE_INVOKE.fqName, NATIVE_GETTER.fqName, NATIVE_SETTER.fqName)
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ public class SuppressUnusedParameterForJsNative : SuppressDiagnosticsByAnnotatio
|
|||||||
|
|
||||||
public class SuppressNoBodyErrorsForNativeDeclarations : SuppressDiagnosticsByAnnotations(FUNCTION_NO_BODY_ERRORS + PROPERTY_NOT_INITIALIZED_ERRORS, *NATIVE_ANNOTATIONS)
|
public class SuppressNoBodyErrorsForNativeDeclarations : SuppressDiagnosticsByAnnotations(FUNCTION_NO_BODY_ERRORS + PROPERTY_NOT_INITIALIZED_ERRORS, *NATIVE_ANNOTATIONS)
|
||||||
|
|
||||||
public class SuppressUninitializedErrorsForNativeDeclarations : DiagnosticsWithSuppression.DiagnosticSuppressor {
|
public class SuppressUninitializedErrorsForNativeDeclarations : DiagnosticSuppressor {
|
||||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||||
if (diagnostic.getFactory() != Errors.UNINITIALIZED_VARIABLE) return false
|
if (diagnostic.getFactory() != Errors.UNINITIALIZED_VARIABLE) return false
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ public class SuppressUninitializedErrorsForNativeDeclarations : DiagnosticsWithS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SuppressWarningsFromExternalModules : DiagnosticsWithSuppression.DiagnosticSuppressor {
|
public class SuppressWarningsFromExternalModules : DiagnosticSuppressor {
|
||||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||||
val file = diagnostic.getPsiFile()
|
val file = diagnostic.getPsiFile()
|
||||||
return diagnostic.getSeverity() == Severity.WARNING &&
|
return diagnostic.getSeverity() == Severity.WARNING &&
|
||||||
|
|||||||
Reference in New Issue
Block a user