Package ro.redeul.google.go.inspection

Source Code of ro.redeul.google.go.inspection.LabelUsageInspection

package ro.redeul.google.go.inspection;

import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import ro.redeul.google.go.GoBundle;
import ro.redeul.google.go.lang.parser.GoElementTypes;
import ro.redeul.google.go.lang.psi.GoFile;
import ro.redeul.google.go.lang.psi.declarations.GoConstDeclaration;
import ro.redeul.google.go.lang.psi.declarations.GoVarDeclaration;
import ro.redeul.google.go.lang.psi.expressions.literals.GoLiteralFunction;
import ro.redeul.google.go.lang.psi.expressions.literals.GoLiteralIdentifier;
import ro.redeul.google.go.lang.psi.statements.*;
import ro.redeul.google.go.lang.psi.toplevel.GoFunctionDeclaration;
import ro.redeul.google.go.lang.psi.toplevel.GoMethodDeclaration;
import ro.redeul.google.go.lang.psi.visitors.GoRecursiveElementVisitor;

import java.util.*;

import static com.intellij.psi.util.PsiTreeUtil.findCommonParent;
import static com.intellij.psi.util.PsiTreeUtil.isAncestor;
import static ro.redeul.google.go.lang.psi.utils.GoPsiUtils.isNodeOfType;
import static ro.redeul.google.go.lang.psi.utils.GoPsiUtils.resolveSafely;

public class LabelUsageInspection extends AbstractWholeGoFileInspection {
    @Nls
    @NotNull
    @Override
    public String getDisplayName() {
        return "Label usage";
    }

    @Override
    protected void doCheckFile(@NotNull GoFile file, @NotNull final InspectionResult result) {
        new GoRecursiveElementVisitor() {
            @Override
            public void visitFunctionDeclaration(GoFunctionDeclaration declaration) {
                checkFunction(result, declaration);
            }

            @Override
            public void visitMethodDeclaration(GoMethodDeclaration declaration) {
                checkFunction(result, declaration);
            }
        }.visitFile(file);
    }

    private static void checkFunction(final InspectionResult result, GoFunctionDeclaration function) {
        final Map<String, GoLiteralIdentifier> labelDeclarations = new HashMap<String, GoLiteralIdentifier>();
        final List<GoLiteralIdentifier> labelUsages = new ArrayList<GoLiteralIdentifier>();
        new GoRecursiveElementVisitor() {
            @Override
            public void visitLabeledStatement(GoLabeledStatement statement) {
                GoLiteralIdentifier label = statement.getLabel();
                String name = label.getName();
                if (labelDeclarations.containsKey(name)) {
                    result.addProblem(label, GoBundle.message("error.label.already.defined", name));
                } else {
                    labelDeclarations.put(name, label);
                }

                super.visitLabeledStatement(statement);
            }

            @Override
            public void visitBreakStatement(GoBreakStatement statement) {
                checkLabelUsage(statement.getLabel());
            }

            @Override
            public void visitContinueStatement(GoContinueStatement statement) {
                checkLabelUsage(statement.getLabel());
            }

            @Override
            public void visitGotoStatement(GoGotoStatement statement) {
                checkLabelUsage(statement.getLabel());
            }

            private void checkLabelUsage(GoLiteralIdentifier label) {
                if (label == null) {
                    return;
                }

                String name = label.getName();
                if (name == null || name.isEmpty()) {
                    return;
                }

                labelUsages.add(label);
            }

            @Override
            public void visitFunctionLiteral(GoLiteralFunction literal) {
                checkFunction(result, literal);
            }
        }.visitElement(function);

        Set<String> usedLabels = new HashSet<String>();
        for (GoLiteralIdentifier label : labelUsages) {
            String name = label.getName();
            if (labelDeclarations.containsKey(name)) {
                usedLabels.add(name);
            }
        }

        for (Map.Entry<String, GoLiteralIdentifier> e : labelDeclarations.entrySet()) {
            String name = e.getKey();
            if (!usedLabels.contains(name)) {
                result.addProblem(e.getValue(), GoBundle.message("error.label.defined.and.not.used", name));
            }
        }

        for (GoLiteralIdentifier label : labelUsages) {
            String name = label.getName();
            if (name == null || !labelDeclarations.containsKey(name)) {
                continue;
            }
            checkUsage(label, labelDeclarations.get(name), result);
        }
    }

