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 }