org.napile.compiler.checkers.CheckerTestUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.napile.compiler.checkers.CheckerTestUtil.java

Source

/*
 * Copyright 2010-2012 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.napile.compiler.checkers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.napile.compiler.lang.diagnostics.AbstractDiagnosticFactory;
import org.napile.compiler.lang.diagnostics.Diagnostic;
import org.napile.compiler.lang.diagnostics.Severity;
import org.napile.compiler.lang.resolve.AnalyzingUtils;
import org.napile.compiler.lang.resolve.BindingTrace;
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;

/**
 * @author abreslav
 */
public class CheckerTestUtil {
    public static final Comparator<Diagnostic> DIAGNOSTIC_COMPARATOR = new Comparator<Diagnostic>() {
        @Override
        public int compare(Diagnostic o1, Diagnostic o2) {
            List<TextRange> ranges1 = o1.getTextRanges();
            List<TextRange> 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("(<!\\w+(,\\s*\\w+)*!>)|(<!>)");
    private static final Pattern INDIVIDUAL_DIAGNOSTIC_PATTERN = Pattern.compile("\\w+");

    public static List<Diagnostic> getDiagnosticsIncludingSyntaxErrors(BindingTrace bindingContext,
            PsiElement root) {
        ArrayList<Diagnostic> diagnostics = new ArrayList<Diagnostic>();
        diagnostics.addAll(bindingContext.getDiagnostics());
        for (PsiErrorElement errorElement : AnalyzingUtils.getSyntaxErrorRanges(root)) {
            diagnostics.add(new SyntaxErrorDiagnostic(errorElement));
        }
        return diagnostics;
    }

    public interface DiagnosticDiffCallbacks {
        @NotNull
        PsiFile getFile();

        void missingDiagnostic(String type, int expectedStart, int expectedEnd);

        void unexpectedDiagnostic(String type, int actualStart, int actualEnd);
    }

    public static void diagnosticsDiff(List<DiagnosedRange> expected, Collection<Diagnostic> actual,
            DiagnosticDiffCallbacks callbacks) {
        Iterator<DiagnosedRange> expectedDiagnostics = expected.iterator();
        List<DiagnosticDescriptor> sortedDiagnosticDescriptors = getSortedDiagnosticDescriptors(actual);
        Iterator<DiagnosticDescriptor> 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<String> actualDiagnosticTypes = currentActual.getDiagnosticTypeStrings();
                        Multiset<String> expectedDiagnosticTypes = currentExpected.getDiagnostics();
                        if (!actualDiagnosticTypes.equals(expectedDiagnosticTypes)) {
                            Multiset<String> expectedCopy = HashMultiset.create(expectedDiagnosticTypes);
                            expectedCopy.removeAll(actualDiagnosticTypes);
                            Multiset<String> 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;
                }
            }

            //            assert expectedDiagnostics.hasNext() || actualDiagnostics.hasNext();
        }
    }

    private static void unexpectedDiagnostics(List<Diagnostic> actual, DiagnosticDiffCallbacks callbacks) {
        for (Diagnostic diagnostic : actual) {
            if (!diagnostic.getPsiFile().equals(callbacks.getFile()))
                continue;
            List<TextRange> 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()) {
            if (!currentExpected.getFile().equals(callbacks.getFile()))
                return;
            callbacks.missingDiagnostic(type, currentExpected.getStart(), currentExpected.getEnd());
        }
    }

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

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

        Stack<DiagnosedRange> opened = new Stack<DiagnosedRange>();

        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(PsiFile psiFile, BindingTrace bindingContext,
            List<PsiErrorElement> syntaxErrors) {
        Collection<Diagnostic> diagnostics = new ArrayList<Diagnostic>();
        diagnostics.addAll(bindingContext.getDiagnostics());
        for (PsiErrorElement syntaxError : syntaxErrors) {
            diagnostics.add(new SyntaxErrorDiagnostic(syntaxError));
        }

        return addDiagnosticMarkersToText(psiFile, diagnostics);
    }

    public static StringBuffer addDiagnosticMarkersToText(@NotNull final PsiFile psiFile,
            Collection<Diagnostic> diagnostics) {
        StringBuffer result = new StringBuffer();
        String text = psiFile.getText();
        diagnostics = Collections2.filter(diagnostics, new Predicate<Diagnostic>() {
            @Override
            public boolean apply(@Nullable Diagnostic diagnostic) {
                if (diagnostic == null || !psiFile.equals(diagnostic.getPsiFile()))
                    return false;
                return true;
            }
        });
        if (!diagnostics.isEmpty()) {
            List<DiagnosticDescriptor> diagnosticDescriptors = getSortedDiagnosticDescriptors(diagnostics);

            Stack<DiagnosticDescriptor> opened = new Stack<DiagnosticDescriptor>();
            ListIterator<DiagnosticDescriptor> 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("<!");
        for (Iterator<Diagnostic> 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("<!>");
    }

    private static class SyntaxErrorDiagnosticFactory extends AbstractDiagnosticFactory {
        private static final SyntaxErrorDiagnosticFactory instance = new SyntaxErrorDiagnosticFactory();

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

    public static class SyntaxErrorDiagnostic implements Diagnostic {
        private final PsiErrorElement errorElement;

        public SyntaxErrorDiagnostic(@NotNull PsiErrorElement errorElement) {
            this.errorElement = errorElement;
        }

        @NotNull
        @Override
        public AbstractDiagnosticFactory getFactory() {
            return SyntaxErrorDiagnosticFactory.instance;
        }

        @NotNull
        @Override
        public Severity getSeverity() {
            throw new IllegalStateException();
        }

        @NotNull
        @Override
        public PsiErrorElement getPsiElement() {
            return errorElement;
        }

        @NotNull
        @Override
        public List<TextRange> getTextRanges() {
            return Collections.singletonList(errorElement.getTextRange());
        }

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

        @Override
        public boolean isValid() {
            return errorElement.isValid();
        }
    }

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

        List<DiagnosticDescriptor> diagnosticDescriptors = Lists.newArrayList();
        DiagnosticDescriptor currentDiagnosticDescriptor = null;
        for (Diagnostic diagnostic : list) {
            List<TextRange> 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<Diagnostic> 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<String> getDiagnosticTypeStrings() {
            Multiset<String> 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<Diagnostic> 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<String> 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<String> 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;
        }
    }
}