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