All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.jet.checkers.CheckerTestUtil Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.jet.checkers;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.diagnostics.AbstractDiagnosticFactory;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.diagnostics.Severity;
import org.jetbrains.jet.lang.psi.JetReferenceExpression;
import org.jetbrains.jet.lang.resolve.AnalyzingUtils;
import org.jetbrains.jet.lang.resolve.BindingContext;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CheckerTestUtil {
    public static final Comparator DIAGNOSTIC_COMPARATOR = new Comparator() {
        @Override
        public int compare(Diagnostic o1, Diagnostic o2) {
            List ranges1 = o1.getTextRanges();
            List ranges2 = o2.getTextRanges();
            if (ranges1.size() != ranges2.size()) return ranges1.size() - ranges2.size();
            for (int i = 0; i < ranges1.size(); i++) {
                TextRange range1 = ranges1.get(i);
                TextRange range2 = ranges2.get(i);
                int startOffset1 = range1.getStartOffset();
                int startOffset2 = range2.getStartOffset();
                if (startOffset1 != startOffset2) {
                    // Start early -- go first
                    return startOffset1 - range2.getStartOffset();
                }
                int endOffset1 = range1.getEndOffset();
                int endOffset2 = range2.getEndOffset();
                if (endOffset1 != endOffset2) {
                    // start at the same offset, the one who end later is the outer, i.e. goes first
                    return endOffset2 - endOffset1;
                }
            }
            return 0;
        }
    };
    private static final Pattern RANGE_START_OR_END_PATTERN = Pattern.compile("()|()");
    private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile("\\w+");

    public static List getDiagnosticsIncludingSyntaxErrors(BindingContext bindingContext, final PsiElement root) {
        ArrayList diagnostics = new ArrayList();
        diagnostics.addAll(Collections2.filter(bindingContext.getDiagnostics(),
                                               new Predicate() {
                                                   @Override
                                                   public boolean apply(Diagnostic diagnostic) {
                                                       return  PsiTreeUtil.isAncestor(root, diagnostic.getPsiElement(), false);
                                                   }
                                               }));
        for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) {
            diagnostics.add(new SyntaxErrorDiagnostic(errorElement));
        }
        List debugAnnotations = getDebugInfoDiagnostics(root, bindingContext);
        diagnostics.addAll(debugAnnotations);
        return diagnostics;
    }

    public static List getDebugInfoDiagnostics(@NotNull PsiElement root, @NotNull BindingContext bindingContext) {
        final List debugAnnotations = Lists.newArrayList();
        DebugInfoUtil.markDebugAnnotations(root, bindingContext, new DebugInfoUtil.DebugInfoReporter() {
            @Override
            public void reportElementWithErrorType(@NotNull JetReferenceExpression expression) {
                newDiagnostic(expression, DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE);
            }

            @Override
            public void reportMissingUnresolved(@NotNull JetReferenceExpression expression) {
                newDiagnostic(expression, DebugInfoDiagnosticFactory.MISSING_UNRESOLVED);
            }

            @Override
            public void reportUnresolvedWithTarget(@NotNull JetReferenceExpression expression, @NotNull String target) {
                newDiagnostic(expression, DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET);
            }

            private void newDiagnostic(JetReferenceExpression expression, DebugInfoDiagnosticFactory factory) {
                debugAnnotations.add(new DebugInfoDiagnostic(expression, factory));
            }
        });
        return debugAnnotations;
    }

    public interface DiagnosticDiffCallbacks {
        void missingDiagnostic(String type, int expectedStart, int expectedEnd);
        void unexpectedDiagnostic(String type, int actualStart, int actualEnd);
    }

    public static void diagnosticsDiff(
            List expected,
            Collection actual,
            DiagnosticDiffCallbacks callbacks
    ) {
        assertSameFile(actual);

        Iterator expectedDiagnostics = expected.iterator();
        List sortedDiagnosticDescriptors = getSortedDiagnosticDescriptors(actual);
        Iterator actualDiagnostics = sortedDiagnosticDescriptors.iterator();

        DiagnosedRange currentExpected = safeAdvance(expectedDiagnostics);
        DiagnosticDescriptor currentActual = safeAdvance(actualDiagnostics);
        while (currentExpected != null || currentActual != null) {
            if (currentExpected != null) {
                if (currentActual == null) {
                    missingDiagnostics(callbacks, currentExpected);
                    currentExpected = safeAdvance(expectedDiagnostics);
                }
                else {
                    int expectedStart = currentExpected.getStart();
                    int actualStart = currentActual.getStart();
                    int expectedEnd = currentExpected.getEnd();
                    int actualEnd = currentActual.getEnd();
                    if (expectedStart < actualStart) {
                        missingDiagnostics(callbacks, currentExpected);
                        currentExpected = safeAdvance(expectedDiagnostics);
                    }
                    else if (expectedStart > actualStart) {
                        unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks);
                        currentActual = safeAdvance(actualDiagnostics);
                    }
                    else if (expectedEnd > actualEnd) {
                        assert expectedStart == actualStart;
                        missingDiagnostics(callbacks, currentExpected);
                        currentExpected = safeAdvance(expectedDiagnostics);
                    }
                    else if (expectedEnd < actualEnd) {
                        assert expectedStart == actualStart;
                        unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks);
                        currentActual = safeAdvance(actualDiagnostics);
                    }
                    else {
                        assert expectedStart == actualStart && expectedEnd == actualEnd;
                        Multiset actualDiagnosticTypes = currentActual.getDiagnosticTypeStrings();
                        Multiset expectedDiagnosticTypes = currentExpected.getDiagnostics();
                        if (!actualDiagnosticTypes.equals(expectedDiagnosticTypes)) {
                            Multiset expectedCopy = HashMultiset.create(expectedDiagnosticTypes);
                            expectedCopy.removeAll(actualDiagnosticTypes);
                            Multiset actualCopy = HashMultiset.create(actualDiagnosticTypes);
                            actualCopy.removeAll(expectedDiagnosticTypes);

                            for (String type : expectedCopy) {
                                callbacks.missingDiagnostic(type, expectedStart, expectedEnd);
                            }
                            for (String type : actualCopy) {
                                callbacks.unexpectedDiagnostic(type, actualStart, actualEnd);
                            }
                        }
                        currentExpected = safeAdvance(expectedDiagnostics);
                        currentActual = safeAdvance(actualDiagnostics);
                    }
                }
            }
            else {
                if (currentActual != null) {
                    unexpectedDiagnostics(currentActual.getDiagnostics(), callbacks);
                    currentActual = safeAdvance(actualDiagnostics);
                }
                else {
                    break;
                }
            }
        }
    }

    private static void assertSameFile(Collection actual) {
        if (actual.isEmpty()) return;
        PsiFile file = actual.iterator().next().getPsiElement().getContainingFile();
        for (Diagnostic diagnostic : actual) {
            assert diagnostic.getPsiFile().equals(file)
                    : "All diagnostics should come from the same file: " + diagnostic.getPsiFile() + ", " + file;
        }
    }

    private static void unexpectedDiagnostics(List actual, DiagnosticDiffCallbacks callbacks) {
        for (Diagnostic diagnostic : actual) {
            List textRanges = diagnostic.getTextRanges();
            for (TextRange textRange : textRanges) {
                callbacks.unexpectedDiagnostic(diagnostic.getFactory().getName(), textRange.getStartOffset(), textRange.getEndOffset());
            }
        }
    }

    private static void missingDiagnostics(DiagnosticDiffCallbacks callbacks, DiagnosedRange currentExpected) {
        for (String type : currentExpected.getDiagnostics()) {
            callbacks.missingDiagnostic(type, currentExpected.getStart(), currentExpected.getEnd());
        }
    }

    private static  T safeAdvance(Iterator iterator) {
        return iterator.hasNext() ? iterator.next() : null;
    }

    public static String parseDiagnosedRanges(String text, List result) {
        Matcher matcher = RANGE_START_OR_END_PATTERN.matcher(text);

        Stack opened = new Stack();

        int offsetCompensation = 0;

        while (matcher.find()) {
            int effectiveOffset = matcher.start() - offsetCompensation;
            String matchedText = matcher.group();
            if ("".equals(matchedText)) {
                opened.pop().setEnd(effectiveOffset);
            }
            else {
                Matcher diagnosticTypeMatcher = INDIVIDUAL_DIAGNOSTIC_PATTERN.matcher(matchedText);
                DiagnosedRange range = new DiagnosedRange(effectiveOffset);
                while (diagnosticTypeMatcher.find()) {
                    range.addDiagnostic(diagnosticTypeMatcher.group());
                }
                opened.push(range);
                result.add(range);
            }
            offsetCompensation += matchedText.length();
        }

        assert opened.isEmpty() : "Stack is not empty";

        matcher.reset();
        return matcher.replaceAll("");
    }
    
    public static StringBuffer addDiagnosticMarkersToText(@NotNull final PsiFile psiFile, Collection diagnostics) {
        StringBuffer result = new StringBuffer();
        String text = psiFile.getText();
        diagnostics = Collections2.filter(diagnostics, new Predicate() {
            @Override
            public boolean apply(Diagnostic diagnostic) {
                return psiFile.equals(diagnostic.getPsiFile());
            }
        });
        if (!diagnostics.isEmpty()) {
            List diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics);

            Stack opened = new Stack();
            ListIterator iterator = diagnosticDescriptors.listIterator();
            DiagnosticDescriptor currentDescriptor = iterator.next();

            for (int i = 0; i < text.length(); i++) {
                char c = text.charAt(i);
                while (!opened.isEmpty() && i == opened.peek().end) {
                    closeDiagnosticString(result);
                    opened.pop();
                }
                while (currentDescriptor != null && i == currentDescriptor.start) {
                    openDiagnosticsString(result, currentDescriptor);
                    if (currentDescriptor.getEnd() == i) {
                        closeDiagnosticString(result);
                    }
                    else {
                        opened.push(currentDescriptor);
                    }
                    if (iterator.hasNext()) {
                        currentDescriptor = iterator.next();
                    }
                    else {
                        currentDescriptor = null;
                    }
                }
                result.append(c);
            }
            
            if (currentDescriptor != null) {
                assert currentDescriptor.start == text.length();
                assert currentDescriptor.end == text.length();
                openDiagnosticsString(result, currentDescriptor);
                opened.push(currentDescriptor);
            }
            
            while (!opened.isEmpty() && text.length() == opened.peek().end) {
                closeDiagnosticString(result);
                opened.pop();
            }

            assert opened.isEmpty() : "Stack is not empty: " + opened;

        }
        else {
            result.append(text);
        }
        return result;
    }

    private static void openDiagnosticsString(StringBuffer result, DiagnosticDescriptor currentDescriptor) {
        result.append(" iterator = currentDescriptor.diagnostics.iterator(); iterator.hasNext(); ) {
            Diagnostic diagnostic = iterator.next();
            result.append(diagnostic.getFactory().getName());
            if (iterator.hasNext()) {
                result.append(", ");
            }
        }
        result.append("!>");
    }

    private static void closeDiagnosticString(StringBuffer result) {
        result.append("");
    }

    public static class AbstractDiagnosticForTests implements Diagnostic {
        private final PsiElement element;
        private final AbstractDiagnosticFactory factory;

        public AbstractDiagnosticForTests(@NotNull PsiElement element, @NotNull AbstractDiagnosticFactory factory) {
            this.element = element;
            this.factory = factory;
        }

        @NotNull
        @Override
        public AbstractDiagnosticFactory getFactory() {
            return factory;
        }

        @NotNull
        @Override
        public Severity getSeverity() {
            return Severity.ERROR;
        }

        @NotNull
        @Override
        public PsiElement getPsiElement() {
            return element;
        }

        @NotNull
        @Override
        public List getTextRanges() {
            return Collections.singletonList(element.getTextRange());
        }

        @NotNull
        @Override
        public PsiFile getPsiFile() {
            return element.getContainingFile();
        }

        @Override
        public boolean isValid() {
            return true;
        }
    }

    public static class SyntaxErrorDiagnosticFactory extends AbstractDiagnosticFactory {
        public static final SyntaxErrorDiagnosticFactory INSTANCE = new SyntaxErrorDiagnosticFactory();

        private SyntaxErrorDiagnosticFactory() {}

        @NotNull
        @Override
        public String getName() {
            return "SYNTAX";
        }
    }

    public static class SyntaxErrorDiagnostic extends AbstractDiagnosticForTests {
        public SyntaxErrorDiagnostic(@NotNull PsiErrorElement errorElement) {
            super(errorElement, SyntaxErrorDiagnosticFactory.INSTANCE);
        }
    }

    public static class DebugInfoDiagnosticFactory extends AbstractDiagnosticFactory {
        public static final DebugInfoDiagnosticFactory ELEMENT_WITH_ERROR_TYPE = new DebugInfoDiagnosticFactory("ELEMENT_WITH_ERROR_TYPE");
        public static final DebugInfoDiagnosticFactory UNRESOLVED_WITH_TARGET = new DebugInfoDiagnosticFactory("UNRESOLVED_WITH_TARGET");
        public static final DebugInfoDiagnosticFactory MISSING_UNRESOLVED = new DebugInfoDiagnosticFactory("MISSING_UNRESOLVED");

        private final String name;
        private DebugInfoDiagnosticFactory(String name) {
            this.name = name;
        }

        @NotNull
        @Override
        public String getName() {
            return "DEBUG_INFO_" + name;
        }
    }

    public static class DebugInfoDiagnostic extends AbstractDiagnosticForTests {
        public DebugInfoDiagnostic(@NotNull JetReferenceExpression reference, @NotNull DebugInfoDiagnosticFactory factory) {
            super(reference, factory);
        }
    }

    private static List getSortedDiagnosticDescriptors(Collection diagnostics) {
        List list = Lists.newArrayList(diagnostics);
        Collections.sort(list, DIAGNOSTIC_COMPARATOR);

        List diagnosticDescriptors = Lists.newArrayList();
        DiagnosticDescriptor currentDiagnosticDescriptor = null;
        for (Diagnostic diagnostic : list) {
            List textRanges = diagnostic.getTextRanges();
            if (!diagnostic.isValid()) continue;

            TextRange textRange = textRanges.get(0);
            if (currentDiagnosticDescriptor != null && currentDiagnosticDescriptor.equalRange(textRange)) {
                currentDiagnosticDescriptor.diagnostics.add(diagnostic);
            }
            else {
                currentDiagnosticDescriptor = new DiagnosticDescriptor(textRange.getStartOffset(), textRange.getEndOffset(), diagnostic);
                diagnosticDescriptors.add(currentDiagnosticDescriptor);
            }
        }
        return diagnosticDescriptors;
    }

    private static class DiagnosticDescriptor {
        private final int start;
        private final int end;
        private final List diagnostics = Lists.newArrayList();

        DiagnosticDescriptor(int start, int end, Diagnostic diagnostic) {
            this.start = start;
            this.end = end;
            this.diagnostics.add(diagnostic);
        }

        public boolean equalRange(TextRange textRange) {
            return start == textRange.getStartOffset() && end == textRange.getEndOffset();
        }

        public Multiset getDiagnosticTypeStrings() {
            Multiset actualDiagnosticTypes = HashMultiset.create();
            for (Diagnostic diagnostic : diagnostics) {
                actualDiagnosticTypes.add(diagnostic.getFactory().getName());
            }
            return actualDiagnosticTypes;
        }

        public int getStart() {
            return start;
        }

        public int getEnd() {
            return end;
        }

        public List getDiagnostics() {
            return diagnostics;
        }

        public TextRange getTextRange() {
            return new TextRange(start, end);
        }
    }

    public static class DiagnosedRange {
        private final int start;
        private int end;
        private final Multiset diagnostics = HashMultiset.create();
        private PsiFile file;

        private DiagnosedRange(int start) {
            this.start = start;
        }

        public int getStart() {
            return start;
        }

        public int getEnd() {
            return end;
        }

        public Multiset getDiagnostics() {
            return diagnostics;
        }

        public void setEnd(int end) {
            this.end = end;
        }
        
        public void addDiagnostic(String diagnostic) {
            diagnostics.add(diagnostic);
        }

        public void setFile(@NotNull PsiFile file) {
            this.file = file;
        }

        @NotNull
        public PsiFile getFile() {
            return file;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy