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