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