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