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 }