Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.checkers.CheckerTestUtil Maven / Gradle / Ivy
/*
* Copyright 2010-2016 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.checkers;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import kotlin.Pair;
import kotlin.TuplesKt;
import kotlin.collections.CollectionsKt;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory;
import org.jetbrains.kotlin.diagnostics.Severity;
import org.jetbrains.kotlin.diagnostics.rendering.AbstractDiagnosticWithParametersRenderer;
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticRenderer;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtReferenceExpression;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.MultiTargetPlatform;
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CheckerTestUtil {
public static final Comparator DIAGNOSTIC_COMPARATOR = (o1, o2) -> {
List ranges1 = o1.diagnostic.getTextRanges();
List ranges2 = o2.diagnostic.getTextRanges();
int minNumberOfRanges = ranges1.size() < ranges2.size() ? ranges1.size() : ranges2.size();
for (int i = 0; i < minNumberOfRanges; i++) {
TextRange range1 = ranges1.get(i);
TextRange range2 = ranges2.get(i);
int startOffset1 = range1.getStartOffset();
int startOffset2 = range2.getStartOffset();
if (startOffset1 != startOffset2) {
// Start early -- go first
return startOffset1 - range2.getStartOffset();
}
int endOffset1 = range1.getEndOffset();
int endOffset2 = range2.getEndOffset();
if (endOffset1 != endOffset2) {
// start at the same offset, the one who end later is the outer, i.e. goes first
return endOffset2 - endOffset1;
}
}
return ranges1.size() - ranges2.size();
};
private static final String IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE";
private static final String SHOULD_BE_ESCAPED = "\\)\\(;";
private static final String DIAGNOSTIC_PARAMETER = "(?:(?:\\\\[" + SHOULD_BE_ESCAPED + "])|[^" + SHOULD_BE_ESCAPED + "])+";
private static final String INDIVIDUAL_DIAGNOSTIC = "(\\w+:)?(\\w+)(\\(" + DIAGNOSTIC_PARAMETER + "(;\\s*" + DIAGNOSTIC_PARAMETER + ")*\\))?";
private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("()|()");
private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile(INDIVIDUAL_DIAGNOSTIC);
private static final Pattern INDIVIDUAL_PARAMETER_PATTERN = Pattern.compile(DIAGNOSTIC_PARAMETER);
@NotNull
public static List getDiagnosticsIncludingSyntaxErrors(
@NotNull BindingContext bindingContext,
@NotNull List> implementingModulesBindings,
@NotNull PsiElement root,
boolean markDynamicCalls,
@Nullable List dynamicCallDescriptors
) {
List result =
getDiagnosticsIncludingSyntaxErrors(bindingContext, root, markDynamicCalls, dynamicCallDescriptors, null);
List> sortedBindings = CollectionsKt.sortedWith(
implementingModulesBindings,
(o1, o2) -> o1.getFirst().compareTo(o2.getFirst())
);
for (Pair binding : sortedBindings) {
MultiTargetPlatform platform = binding.getFirst();
assert platform instanceof MultiTargetPlatform.Specific : "Implementing module must have a specific platform: " + platform;
result.addAll(getDiagnosticsIncludingSyntaxErrors(
binding.getSecond(), root, markDynamicCalls, dynamicCallDescriptors,
((MultiTargetPlatform.Specific) platform).getPlatform()
));
}
return result;
}
@NotNull
public static List getDiagnosticsIncludingSyntaxErrors(
@NotNull BindingContext bindingContext,
@NotNull PsiElement root,
boolean markDynamicCalls,
@Nullable List dynamicCallDescriptors,
@Nullable String platform
) {
List diagnostics = new ArrayList<>();
for (Diagnostic diagnostic : bindingContext.getDiagnostics().all()) {
if (PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false)) {
diagnostics.add(new ActualDiagnostic(diagnostic, platform));
}
}
for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) {
diagnostics.add(new ActualDiagnostic(new SyntaxErrorDiagnostic(errorElement), platform));
}
diagnostics.addAll(getDebugInfoDiagnostics(root, bindingContext, markDynamicCalls, dynamicCallDescriptors, platform));
return diagnostics;
}
@SuppressWarnings("TestOnlyProblems")
@NotNull
private static List getDebugInfoDiagnostics(
@NotNull PsiElement root,
@NotNull BindingContext bindingContext,
boolean markDynamicCalls,
@Nullable List dynamicCallDescriptors,
@Nullable String platform
) {
List debugAnnotations = new ArrayList<>();
DebugInfoUtil.markDebugAnnotations(root, bindingContext, new DebugInfoUtil.DebugInfoReporter() {
@Override
public void reportElementWithErrorType(@NotNull KtReferenceExpression expression) {
newDiagnostic(expression, DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE);
}
@Override
public void reportMissingUnresolved(@NotNull KtReferenceExpression expression) {
newDiagnostic(expression, DebugInfoDiagnosticFactory.MISSING_UNRESOLVED);
}
@Override
public void reportUnresolvedWithTarget(@NotNull KtReferenceExpression expression, @NotNull String target) {
newDiagnostic(expression, DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET);
}
@Override
public void reportDynamicCall(@NotNull KtElement element, DeclarationDescriptor declarationDescriptor) {
if (dynamicCallDescriptors != null) {
dynamicCallDescriptors.add(declarationDescriptor);
}
if (markDynamicCalls) {
newDiagnostic(element, DebugInfoDiagnosticFactory.DYNAMIC);
}
}
private void newDiagnostic(KtElement element, DebugInfoDiagnosticFactory factory) {
debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(element, factory), platform));
}
});
// this code is used in tests and in internal action 'copy current file as diagnostic test'
//noinspection unchecked
for (Pair, DebugInfoDiagnosticFactory> factory : Arrays.asList(
TuplesKt.to(BindingContext.SMARTCAST, DebugInfoDiagnosticFactory.SMARTCAST),
TuplesKt.to(BindingContext.IMPLICIT_RECEIVER_SMARTCAST, DebugInfoDiagnosticFactory.IMPLICIT_RECEIVER_SMARTCAST),
TuplesKt.to(BindingContext.SMARTCAST_NULL, DebugInfoDiagnosticFactory.CONSTANT),
TuplesKt.to(BindingContext.LEAKING_THIS, DebugInfoDiagnosticFactory.LEAKING_THIS),
TuplesKt.to(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, DebugInfoDiagnosticFactory.IMPLICIT_EXHAUSTIVE)
)) {
for (KtExpression expression : bindingContext.getSliceContents(factory.getFirst()).keySet()) {
if (PsiTreeUtil.isAncestor(root, expression, false)) {
debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(expression, factory.getSecond()), platform));
}
}
}
return debugAnnotations;
}
public interface DiagnosticDiffCallbacks {
void missingDiagnostic(TextDiagnostic diagnostic, int expectedStart, int expectedEnd);
void wrongParametersDiagnostic(TextDiagnostic expectedDiagnostic, TextDiagnostic actualDiagnostic, int start, int end);
void unexpectedDiagnostic(TextDiagnostic diagnostic, int actualStart, int actualEnd);
}
public static Map diagnosticsDiff(
List expected,
Collection actual,
DiagnosticDiffCallbacks callbacks
) {
Map diagnosticToExpectedDiagnostic = new HashMap<>();
assertSameFile(actual);
Iterator expectedDiagnostics = expected.iterator();
List sortedDiagnosticDescriptors = getSortedDiagnosticDescriptors(actual);
Iterator actualDiagnostics = sortedDiagnosticDescriptors.iterator();
DiagnosedRange currentExpected = safeAdvance(expectedDiagnostics);
DiagnosticDescriptor currentActual = safeAdvance(actualDiagnostics);
while (currentExpected != null || currentActual != null) {
if (currentExpected != null) {
if (currentActual == null) {
missingDiagnostics(callbacks, currentExpected);
currentExpected = safeAdvance(expectedDiagnostics);
}
else {
int expectedStart = currentExpected.getStart();
int actualStart = currentActual.getStart();
int expectedEnd = currentExpected.getEnd();
int actualEnd = currentActual.getEnd();
if (expectedStart < actualStart) {
missingDiagnostics(callbacks, currentExpected);
currentExpected = safeAdvance(expectedDiagnostics);
}
else if (expectedStart > actualStart) {
unexpectedDiagnostics(currentActual, callbacks);
currentActual = safeAdvance(actualDiagnostics);
}
else if (expectedEnd > actualEnd) {
assert expectedStart == actualStart;
missingDiagnostics(callbacks, currentExpected);
currentExpected = safeAdvance(expectedDiagnostics);
}
else if (expectedEnd < actualEnd) {
assert expectedStart == actualStart;
unexpectedDiagnostics(currentActual, callbacks);
currentActual = safeAdvance(actualDiagnostics);
}
else {
compareDiagnostics(callbacks, currentExpected, currentActual, diagnosticToExpectedDiagnostic);
currentExpected = safeAdvance(expectedDiagnostics);
currentActual = safeAdvance(actualDiagnostics);
}
}
}
else {
//noinspection ConstantConditions
assert (currentActual != null);
unexpectedDiagnostics(currentActual, callbacks);
currentActual = safeAdvance(actualDiagnostics);
}
}
return diagnosticToExpectedDiagnostic;
}
private static void compareDiagnostics(
@NotNull DiagnosticDiffCallbacks callbacks,
@NotNull DiagnosedRange currentExpected,
@NotNull DiagnosticDescriptor currentActual,
@NotNull Map diagnosticToInput
) {
int expectedStart = currentExpected.getStart();
int expectedEnd = currentExpected.getEnd();
int actualStart = currentActual.getStart();
int actualEnd = currentActual.getEnd();
assert expectedStart == actualStart && expectedEnd == actualEnd;
Map actualDiagnostics = currentActual.getTextDiagnosticsMap();
List expectedDiagnostics = currentExpected.getDiagnostics();
for (TextDiagnostic expectedDiagnostic : expectedDiagnostics) {
Map.Entry actualDiagnosticEntry = CollectionsKt.firstOrNull(
actualDiagnostics.entrySet(), entry -> expectedDiagnostic.getDescription().equals(entry.getValue().getDescription())
);
if (actualDiagnosticEntry != null) {
ActualDiagnostic actualDiagnostic = actualDiagnosticEntry.getKey();
TextDiagnostic actualTextDiagnostic = actualDiagnosticEntry.getValue();
if (!compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) {
callbacks.wrongParametersDiagnostic(expectedDiagnostic, actualTextDiagnostic, expectedStart, expectedEnd);
}
actualDiagnostics.remove(actualDiagnostic);
diagnosticToInput.put(actualDiagnostic, expectedDiagnostic);
}
else {
callbacks.missingDiagnostic(expectedDiagnostic, expectedStart, expectedEnd);
}
}
for (TextDiagnostic unexpectedDiagnostic : actualDiagnostics.values()) {
callbacks.unexpectedDiagnostic(unexpectedDiagnostic, actualStart, actualEnd);
}
}
private static boolean compareTextDiagnostic(@NotNull TextDiagnostic expected, @NotNull TextDiagnostic actual) {
if (!expected.getDescription().equals(actual.getDescription())) return false;
if (expected.getParameters() == null) return true;
if (actual.getParameters() == null || expected.getParameters().size() != actual.getParameters().size()) return false;
for (int index = 0; index < expected.getParameters().size(); index++) {
String expectedParameter = expected.getParameters().get(index);
String actualParameter = actual.getParameters().get(index);
if (!expectedParameter.equals(IGNORE_DIAGNOSTIC_PARAMETER) && !expectedParameter.equals(actualParameter)) {
return false;
}
}
return true;
}
private static void assertSameFile(Collection actual) {
if (actual.isEmpty()) return;
PsiFile file = CollectionsKt.first(actual).getFile();
for (ActualDiagnostic actualDiagnostic : actual) {
assert actualDiagnostic.getFile().equals(file)
: "All diagnostics should come from the same file: " + actualDiagnostic.getFile() + ", " + file;
}
}
private static void unexpectedDiagnostics(DiagnosticDescriptor descriptor, DiagnosticDiffCallbacks callbacks) {
for (ActualDiagnostic diagnostic : descriptor.diagnostics) {
callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), descriptor.start, descriptor.end);
}
}
private static void missingDiagnostics(DiagnosticDiffCallbacks callbacks, DiagnosedRange currentExpected) {
for (TextDiagnostic diagnostic : currentExpected.getDiagnostics()) {
callbacks.missingDiagnostic(diagnostic, currentExpected.getStart(), currentExpected.getEnd());
}
}
private static T safeAdvance(Iterator iterator) {
return iterator.hasNext() ? iterator.next() : null;
}
public static String parseDiagnosedRanges(String text, List result) {
Matcher matcher = RANGE_START_OR_END_PATTERN.matcher(text);
Stack opened = new Stack<>();
int offsetCompensation = 0;
while (matcher.find()) {
int effectiveOffset = matcher.start() - offsetCompensation;
String matchedText = matcher.group();
if ("".equals(matchedText)) {
opened.pop().setEnd(effectiveOffset);
}
else {
Matcher diagnosticTypeMatcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(matchedText);
DiagnosedRange range = new DiagnosedRange(effectiveOffset);
while (diagnosticTypeMatcher.find()) {
range.addDiagnostic(diagnosticTypeMatcher.group());
}
opened.push(range);
result.add(range);
}
offsetCompensation += matchedText.length();
}
assert opened.isEmpty() : "Stack is not empty";
matcher.reset();
return matcher.replaceAll("");
}
public static StringBuffer addDiagnosticMarkersToText(@NotNull PsiFile psiFile, @NotNull Collection diagnostics) {
return addDiagnosticMarkersToText(psiFile, diagnostics, Collections.emptyMap(), PsiElement::getText);
}
public static StringBuffer addDiagnosticMarkersToText(
@NotNull PsiFile psiFile,
@NotNull Collection diagnostics,
@NotNull Map diagnosticToExpectedDiagnostic,
@NotNull Function getFileText
) {
String text = getFileText.fun(psiFile);
StringBuffer result = new StringBuffer();
diagnostics = CollectionsKt.filter(diagnostics, actualDiagnostic -> psiFile.equals(actualDiagnostic.getFile()));
if (!diagnostics.isEmpty()) {
List diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics);
Stack opened = new Stack<>();
ListIterator iterator = diagnosticDescriptors.listIterator();
DiagnosticDescriptor currentDescriptor = iterator.next();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
while (!opened.isEmpty() && i == opened.peek().end) {
closeDiagnosticString(result);
opened.pop();
}
while (currentDescriptor != null && i == currentDescriptor.start) {
openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic);
if (currentDescriptor.getEnd() == i) {
closeDiagnosticString(result);
}
else {
opened.push(currentDescriptor);
}
if (iterator.hasNext()) {
currentDescriptor = iterator.next();
}
else {
currentDescriptor = null;
}
}
result.append(c);
}
if (currentDescriptor != null) {
assert currentDescriptor.start == text.length();
assert currentDescriptor.end == text.length();
openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic);
opened.push(currentDescriptor);
}
while (!opened.isEmpty() && text.length() == opened.peek().end) {
closeDiagnosticString(result);
opened.pop();
}
assert opened.isEmpty() : "Stack is not empty: " + opened;
}
else {
result.append(text);
}
return result;
}
private static void openDiagnosticsString(
StringBuffer result,
DiagnosticDescriptor currentDescriptor,
Map diagnosticToExpectedDiagnostic
) {
result.append(" iterator = currentDescriptor.diagnostics.iterator(); iterator.hasNext(); ) {
ActualDiagnostic diagnostic = iterator.next();
TextDiagnostic expectedDiagnostic = diagnosticToExpectedDiagnostic.get(diagnostic);
if (expectedDiagnostic != null) {
TextDiagnostic actualTextDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic);
if (compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) {
result.append(expectedDiagnostic.asString());
}
else {
result.append(actualTextDiagnostic.asString());
}
}
else {
if (diagnostic.platform != null) {
result.append(diagnostic.platform);
result.append(":");
}
result.append(diagnostic.getName());
}
if (iterator.hasNext()) {
result.append(", ");
}
}
result.append("!>");
}
private static void closeDiagnosticString(StringBuffer result) {
result.append("");
}
public static class AbstractDiagnosticForTests implements Diagnostic {
private final PsiElement element;
private final DiagnosticFactory factory;
public AbstractDiagnosticForTests(@NotNull PsiElement element, @NotNull DiagnosticFactory factory) {
this.element = element;
this.factory = factory;
}
@NotNull
@Override
public DiagnosticFactory getFactory() {
return factory;
}
@NotNull
@Override
public Severity getSeverity() {
return Severity.ERROR;
}
@NotNull
@Override
public PsiElement getPsiElement() {
return element;
}
@NotNull
@Override
public List getTextRanges() {
return Collections.singletonList(element.getTextRange());
}
@NotNull
@Override
public PsiFile getPsiFile() {
return element.getContainingFile();
}
@Override
public boolean isValid() {
return true;
}
}
public static class SyntaxErrorDiagnosticFactory extends DiagnosticFactory {
public static final SyntaxErrorDiagnosticFactory INSTANCE = new SyntaxErrorDiagnosticFactory();
private SyntaxErrorDiagnosticFactory() {
super(Severity.ERROR);
}
@NotNull
@Override
public String getName() {
return "SYNTAX";
}
}
public static class SyntaxErrorDiagnostic extends AbstractDiagnosticForTests {
public SyntaxErrorDiagnostic(@NotNull PsiErrorElement errorElement) {
super(errorElement, SyntaxErrorDiagnosticFactory.INSTANCE);
}
}
public static class DebugInfoDiagnosticFactory extends DiagnosticFactory {
public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST");
public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST");
public static final DebugInfoDiagnosticFactory CONSTANT = new DebugInfoDiagnosticFactory("CONSTANT");
public static final DebugInfoDiagnosticFactory LEAKING_THIS = new DebugInfoDiagnosticFactory("LEAKING_THIS");
public static final DebugInfoDiagnosticFactory IMPLICIT_EXHAUSTIVE = new DebugInfoDiagnosticFactory("IMPLICIT_EXHAUSTIVE");
public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE");
public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET");
public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED");
public static final DebugInfoDiagnosticFactory DYNAMIC = new DebugInfoDiagnosticFactory("DYNAMIC");
private final String name;
private DebugInfoDiagnosticFactory(String name, Severity severity) {
super(severity);
this.name = name;
}
private DebugInfoDiagnosticFactory(String name) {
this(name, Severity.ERROR);
}
@NotNull
@Override
public String getName() {
return "DEBUG_INFO_" + name;
}
}
public static class DebugInfoDiagnostic extends AbstractDiagnosticForTests {
public DebugInfoDiagnostic(@NotNull KtElement element, @NotNull DebugInfoDiagnosticFactory factory) {
super(element, factory);
}
}
@NotNull
private static List getSortedDiagnosticDescriptors(@NotNull Collection diagnostics) {
LinkedListMultimap diagnosticsGroupedByRanges = LinkedListMultimap.create();
for (ActualDiagnostic actualDiagnostic : diagnostics) {
Diagnostic diagnostic = actualDiagnostic.diagnostic;
if (!diagnostic.isValid()) continue;
for (TextRange textRange : diagnostic.getTextRanges()) {
diagnosticsGroupedByRanges.put(textRange, actualDiagnostic);
}
}
List diagnosticDescriptors = Lists.newArrayList();
for (TextRange range : diagnosticsGroupedByRanges.keySet()) {
diagnosticDescriptors.add(
new DiagnosticDescriptor(range.getStartOffset(), range.getEndOffset(), diagnosticsGroupedByRanges.get(range)));
}
diagnosticDescriptors.sort((d1, d2) -> {
// Start early -- go first; start at the same offset, the one who end later is the outer, i.e. goes first
return (d1.start != d2.start) ? d1.start - d2.start : d2.end - d1.end;
});
return diagnosticDescriptors;
}
private static class DiagnosticDescriptor {
private final int start;
private final int end;
private final List diagnostics;
DiagnosticDescriptor(int start, int end, List diagnostics) {
this.start = start;
this.end = end;
this.diagnostics = diagnostics;
}
public Map getTextDiagnosticsMap() {
Map diagnosticMap = new HashMap<>();
for (ActualDiagnostic diagnostic : diagnostics) {
diagnosticMap.put(diagnostic, TextDiagnostic.asTextDiagnostic(diagnostic));
}
return diagnosticMap;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public TextRange getTextRange() {
return new TextRange(start, end);
}
}
public static class ActualDiagnostic {
public final Diagnostic diagnostic;
public final String platform;
ActualDiagnostic(@NotNull Diagnostic diagnostic, @Nullable String platform) {
this.diagnostic = diagnostic;
this.platform = platform;
}
@NotNull
public String getName() {
return diagnostic.getFactory().getName();
}
@NotNull
public PsiFile getFile() {
return diagnostic.getPsiFile();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ActualDiagnostic)) return false;
ActualDiagnostic other = (ActualDiagnostic) obj;
// '==' on diagnostics is intentional here
return other.diagnostic == diagnostic &&
(other.platform == null ? platform == null : other.platform.equals(platform));
}
@Override
public int hashCode() {
return System.identityHashCode(diagnostic) * 31 + (platform != null ? platform.hashCode() : 0);
}
@Override
public String toString() {
return (platform != null ? platform + ":" : "") + diagnostic.toString();
}
}
public static class TextDiagnostic {
@NotNull
private static TextDiagnostic parseDiagnostic(String text) {
Matcher matcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(text);
if (!matcher.find())
throw new IllegalArgumentException("Could not parse diagnostic: " + text);
String platformPrefix = matcher.group(1);
assert platformPrefix == null || platformPrefix.endsWith(":") : platformPrefix;
String platform = platformPrefix == null ? null : StringsKt.substringBeforeLast(platformPrefix, ":", platformPrefix);
String name = matcher.group(2);
String parameters = matcher.group(3);
if (parameters == null) {
return new TextDiagnostic(name, platform, null);
}
List parsedParameters = new SmartList<>();
Matcher parametersMatcher = INDIVIDUAL_PARAMETER_PATTERN.matcher(parameters);
while (parametersMatcher.find())
parsedParameters.add(unescape(parametersMatcher.group().trim()));
return new TextDiagnostic(name, platform, parsedParameters);
}
private static @NotNull String escape(@NotNull String s) {
return s.replaceAll("([" + SHOULD_BE_ESCAPED + "])", "\\\\$1");
}
private static @NotNull String unescape(@NotNull String s) {
return s.replaceAll("\\\\([" + SHOULD_BE_ESCAPED + "])", "$1");
}
@NotNull
public static TextDiagnostic asTextDiagnostic(@NotNull ActualDiagnostic actualDiagnostic) {
Diagnostic diagnostic = actualDiagnostic.diagnostic;
//noinspection TestOnlyProblems
DiagnosticRenderer renderer = DefaultErrorMessages.getRendererForDiagnostic(diagnostic);
String diagnosticName = actualDiagnostic.getName();
if (renderer instanceof AbstractDiagnosticWithParametersRenderer) {
//noinspection unchecked
Object[] renderParameters = ((AbstractDiagnosticWithParametersRenderer) renderer).renderParameters(diagnostic);
List parameters = ContainerUtil.map(renderParameters, Object::toString);
return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, parameters);
}
return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, null);
}
@NotNull
private final String name;
@Nullable
private final String platform;
@Nullable
private final List parameters;
public TextDiagnostic(@NotNull String name, @Nullable String platform, @Nullable List parameters) {
this.name = name;
this.platform = platform;
this.parameters = parameters;
}
@Nullable
public String getPlatform() {
return platform;
}
@NotNull
public String getDescription() {
return (platform != null ? platform + ":" : "") + name;
}
@Nullable
public List getParameters() {
return parameters;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextDiagnostic that = (TextDiagnostic) o;
if (!name.equals(that.name)) return false;
if (platform != null ? !platform.equals(that.platform) : that.platform != null) return false;
if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (platform != null ? platform.hashCode() : 0);
result = 31 * result + (parameters != null ? parameters.hashCode() : 0);
return result;
}
@NotNull
public String asString() {
StringBuilder result = new StringBuilder();
if (platform != null) {
result.append(platform);
result.append(":");
}
result.append(name);
if (parameters != null) {
result.append("(");
result.append(StringUtil.join(parameters, TextDiagnostic::escape, "; "));
result.append(")");
}
return result.toString();
}
@Override
public String toString() {
return asString();
}
}
public static class DiagnosedRange {
private final int start;
private int end;
private final List diagnostics = ContainerUtil.newSmartList();
protected DiagnosedRange(int start) {
this.start = start;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public List getDiagnostics() {
return diagnostics;
}
public void setEnd(int end) {
this.end = end;
}
public void addDiagnostic(String diagnostic) {
diagnostics.add(TextDiagnostic.parseDiagnostic(diagnostic));
}
}
}