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