001 /*
002 * Copyright 2010-2016 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.checkers;
018
019 import com.google.common.base.Predicate;
020 import com.google.common.collect.Collections2;
021 import com.google.common.collect.LinkedListMultimap;
022 import com.google.common.collect.Lists;
023 import com.intellij.openapi.util.TextRange;
024 import com.intellij.openapi.util.text.StringUtil;
025 import com.intellij.psi.PsiElement;
026 import com.intellij.psi.PsiErrorElement;
027 import com.intellij.psi.PsiFile;
028 import com.intellij.psi.util.PsiTreeUtil;
029 import com.intellij.util.Function;
030 import com.intellij.util.SmartList;
031 import com.intellij.util.containers.ContainerUtil;
032 import com.intellij.util.containers.Stack;
033 import kotlin.Pair;
034 import kotlin.TuplesKt;
035 import kotlin.collections.CollectionsKt;
036 import kotlin.jvm.functions.Function1;
037 import kotlin.text.StringsKt;
038 import org.jetbrains.annotations.NotNull;
039 import org.jetbrains.annotations.Nullable;
040 import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
041 import org.jetbrains.kotlin.diagnostics.Diagnostic;
042 import org.jetbrains.kotlin.diagnostics.DiagnosticFactory;
043 import org.jetbrains.kotlin.diagnostics.Severity;
044 import org.jetbrains.kotlin.diagnostics.rendering.AbstractDiagnosticWithParametersRenderer;
045 import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
046 import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticRenderer;
047 import org.jetbrains.kotlin.psi.KtElement;
048 import org.jetbrains.kotlin.psi.KtExpression;
049 import org.jetbrains.kotlin.psi.KtReferenceExpression;
050 import org.jetbrains.kotlin.resolve.AnalyzingUtils;
051 import org.jetbrains.kotlin.resolve.BindingContext;
052 import org.jetbrains.kotlin.resolve.MultiTargetPlatform;
053 import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
054
055 import java.util.*;
056 import java.util.regex.Matcher;
057 import java.util.regex.Pattern;
058
059 public class CheckerTestUtil {
060 public static final Comparator<ActualDiagnostic> DIAGNOSTIC_COMPARATOR = new Comparator<ActualDiagnostic>() {
061 @Override
062 public int compare(@NotNull ActualDiagnostic o1, @NotNull ActualDiagnostic o2) {
063 List<TextRange> ranges1 = o1.diagnostic.getTextRanges();
064 List<TextRange> ranges2 = o2.diagnostic.getTextRanges();
065 int minNumberOfRanges = ranges1.size() < ranges2.size() ? ranges1.size() : ranges2.size();
066 for (int i = 0; i < minNumberOfRanges; i++) {
067 TextRange range1 = ranges1.get(i);
068 TextRange range2 = ranges2.get(i);
069 int startOffset1 = range1.getStartOffset();
070 int startOffset2 = range2.getStartOffset();
071 if (startOffset1 != startOffset2) {
072 // Start early -- go first
073 return startOffset1 - range2.getStartOffset();
074 }
075 int endOffset1 = range1.getEndOffset();
076 int endOffset2 = range2.getEndOffset();
077 if (endOffset1 != endOffset2) {
078 // start at the same offset, the one who end later is the outer, i.e. goes first
079 return endOffset2 - endOffset1;
080 }
081 }
082 return ranges1.size() - ranges2.size();
083 }
084 };
085
086 private static final String IGNORE_DIAGNOSTIC_PARAMETER = "IGNORE";
087 private static final String SHOULD_BE_ESCAPED = "\\)\\(;";
088 private static final String DIAGNOSTIC_PARAMETER = "(?:(?:\\\\[" + SHOULD_BE_ESCAPED + "])|[^" + SHOULD_BE_ESCAPED + "])+";
089 private static final String INDIVIDUAL_DIAGNOSTIC = "(\\w+:)?(\\w+)(\\(" + DIAGNOSTIC_PARAMETER + "(;\\s*" + DIAGNOSTIC_PARAMETER + ")*\\))?";
090 private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("(<!" +
091 INDIVIDUAL_DIAGNOSTIC + "(,\\s*" +
092 INDIVIDUAL_DIAGNOSTIC + ")*!>)|(<!>)");
093 private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile(INDIVIDUAL_DIAGNOSTIC);
094 private static final Pattern INDIVIDUAL_PARAMETER_PATTERN = Pattern.compile(DIAGNOSTIC_PARAMETER);
095
096 @NotNull
097 public static List<ActualDiagnostic> getDiagnosticsIncludingSyntaxErrors(
098 @NotNull BindingContext bindingContext,
099 @NotNull List<Pair<MultiTargetPlatform, BindingContext>> implementingModulesBindings,
100 @NotNull PsiElement root,
101 boolean markDynamicCalls,
102 @Nullable List<DeclarationDescriptor> dynamicCallDescriptors
103 ) {
104 List<ActualDiagnostic> result =
105 getDiagnosticsIncludingSyntaxErrors(bindingContext, root, markDynamicCalls, dynamicCallDescriptors, null);
106
107 List<Pair<MultiTargetPlatform, BindingContext>> sortedBindings = CollectionsKt.sortedWith(
108 implementingModulesBindings,
109 new Comparator<Pair<MultiTargetPlatform, BindingContext>>() {
110 @Override
111 public int compare(Pair<MultiTargetPlatform, BindingContext> o1, Pair<MultiTargetPlatform, BindingContext> o2) {
112 return o1.getFirst().compareTo(o2.getFirst());
113 }
114 }
115 );
116
117 for (Pair<MultiTargetPlatform, BindingContext> binding : sortedBindings) {
118 MultiTargetPlatform platform = binding.getFirst();
119 assert platform instanceof MultiTargetPlatform.Specific : "Implementing module must have a specific platform: " + platform;
120
121 result.addAll(getDiagnosticsIncludingSyntaxErrors(
122 binding.getSecond(), root, markDynamicCalls, dynamicCallDescriptors,
123 ((MultiTargetPlatform.Specific) platform).getPlatform()
124 ));
125 }
126
127 return result;
128 }
129
130 @NotNull
131 public static List<ActualDiagnostic> getDiagnosticsIncludingSyntaxErrors(
132 @NotNull BindingContext bindingContext,
133 @NotNull PsiElement root,
134 boolean markDynamicCalls,
135 @Nullable List<DeclarationDescriptor> dynamicCallDescriptors,
136 @Nullable String platform
137 ) {
138 List<ActualDiagnostic> diagnostics = new ArrayList<ActualDiagnostic>();
139 for (Diagnostic diagnostic : bindingContext.getDiagnostics().all()) {
140 if (PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false)) {
141 diagnostics.add(new ActualDiagnostic(diagnostic, platform));
142 }
143 }
144
145 for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) {
146 diagnostics.add(new ActualDiagnostic(new SyntaxErrorDiagnostic(errorElement), platform));
147 }
148
149 diagnostics.addAll(getDebugInfoDiagnostics(root, bindingContext, markDynamicCalls, dynamicCallDescriptors, platform));
150 return diagnostics;
151 }
152
153 @SuppressWarnings("TestOnlyProblems")
154 @NotNull
155 private static List<ActualDiagnostic> getDebugInfoDiagnostics(
156 @NotNull PsiElement root,
157 @NotNull BindingContext bindingContext,
158 final boolean markDynamicCalls,
159 @Nullable final List<DeclarationDescriptor> dynamicCallDescriptors,
160 @Nullable final String platform
161 ) {
162 final List<ActualDiagnostic> debugAnnotations = new ArrayList<ActualDiagnostic>();
163
164 DebugInfoUtil.markDebugAnnotations(root, bindingContext, new DebugInfoUtil.DebugInfoReporter() {
165 @Override
166 public void reportElementWithErrorType(@NotNull KtReferenceExpression expression) {
167 newDiagnostic(expression, DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE);
168 }
169
170 @Override
171 public void reportMissingUnresolved(@NotNull KtReferenceExpression expression) {
172 newDiagnostic(expression, DebugInfoDiagnosticFactory.MISSING_UNRESOLVED);
173 }
174
175 @Override
176 public void reportUnresolvedWithTarget(@NotNull KtReferenceExpression expression, @NotNull String target) {
177 newDiagnostic(expression, DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET);
178 }
179
180 @Override
181 public void reportDynamicCall(@NotNull KtElement element, DeclarationDescriptor declarationDescriptor) {
182 if (dynamicCallDescriptors != null) {
183 dynamicCallDescriptors.add(declarationDescriptor);
184 }
185
186 if (markDynamicCalls) {
187 newDiagnostic(element, DebugInfoDiagnosticFactory.DYNAMIC);
188 }
189 }
190
191 private void newDiagnostic(KtElement element, DebugInfoDiagnosticFactory factory) {
192 debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(element, factory), platform));
193 }
194 });
195
196 // this code is used in tests and in internal action 'copy current file as diagnostic test'
197 //noinspection unchecked
198 for (Pair<? extends WritableSlice<? extends KtExpression, ?>, DebugInfoDiagnosticFactory> factory : Arrays.asList(
199 TuplesKt.to(BindingContext.SMARTCAST, DebugInfoDiagnosticFactory.SMARTCAST),
200 TuplesKt.to(BindingContext.IMPLICIT_RECEIVER_SMARTCAST, DebugInfoDiagnosticFactory.IMPLICIT_RECEIVER_SMARTCAST),
201 TuplesKt.to(BindingContext.SMARTCAST_NULL, DebugInfoDiagnosticFactory.CONSTANT),
202 TuplesKt.to(BindingContext.LEAKING_THIS, DebugInfoDiagnosticFactory.LEAKING_THIS),
203 TuplesKt.to(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, DebugInfoDiagnosticFactory.IMPLICIT_EXHAUSTIVE)
204 )) {
205 for (KtExpression expression : bindingContext.getSliceContents(factory.getFirst()).keySet()) {
206 if (PsiTreeUtil.isAncestor(root, expression, false)) {
207 debugAnnotations.add(new ActualDiagnostic(new DebugInfoDiagnostic(expression, factory.getSecond()), platform));
208 }
209 }
210 }
211
212 return debugAnnotations;
213 }
214
215 public interface DiagnosticDiffCallbacks {
216 void missingDiagnostic(TextDiagnostic diagnostic, int expectedStart, int expectedEnd);
217
218 void wrongParametersDiagnostic(TextDiagnostic expectedDiagnostic, TextDiagnostic actualDiagnostic, int start, int end);
219
220 void unexpectedDiagnostic(TextDiagnostic diagnostic, int actualStart, int actualEnd);
221 }
222
223 public static Map<ActualDiagnostic, TextDiagnostic> diagnosticsDiff(
224 List<DiagnosedRange> expected,
225 Collection<ActualDiagnostic> actual,
226 DiagnosticDiffCallbacks callbacks
227 ) {
228 Map<ActualDiagnostic, TextDiagnostic> diagnosticToExpectedDiagnostic = new HashMap<ActualDiagnostic, TextDiagnostic>();
229
230 assertSameFile(actual);
231
232 Iterator<DiagnosedRange> expectedDiagnostics = expected.iterator();
233 List<DiagnosticDescriptor> sortedDiagnosticDescriptors = getSortedDiagnosticDescriptors(actual);
234 Iterator<DiagnosticDescriptor> actualDiagnostics = sortedDiagnosticDescriptors.iterator();
235
236 DiagnosedRange currentExpected = safeAdvance(expectedDiagnostics);
237 DiagnosticDescriptor currentActual = safeAdvance(actualDiagnostics);
238 while (currentExpected != null || currentActual != null) {
239 if (currentExpected != null) {
240 if (currentActual == null) {
241 missingDiagnostics(callbacks, currentExpected);
242 currentExpected = safeAdvance(expectedDiagnostics);
243 }
244 else {
245 int expectedStart = currentExpected.getStart();
246 int actualStart = currentActual.getStart();
247 int expectedEnd = currentExpected.getEnd();
248 int actualEnd = currentActual.getEnd();
249 if (expectedStart < actualStart) {
250 missingDiagnostics(callbacks, currentExpected);
251 currentExpected = safeAdvance(expectedDiagnostics);
252 }
253 else if (expectedStart > actualStart) {
254 unexpectedDiagnostics(currentActual, callbacks);
255 currentActual = safeAdvance(actualDiagnostics);
256 }
257 else if (expectedEnd > actualEnd) {
258 assert expectedStart == actualStart;
259 missingDiagnostics(callbacks, currentExpected);
260 currentExpected = safeAdvance(expectedDiagnostics);
261 }
262 else if (expectedEnd < actualEnd) {
263 assert expectedStart == actualStart;
264 unexpectedDiagnostics(currentActual, callbacks);
265 currentActual = safeAdvance(actualDiagnostics);
266 }
267 else {
268 compareDiagnostics(callbacks, currentExpected, currentActual, diagnosticToExpectedDiagnostic);
269 currentExpected = safeAdvance(expectedDiagnostics);
270 currentActual = safeAdvance(actualDiagnostics);
271 }
272 }
273 }
274 else {
275 //noinspection ConstantConditions
276 assert (currentActual != null);
277
278 unexpectedDiagnostics(currentActual, callbacks);
279 currentActual = safeAdvance(actualDiagnostics);
280 }
281 }
282
283 return diagnosticToExpectedDiagnostic;
284 }
285
286 private static void compareDiagnostics(
287 @NotNull DiagnosticDiffCallbacks callbacks,
288 @NotNull DiagnosedRange currentExpected,
289 @NotNull DiagnosticDescriptor currentActual,
290 @NotNull Map<ActualDiagnostic, TextDiagnostic> diagnosticToInput
291 ) {
292 int expectedStart = currentExpected.getStart();
293 int expectedEnd = currentExpected.getEnd();
294
295 int actualStart = currentActual.getStart();
296 int actualEnd = currentActual.getEnd();
297 assert expectedStart == actualStart && expectedEnd == actualEnd;
298
299 Map<ActualDiagnostic, TextDiagnostic> actualDiagnostics = currentActual.getTextDiagnosticsMap();
300 List<TextDiagnostic> expectedDiagnostics = currentExpected.getDiagnostics();
301
302 for (final TextDiagnostic expectedDiagnostic : expectedDiagnostics) {
303 Map.Entry<ActualDiagnostic, TextDiagnostic> actualDiagnosticEntry = CollectionsKt.firstOrNull(
304 actualDiagnostics.entrySet(), new Function1<Map.Entry<ActualDiagnostic, TextDiagnostic>, Boolean>() {
305 @Override
306 public Boolean invoke(Map.Entry<ActualDiagnostic, TextDiagnostic> entry) {
307 return expectedDiagnostic.getDescription().equals(entry.getValue().getDescription());
308 }
309 }
310 );
311
312 if (actualDiagnosticEntry != null) {
313 ActualDiagnostic actualDiagnostic = actualDiagnosticEntry.getKey();
314 TextDiagnostic actualTextDiagnostic = actualDiagnosticEntry.getValue();
315
316 if (!compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) {
317 callbacks.wrongParametersDiagnostic(expectedDiagnostic, actualTextDiagnostic, expectedStart, expectedEnd);
318 }
319
320 actualDiagnostics.remove(actualDiagnostic);
321 diagnosticToInput.put(actualDiagnostic, expectedDiagnostic);
322 }
323 else {
324 callbacks.missingDiagnostic(expectedDiagnostic, expectedStart, expectedEnd);
325 }
326 }
327
328 for (TextDiagnostic unexpectedDiagnostic : actualDiagnostics.values()) {
329 callbacks.unexpectedDiagnostic(unexpectedDiagnostic, actualStart, actualEnd);
330 }
331 }
332
333 private static boolean compareTextDiagnostic(@NotNull TextDiagnostic expected, @NotNull TextDiagnostic actual) {
334 if (!expected.getDescription().equals(actual.getDescription())) return false;
335
336 if (expected.getParameters() == null) return true;
337 if (actual.getParameters() == null || expected.getParameters().size() != actual.getParameters().size()) return false;
338
339 for (int index = 0; index < expected.getParameters().size(); index++) {
340 String expectedParameter = expected.getParameters().get(index);
341 String actualParameter = actual.getParameters().get(index);
342 if (!expectedParameter.equals(IGNORE_DIAGNOSTIC_PARAMETER) && !expectedParameter.equals(actualParameter)) {
343 return false;
344 }
345 }
346 return true;
347 }
348
349 private static void assertSameFile(Collection<ActualDiagnostic> actual) {
350 if (actual.isEmpty()) return;
351 PsiFile file = CollectionsKt.first(actual).getFile();
352 for (ActualDiagnostic actualDiagnostic : actual) {
353 assert actualDiagnostic.getFile().equals(file)
354 : "All diagnostics should come from the same file: " + actualDiagnostic.getFile() + ", " + file;
355 }
356 }
357
358 private static void unexpectedDiagnostics(DiagnosticDescriptor descriptor, DiagnosticDiffCallbacks callbacks) {
359 for (ActualDiagnostic diagnostic : descriptor.diagnostics) {
360 callbacks.unexpectedDiagnostic(TextDiagnostic.asTextDiagnostic(diagnostic), descriptor.start, descriptor.end);
361 }
362 }
363
364 private static void missingDiagnostics(DiagnosticDiffCallbacks callbacks, DiagnosedRange currentExpected) {
365 for (TextDiagnostic diagnostic : currentExpected.getDiagnostics()) {
366 callbacks.missingDiagnostic(diagnostic, currentExpected.getStart(), currentExpected.getEnd());
367 }
368 }
369
370 private static <T> T safeAdvance(Iterator<T> iterator) {
371 return iterator.hasNext() ? iterator.next() : null;
372 }
373
374 public static String parseDiagnosedRanges(String text, List<DiagnosedRange> result) {
375 Matcher matcher = RANGE_START_OR_END_PATTERN.matcher(text);
376
377 Stack<DiagnosedRange> opened = new Stack<DiagnosedRange>();
378
379 int offsetCompensation = 0;
380
381 while (matcher.find()) {
382 int effectiveOffset = matcher.start() - offsetCompensation;
383 String matchedText = matcher.group();
384 if ("<!>".equals(matchedText)) {
385 opened.pop().setEnd(effectiveOffset);
386 }
387 else {
388 Matcher diagnosticTypeMatcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(matchedText);
389 DiagnosedRange range = new DiagnosedRange(effectiveOffset);
390 while (diagnosticTypeMatcher.find()) {
391 range.addDiagnostic(diagnosticTypeMatcher.group());
392 }
393 opened.push(range);
394 result.add(range);
395 }
396 offsetCompensation += matchedText.length();
397 }
398
399 assert opened.isEmpty() : "Stack is not empty";
400
401 matcher.reset();
402 return matcher.replaceAll("");
403 }
404
405 public static StringBuffer addDiagnosticMarkersToText(@NotNull PsiFile psiFile, @NotNull Collection<ActualDiagnostic> diagnostics) {
406 return addDiagnosticMarkersToText(
407 psiFile, diagnostics, Collections.<ActualDiagnostic, TextDiagnostic>emptyMap(),
408 new Function<PsiFile, String>() {
409 @Override
410 public String fun(PsiFile file) {
411 return file.getText();
412 }
413 }
414 );
415 }
416
417 public static StringBuffer addDiagnosticMarkersToText(
418 @NotNull final PsiFile psiFile,
419 @NotNull Collection<ActualDiagnostic> diagnostics,
420 @NotNull Map<ActualDiagnostic, TextDiagnostic> diagnosticToExpectedDiagnostic,
421 @NotNull Function<PsiFile, String> getFileText
422 ) {
423 String text = getFileText.fun(psiFile);
424 StringBuffer result = new StringBuffer();
425 diagnostics = Collections2.filter(diagnostics, new Predicate<ActualDiagnostic>() {
426 @Override
427 public boolean apply(ActualDiagnostic actualDiagnostic) {
428 return psiFile.equals(actualDiagnostic.getFile());
429 }
430 });
431 if (!diagnostics.isEmpty()) {
432 List<DiagnosticDescriptor> diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics);
433
434 Stack<DiagnosticDescriptor> opened = new Stack<DiagnosticDescriptor>();
435 ListIterator<DiagnosticDescriptor> iterator = diagnosticDescriptors.listIterator();
436 DiagnosticDescriptor currentDescriptor = iterator.next();
437
438 for (int i = 0; i < text.length(); i++) {
439 char c = text.charAt(i);
440 while (!opened.isEmpty() && i == opened.peek().end) {
441 closeDiagnosticString(result);
442 opened.pop();
443 }
444 while (currentDescriptor != null && i == currentDescriptor.start) {
445 openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic);
446 if (currentDescriptor.getEnd() == i) {
447 closeDiagnosticString(result);
448 }
449 else {
450 opened.push(currentDescriptor);
451 }
452 if (iterator.hasNext()) {
453 currentDescriptor = iterator.next();
454 }
455 else {
456 currentDescriptor = null;
457 }
458 }
459 result.append(c);
460 }
461
462 if (currentDescriptor != null) {
463 assert currentDescriptor.start == text.length();
464 assert currentDescriptor.end == text.length();
465 openDiagnosticsString(result, currentDescriptor, diagnosticToExpectedDiagnostic);
466 opened.push(currentDescriptor);
467 }
468
469 while (!opened.isEmpty() && text.length() == opened.peek().end) {
470 closeDiagnosticString(result);
471 opened.pop();
472 }
473
474 assert opened.isEmpty() : "Stack is not empty: " + opened;
475 }
476 else {
477 result.append(text);
478 }
479 return result;
480 }
481
482 private static void openDiagnosticsString(
483 StringBuffer result,
484 DiagnosticDescriptor currentDescriptor,
485 Map<ActualDiagnostic, TextDiagnostic> diagnosticToExpectedDiagnostic
486 ) {
487 result.append("<!");
488 for (Iterator<ActualDiagnostic> iterator = currentDescriptor.diagnostics.iterator(); iterator.hasNext(); ) {
489 ActualDiagnostic diagnostic = iterator.next();
490 TextDiagnostic expectedDiagnostic = diagnosticToExpectedDiagnostic.get(diagnostic);
491 if (expectedDiagnostic != null) {
492 TextDiagnostic actualTextDiagnostic = TextDiagnostic.asTextDiagnostic(diagnostic);
493 if (compareTextDiagnostic(expectedDiagnostic, actualTextDiagnostic)) {
494 result.append(expectedDiagnostic.asString());
495 }
496 else {
497 result.append(actualTextDiagnostic.asString());
498 }
499 }
500 else {
501 if (diagnostic.platform != null) {
502 result.append(diagnostic.platform);
503 result.append(":");
504 }
505 result.append(diagnostic.getName());
506 }
507 if (iterator.hasNext()) {
508 result.append(", ");
509 }
510 }
511 result.append("!>");
512 }
513
514 private static void closeDiagnosticString(StringBuffer result) {
515 result.append("<!>");
516 }
517
518 public static class AbstractDiagnosticForTests implements Diagnostic {
519 private final PsiElement element;
520 private final DiagnosticFactory<?> factory;
521
522 public AbstractDiagnosticForTests(@NotNull PsiElement element, @NotNull DiagnosticFactory<?> factory) {
523 this.element = element;
524 this.factory = factory;
525 }
526
527 @NotNull
528 @Override
529 public DiagnosticFactory<?> getFactory() {
530 return factory;
531 }
532
533 @NotNull
534 @Override
535 public Severity getSeverity() {
536 return Severity.ERROR;
537 }
538
539 @NotNull
540 @Override
541 public PsiElement getPsiElement() {
542 return element;
543 }
544
545 @NotNull
546 @Override
547 public List<TextRange> getTextRanges() {
548 return Collections.singletonList(element.getTextRange());
549 }
550
551 @NotNull
552 @Override
553 public PsiFile getPsiFile() {
554 return element.getContainingFile();
555 }
556
557 @Override
558 public boolean isValid() {
559 return true;
560 }
561 }
562
563 public static class SyntaxErrorDiagnosticFactory extends DiagnosticFactory<SyntaxErrorDiagnostic> {
564 public static final SyntaxErrorDiagnosticFactory INSTANCE = new SyntaxErrorDiagnosticFactory();
565
566 private SyntaxErrorDiagnosticFactory() {
567 super(Severity.ERROR);
568 }
569
570 @NotNull
571 @Override
572 public String getName() {
573 return "SYNTAX";
574 }
575 }
576
577 public static class SyntaxErrorDiagnostic extends AbstractDiagnosticForTests {
578 public SyntaxErrorDiagnostic(@NotNull PsiErrorElement errorElement) {
579 super(errorElement, SyntaxErrorDiagnosticFactory.INSTANCE);
580 }
581 }
582
583 public static class DebugInfoDiagnosticFactory extends DiagnosticFactory<DebugInfoDiagnostic> {
584 public static final DebugInfoDiagnosticFactory SMARTCAST = new DebugInfoDiagnosticFactory("SMARTCAST");
585 public static final DebugInfoDiagnosticFactory IMPLICIT_RECEIVER_SMARTCAST = new DebugInfoDiagnosticFactory("IMPLICIT_RECEIVER_SMARTCAST");
586 public static final DebugInfoDiagnosticFactory CONSTANT = new DebugInfoDiagnosticFactory("CONSTANT");
587 public static final DebugInfoDiagnosticFactory LEAKING_THIS = new DebugInfoDiagnosticFactory("LEAKING_THIS");
588 public static final DebugInfoDiagnosticFactory IMPLICIT_EXHAUSTIVE = new DebugInfoDiagnosticFactory("IMPLICIT_EXHAUSTIVE");
589 public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE");
590 public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET");
591 public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED");
592 public static final DebugInfoDiagnosticFactory DYNAMIC = new DebugInfoDiagnosticFactory("DYNAMIC");
593
594 private final String name;
595
596 private DebugInfoDiagnosticFactory(String name, Severity severity) {
597 super(severity);
598 this.name = name;
599 }
600
601 private DebugInfoDiagnosticFactory(String name) {
602 this(name, Severity.ERROR);
603 }
604
605 @NotNull
606 @Override
607 public String getName() {
608 return "DEBUG_INFO_" + name;
609 }
610 }
611
612 public static class DebugInfoDiagnostic extends AbstractDiagnosticForTests {
613 public DebugInfoDiagnostic(@NotNull KtElement element, @NotNull DebugInfoDiagnosticFactory factory) {
614 super(element, factory);
615 }
616 }
617
618 @NotNull
619 private static List<DiagnosticDescriptor> getSortedDiagnosticDescriptors(@NotNull Collection<ActualDiagnostic> diagnostics) {
620 LinkedListMultimap<TextRange, ActualDiagnostic> diagnosticsGroupedByRanges = LinkedListMultimap.create();
621 for (ActualDiagnostic actualDiagnostic : diagnostics) {
622 Diagnostic diagnostic = actualDiagnostic.diagnostic;
623 if (!diagnostic.isValid()) continue;
624 for (TextRange textRange : diagnostic.getTextRanges()) {
625 diagnosticsGroupedByRanges.put(textRange, actualDiagnostic);
626 }
627 }
628 List<DiagnosticDescriptor> diagnosticDescriptors = Lists.newArrayList();
629 for (TextRange range : diagnosticsGroupedByRanges.keySet()) {
630 diagnosticDescriptors.add(
631 new DiagnosticDescriptor(range.getStartOffset(), range.getEndOffset(), diagnosticsGroupedByRanges.get(range)));
632 }
633 Collections.sort(diagnosticDescriptors, new Comparator<DiagnosticDescriptor>() {
634 @Override
635 public int compare(@NotNull DiagnosticDescriptor d1, @NotNull DiagnosticDescriptor d2) {
636 // Start early -- go first; start at the same offset, the one who end later is the outer, i.e. goes first
637 return (d1.start != d2.start) ? d1.start - d2.start : d2.end - d1.end;
638 }
639 });
640 return diagnosticDescriptors;
641 }
642
643 private static class DiagnosticDescriptor {
644 private final int start;
645 private final int end;
646 private final List<ActualDiagnostic> diagnostics;
647
648 DiagnosticDescriptor(int start, int end, List<ActualDiagnostic> diagnostics) {
649 this.start = start;
650 this.end = end;
651 this.diagnostics = diagnostics;
652 }
653
654 public Map<ActualDiagnostic, TextDiagnostic> getTextDiagnosticsMap() {
655 Map<ActualDiagnostic, TextDiagnostic> diagnosticMap = new HashMap<ActualDiagnostic, TextDiagnostic>();
656 for (ActualDiagnostic diagnostic : diagnostics) {
657 diagnosticMap.put(diagnostic, TextDiagnostic.asTextDiagnostic(diagnostic));
658 }
659 return diagnosticMap;
660 }
661
662 public int getStart() {
663 return start;
664 }
665
666 public int getEnd() {
667 return end;
668 }
669
670 public TextRange getTextRange() {
671 return new TextRange(start, end);
672 }
673 }
674
675 public static class ActualDiagnostic {
676 public final Diagnostic diagnostic;
677 public final String platform;
678
679 ActualDiagnostic(@NotNull Diagnostic diagnostic, @Nullable String platform) {
680 this.diagnostic = diagnostic;
681 this.platform = platform;
682 }
683
684 @NotNull
685 public String getName() {
686 return diagnostic.getFactory().getName();
687 }
688
689 @NotNull
690 public PsiFile getFile() {
691 return diagnostic.getPsiFile();
692 }
693
694 @Override
695 public boolean equals(Object obj) {
696 if (!(obj instanceof ActualDiagnostic)) return false;
697
698 ActualDiagnostic other = (ActualDiagnostic) obj;
699 // '==' on diagnostics is intentional here
700 return other.diagnostic == diagnostic &&
701 (other.platform == null ? platform == null : other.platform.equals(platform));
702 }
703
704 @Override
705 public int hashCode() {
706 return System.identityHashCode(diagnostic) * 31 + (platform != null ? platform.hashCode() : 0);
707 }
708
709 @Override
710 public String toString() {
711 return (platform != null ? platform + ":" : "") + diagnostic.toString();
712 }
713 }
714
715 public static class TextDiagnostic {
716 @NotNull
717 private static TextDiagnostic parseDiagnostic(String text) {
718 Matcher matcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(text);
719 if (!matcher.find())
720 throw new IllegalArgumentException("Could not parse diagnostic: " + text);
721
722 String platformPrefix = matcher.group(1);
723 assert platformPrefix == null || platformPrefix.endsWith(":") : platformPrefix;
724 String platform = platformPrefix == null ? null : StringsKt.substringBeforeLast(platformPrefix, ":", platformPrefix);
725
726 String name = matcher.group(2);
727 String parameters = matcher.group(3);
728 if (parameters == null) {
729 return new TextDiagnostic(name, platform, null);
730 }
731
732 List<String> parsedParameters = new SmartList<String>();
733 Matcher parametersMatcher = INDIVIDUAL_PARAMETER_PATTERN.matcher(parameters);
734 while (parametersMatcher.find())
735 parsedParameters.add(unescape(parametersMatcher.group().trim()));
736 return new TextDiagnostic(name, platform, parsedParameters);
737 }
738
739 private static @NotNull String escape(@NotNull String s) {
740 return s.replaceAll("([" + SHOULD_BE_ESCAPED + "])", "\\\\$1");
741 }
742
743 private static @NotNull String unescape(@NotNull String s) {
744 return s.replaceAll("\\\\([" + SHOULD_BE_ESCAPED + "])", "$1");
745 }
746
747 @NotNull
748 public static TextDiagnostic asTextDiagnostic(@NotNull ActualDiagnostic actualDiagnostic) {
749 Diagnostic diagnostic = actualDiagnostic.diagnostic;
750 //noinspection TestOnlyProblems
751 DiagnosticRenderer renderer = DefaultErrorMessages.getRendererForDiagnostic(diagnostic);
752 String diagnosticName = actualDiagnostic.getName();
753 if (renderer instanceof AbstractDiagnosticWithParametersRenderer) {
754 //noinspection unchecked
755 Object[] renderParameters = ((AbstractDiagnosticWithParametersRenderer) renderer).renderParameters(diagnostic);
756 List<String> parameters = ContainerUtil.map(renderParameters, new Function<Object, String>() {
757 @Override
758 public String fun(Object o) {
759 return o != null ? o.toString() : "null";
760 }
761 });
762 return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, parameters);
763 }
764 return new TextDiagnostic(diagnosticName, actualDiagnostic.platform, null);
765 }
766
767 @NotNull
768 private final String name;
769 @Nullable
770 private final String platform;
771 @Nullable
772 private final List<String> parameters;
773
774 public TextDiagnostic(@NotNull String name, @Nullable String platform, @Nullable List<String> parameters) {
775 this.name = name;
776 this.platform = platform;
777 this.parameters = parameters;
778 }
779
780 @Nullable
781 public String getPlatform() {
782 return platform;
783 }
784
785 @NotNull
786 public String getDescription() {
787 return (platform != null ? platform + ":" : "") + name;
788 }
789
790 @Nullable
791 public List<String> getParameters() {
792 return parameters;
793 }
794
795 @Override
796 public boolean equals(Object o) {
797 if (this == o) return true;
798 if (o == null || getClass() != o.getClass()) return false;
799
800 TextDiagnostic that = (TextDiagnostic) o;
801
802 if (!name.equals(that.name)) return false;
803 if (platform != null ? !platform.equals(that.platform) : that.platform != null) return false;
804 if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false;
805
806 return true;
807 }
808
809 @Override
810 public int hashCode() {
811 int result = name.hashCode();
812 result = 31 * result + (platform != null ? platform.hashCode() : 0);
813 result = 31 * result + (parameters != null ? parameters.hashCode() : 0);
814 return result;
815 }
816
817 @NotNull
818 public String asString() {
819 StringBuilder result = new StringBuilder();
820 if (platform != null) {
821 result.append(platform);
822 result.append(":");
823 }
824 result.append(name);
825 if (parameters != null) {
826 result.append("(");
827 result.append(StringUtil.join(parameters, new Function<String, String>() {
828 @Override
829 public String fun(String s) {
830 return escape(s);
831 }
832 }, "; "));
833 result.append(")");
834 }
835 return result.toString();
836 }
837
838 @Override
839 public String toString() {
840 return asString();
841 }
842 }
843
844 public static class DiagnosedRange {
845 private final int start;
846 private int end;
847 private final List<TextDiagnostic> diagnostics = ContainerUtil.newSmartList();
848
849 protected DiagnosedRange(int start) {
850 this.start = start;
851 }
852
853 public int getStart() {
854 return start;
855 }
856
857 public int getEnd() {
858 return end;
859 }
860
861 public List<TextDiagnostic> getDiagnostics() {
862 return diagnostics;
863 }
864
865 public void setEnd(int end) {
866 this.end = end;
867 }
868
869 public void addDiagnostic(String diagnostic) {
870 diagnostics.add(TextDiagnostic.parseDiagnostic(diagnostic));
871 }
872 }
873 }