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