    private static final TokenSet BREAK_LABEL_STATEMENT = TokenSet.orSet(
            GoElementTypes.FOR_STATEMENT,
            GoElementTypes.SWITCH_STATEMENT,
            TokenSet.create(GoElementTypes.SELECT_STATEMENT)
    );

    private static void checkUsage(GoLiteralIdentifier label, GoLiteralIdentifier declaration,
                                   InspectionResult result) {
        PsiElement usageParent = label.getParent();
        GoLabeledStatement labeledStatement = (GoLabeledStatement) declaration.getParent();
        GoStatement enclosingStatement = labeledStatement.getStatement();
        if (usageParent instanceof GoBreakStatement) {
            if (!isNodeOfType(enclosingStatement, BREAK_LABEL_STATEMENT) ||
                !isAncestor(enclosingStatement, usageParent, true)) {
                result.addProblem(label, GoBundle.message("error.invalid.break.label", label.getName()));
            }
        } else if (usageParent instanceof GoContinueStatement) {
            if (!isNodeOfType(enclosingStatement, GoElementTypes.FOR_STATEMENT) ||
                !isAncestor(enclosingStatement, usageParent, true)) {
                result.addProblem(label, GoBundle.message("error.invalid.continue.label", label.getName()));
            }
        } else if (usageParent instanceof GoGotoStatement) {
            GoGotoStatement gotoStatement = ((GoGotoStatement) usageParent);
            checkJumpInsideBlock(gotoStatement, labeledStatement, result);
            checkJumpOverVariableDeclaration(gotoStatement, labeledStatement, result);
        }
    }

    private static void checkJumpOverVariableDeclaration(GoGotoStatement gotoStatement,
                                                         GoLabeledStatement labeledStatement, InspectionResult result) {
        if (gotoStatement.getTextOffset() >= labeledStatement.getTextOffset()) {
            return;
        }

        PsiElement parent = findCommonParent(gotoStatement, labeledStatement);
        if (parent == null || !labeledStatement.getParent().equals(parent)) {
            return;
        }

        GoLiteralIdentifier label = gotoStatement.getLabel();
        PsiElement gotoParent = gotoStatement;
        while (gotoParent != null && !parent.equals(gotoParent.getParent())) {
            gotoParent = gotoParent.getParent();
        }
        if (gotoParent == null) {
            return;
        }

        PsiElement statement = gotoParent.getNextSibling();
        while (statement != null && !statement.equals(labeledStatement)) {
            if (statement instanceof GoShortVarDeclaration) {
                addJumpOverProblem(label, ((GoShortVarDeclaration) statement).getIdentifiers(), result);
            } else if (statement instanceof GoVarDeclaration) {
                addJumpOverProblem(label, ((GoVarDeclaration) statement).getIdentifiers(), result);
            } else if (statement instanceof GoConstDeclaration) {
                addJumpOverProblem(label, ((GoConstDeclaration) statement).getIdentifiers(), result);
            }
            statement = statement.getNextSibling();
        }
    }

    private static void addJumpOverProblem(GoLiteralIdentifier label, GoLiteralIdentifier[] identifiers,
                                           InspectionResult result) {
        GoLiteralIdentifier identifier = findFirstIdentifier(identifiers);
        if (identifier == null) {
            return;
        }

        result.addProblem(label,
                GoBundle.message("error.goto.jumps.over.declaration", label.getName(), identifier.getName()));
    }

    private static GoLiteralIdentifier findFirstIdentifier(GoLiteralIdentifier[] identifiers) {
        for (GoLiteralIdentifier identifier : identifiers) {
            if (identifier.isBlank()) {
                continue;
            }

            PsiElement resolve = resolveSafely(identifier, PsiElement.class);
            if (resolve != null && resolve.getTextOffset() != identifier.getTextOffset()) {
                continue;
            }

            return identifier;
        }
        return null;
    }

    private static void checkJumpInsideBlock(GoGotoStatement gotoStatement, GoLabeledStatement labeledStatement,
                                             InspectionResult result) {
        PsiElement parent = findCommonParent(gotoStatement, labeledStatement);
        GoLiteralIdentifier label = gotoStatement.getLabel();
        String name = label.getName();
        if (!labeledStatement.getParent().equals(parent) &&
            !labeledStatement.equals(parent)) {
            result.addProblem(label, GoBundle.message("error.goto.jumps.into.block", name));
        }
    }
}
TOP

Related Classes of ro.redeul.google.go.inspection.LabelUsageInspection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.