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