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    }