sleekbyte/tailor

View on GitHub
src/main/java/com/sleekbyte/tailor/listeners/BraceStyleListener.java

Summary

Maintainability
D
1 day
Test Coverage
package com.sleekbyte.tailor.listeners;

import com.sleekbyte.tailor.antlr.SwiftBaseListener;
import com.sleekbyte.tailor.antlr.SwiftParser;
import com.sleekbyte.tailor.common.Location;
import com.sleekbyte.tailor.common.Messages;
import com.sleekbyte.tailor.common.Rules;
import com.sleekbyte.tailor.output.Printer;
import com.sleekbyte.tailor.utils.ListenerUtil;
import com.sleekbyte.tailor.utils.ParseTreeUtil;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNodeImpl;

import java.util.List;

/**
 * Parse tree listener for brace style checks.
 */
public class BraceStyleListener extends SwiftBaseListener {

    private Printer printer;
    private BufferedTokenStream tokenStream;

    public BraceStyleListener(Printer printer, BufferedTokenStream tokenStream) {
        this.printer = printer;
        this.tokenStream = tokenStream;
    }

    @Override
    public void enterClassBody(SwiftParser.ClassBodyContext ctx) {
        verifyClassBraceStyle(ctx);
    }

    @Override
    public void enterClosureExpression(SwiftParser.ClosureExpressionContext ctx) {
        verifyClosureExpressionBraceStyle(ctx);
    }

    @Override
    public void enterStructBody(SwiftParser.StructBodyContext ctx) {
        verifyStructBraceStyle(ctx);
    }

    @Override
    public void enterSwitchStatement(SwiftParser.SwitchStatementContext ctx) {
        verifySwitchStatementBraceStyle(ctx);
    }

    @Override
    public void enterFunctionDeclaration(SwiftParser.FunctionDeclarationContext ctx) {
        verifyFunctionBraceStyle(ctx);
    }

    @Override
    public void enterElseClause(SwiftParser.ElseClauseContext ctx) {
        verifyElseClauseBraceStyle(ctx);
    }

    @Override
    public void enterIfStatement(SwiftParser.IfStatementContext ctx) {
        verifyIfStatementBraceStyle(ctx);
    }

    @Override
    public void enterWhileStatement(SwiftParser.WhileStatementContext ctx) {
        verifyWhileLoopBraceStyle(ctx);
    }

    @Override
    public void enterRepeatWhileStatement(SwiftParser.RepeatWhileStatementContext ctx) {
        verifyRepeatWhileLoopBraceStyle(ctx);
    }

    @Override
    public void enterInitializerDeclaration(SwiftParser.InitializerDeclarationContext ctx) {
        verifyInitializerBraceStyle(ctx);
    }

    @Override
    public void enterForInStatement(SwiftParser.ForInStatementContext ctx) {
        verifyForInStatementBraceStyle(ctx);
    }

    @Override
    public void enterProtocolBody(SwiftParser.ProtocolBodyContext ctx) {
        verifyProtocolBraceStyle(ctx);
    }

    @Override
    public void enterUnionStyleEnum(SwiftParser.UnionStyleEnumContext ctx) {
        verifyEnumBraceStyle(ctx);
    }

    @Override
    public void enterRawValueStyleEnum(SwiftParser.RawValueStyleEnumContext ctx) {
        verifyEnumBraceStyle(ctx);
    }

    @Override
    public void enterExtensionBody(SwiftParser.ExtensionBodyContext ctx) {
        verifyExtensionBraceStyle(ctx);
    }

    @Override
    public void enterGetterClause(SwiftParser.GetterClauseContext ctx) {
        verifyGetterBraceStyle(ctx);
    }

    @Override
    public void enterSetterClause(SwiftParser.SetterClauseContext ctx) {
        verifySetterBraceStyle(ctx);
    }

    @Override
    public void enterSubscriptDeclaration(SwiftParser.SubscriptDeclarationContext ctx) {
        verifySubscriptBraceStyle(ctx);
    }

    @Override
    public void enterGetterSetterBlock(SwiftParser.GetterSetterBlockContext ctx) {
        verifyGetterSetterBraceStyle(ctx);
    }

    @Override
    public void enterWillSetClause(SwiftParser.WillSetClauseContext ctx) {
        verifyWillSetClauseBraceStyle(ctx);
    }

    @Override
    public void enterDidSetClause(SwiftParser.DidSetClauseContext ctx) {
        verifyDidSetClauseBraceStyle(ctx);
    }

    @Override
    public void enterWillSetDidSetBlock(SwiftParser.WillSetDidSetBlockContext ctx) {
        verifyWillSetDidSetBlockBraceStyle(ctx);
    }

    private void verifySwitchStatementBraceStyle(SwiftParser.SwitchStatementContext ctx) {
        // Open brace
        Location switchExpLocation = ListenerUtil.getTokenLocation(ctx.expression().getStop());
        Location openBraceLocation = ListenerUtil.getLocationOfChildToken(ctx, 2);

        if (switchExpLocation.line != openBraceLocation.line) {
            this.printer.warn(Rules.BRACE_STYLE, Messages.SWITCH_STATEMENT + Messages.OPEN_BRACE_STYLE,
                openBraceLocation);
        }

        // Close brace
        verifyBodyCloseBraceStyle(ctx, Messages.SWITCH_STATEMENT);

    }

    private void verifyForInStatementBraceStyle(SwiftParser.ForInStatementContext ctx) {
        Token leftOfCodeBlock = ctx.whereClause() == null ? ctx.expression().getStop() : ctx.whereClause().getStop();
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), leftOfCodeBlock, Messages.FOR_IN_LOOP);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.FOR_IN_LOOP);
    }

    private void verifyInitializerBraceStyle(SwiftParser.InitializerDeclarationContext ctx) {
        // Check if there is an element between the parameter clause and open brace (i.e. 'throws' or 'rethrows')
        // (Issue #405)
        ParseTree leftSibling = ParseTreeUtil.getLeftSibling(ctx.initializerBody());
        if (leftSibling instanceof TerminalNodeImpl) {
            verifyCodeBlockOpenBraceStyle(
                ctx.initializerBody().codeBlock(),
                ((TerminalNodeImpl) leftSibling).getSymbol(),
                Messages.INITIALIZER_BODY);
        } else {
            verifyCodeBlockOpenBraceStyle(ctx.initializerBody().codeBlock(), ctx.parameterClause().getStop(),
                Messages.INITIALIZER_BODY);
        }

        verifyBodyCloseBraceStyle(ctx.initializerBody().codeBlock(), Messages.INITIALIZER_BODY);
    }

    private void verifyRepeatWhileLoopBraceStyle(SwiftParser.RepeatWhileStatementContext ctx) {
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), ctx.getStart(), Messages.REPEAT_WHILE_STATEMENT);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.REPEAT_WHILE_STATEMENT);
    }

    private void verifyWhileLoopBraceStyle(SwiftParser.WhileStatementContext ctx) {
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), ctx.conditionList().getStop(), Messages.WHILE_STATEMENT);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.WHILE_STATEMENT);
    }

    private void verifyIfStatementBraceStyle(SwiftParser.IfStatementContext ctx) {
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), ctx.conditionList().getStop(), Messages.IF_STATEMENT);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.IF_STATEMENT);
    }

    private void verifyElseClauseBraceStyle(SwiftParser.ElseClauseContext ctx) {
        if (ctx.codeBlock() == null) {
            return;
        }
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), ctx.getStart(), Messages.ELSE_CLAUSE);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.ELSE_CLAUSE);
    }

    private void verifyFunctionBraceStyle(SwiftParser.FunctionDeclarationContext ctx) {
        if (ctx.functionBody() == null) {
            return;
        }
        Token left = ParseTreeUtil.getStopTokenForNode(ParseTreeUtil.getLeftSibling(ctx.functionBody()));
        verifyCodeBlockOpenBraceStyle(ctx.functionBody().codeBlock(), left, Messages.FUNCTION);
        verifyBodyCloseBraceStyle(ctx.functionBody().codeBlock(), Messages.FUNCTION);
    }

    private void verifyClassBraceStyle(SwiftParser.ClassBodyContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.CLASS);
        verifyBodyCloseBraceStyle(ctx, Messages.CLASS);
    }

    private void verifyStructBraceStyle(SwiftParser.StructBodyContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.STRUCT);
        verifyBodyCloseBraceStyle(ctx, Messages.STRUCT);
    }

    private void verifyProtocolBraceStyle(SwiftParser.ProtocolBodyContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.PROTOCOL);
        verifyBodyCloseBraceStyle(ctx, Messages.PROTOCOL);
    }

    private void verifyEnumBraceStyle(ParserRuleContext ctx) {
        for (ParseTree child : ctx.children) {
            if (child instanceof TerminalNodeImpl && child.getText().equals("{")) {
                Token openBrace = ((TerminalNodeImpl) child).getSymbol();
                Location openBraceLocation = ListenerUtil.getTokenLocation(openBrace);
                ParserRuleContext leftSibling = (ParserRuleContext) ParseTreeUtil.getLeftSibling(child);
                Location leftSiblingLocation = ListenerUtil.getContextStopLocation(leftSibling);

                if (openBraceLocation.line != leftSiblingLocation.line) {
                    printer.warn(Rules.BRACE_STYLE, Messages.ENUM + Messages.OPEN_BRACE_STYLE, openBraceLocation);
                } else if (checkLeftSpaces(leftSibling.getStop(), openBrace, 1)) {
                    printer.error(Rules.BRACE_STYLE, Messages.OPEN_BRACE + Messages.SPACE_BEFORE, openBraceLocation);
                }
                break;
            }
        }

        ParseTree lastChild = ParseTreeUtil.getLastChild(ctx);
        verifyCloseBraceStyle(lastChild, ParseTreeUtil.getLeftSibling(lastChild), Messages.ENUM);
    }

    private void verifyClosureExpressionBraceStyle(SwiftParser.ClosureExpressionContext ctx) {
        ParseTree left = ParseTreeUtil.getLeftNode(ctx);
        if (left == null) {
            return;
        }

        // open brace style check
        Location leftLocation = ListenerUtil.getTokenLocation(ParseTreeUtil.getStopTokenForNode(left));
        verifyCodeBlockOpenBraceIsInline(ctx, leftLocation, Messages.CLOSURE);

        // close brace style check
        verifyClosureCloseBraceStyle(ctx);

        /* It doesn't always make sense to check if an opening brace for a closure has a single space before it.
           Example: list.map({(element: Int) in element * 2})
           Only places worth checking are scenarios like these:
            list.map() {(element: Int) in element * 2}
           or
            list.map {(element: Int) in element * 2}
         */
        ParseTree leftSibling = ParseTreeUtil.getLeftSibling(ctx);
        if (leftSibling != null && (leftSibling instanceof SwiftParser.FunctionCallArgumentClauseContext
            || leftSibling instanceof SwiftParser.PostfixExpressionContext)) {
            Token leftToken = ((ParserRuleContext) leftSibling).getStop();
            verifySingleSpaceBeforeOpenBrace(ctx, leftToken);
        }

    }

    private void verifyExtensionBraceStyle(SwiftParser.ExtensionBodyContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.EXTENSION);
        verifyBodyCloseBraceStyle(ctx, Messages.EXTENSION);
    }

    private void verifyGetterBraceStyle(SwiftParser.GetterClauseContext ctx) {
        TerminalNodeImpl get = (TerminalNodeImpl) ParseTreeUtil.getLeftSibling(ctx.codeBlock());
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), get.getSymbol(), Messages.GETTER);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.GETTER);
    }

    private void verifySetterBraceStyle(SwiftParser.SetterClauseContext ctx) {
        ParseTree leftSibling = ParseTreeUtil.getLeftSibling(ctx.codeBlock());
        Token set = ParseTreeUtil.getStopTokenForNode(leftSibling);
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), set, Messages.SETTER);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.SETTER);
    }

    private void verifySubscriptBraceStyle(SwiftParser.SubscriptDeclarationContext ctx) {
        verifyBodyOpenBraceStyle((ParserRuleContext) ParseTreeUtil.getLastChild(ctx), Messages.SUBSCRIPT);
        verifyBodyCloseBraceStyle((ParserRuleContext) ParseTreeUtil.getLastChild(ctx), Messages.SUBSCRIPT);
    }

    private void verifyGetterSetterBraceStyle(SwiftParser.GetterSetterBlockContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.GETTER_SETTER_BLOCK);
        verifyBodyCloseBraceStyle(ctx, Messages.GETTER_SETTER_BLOCK);
    }

    private void verifyWillSetClauseBraceStyle(SwiftParser.WillSetClauseContext ctx) {
        Token left = ParseTreeUtil.getStopTokenForNode(ParseTreeUtil.getLeftSibling(ctx.codeBlock()));
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), left, Messages.WILL_SET_CLAUSE);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.WILL_SET_CLAUSE);
    }

    private void verifyDidSetClauseBraceStyle(SwiftParser.DidSetClauseContext ctx) {
        Token left = ParseTreeUtil.getStopTokenForNode(ParseTreeUtil.getLeftSibling(ctx.codeBlock()));
        verifyCodeBlockOpenBraceStyle(ctx.codeBlock(), left, Messages.DID_SET_CLAUSE);
        verifyBodyCloseBraceStyle(ctx.codeBlock(), Messages.DID_SET_CLAUSE);
    }

    private void verifyWillSetDidSetBlockBraceStyle(SwiftParser.WillSetDidSetBlockContext ctx) {
        verifyBodyOpenBraceStyle(ctx, Messages.WILLSET_DIDSET_BLOCK);
        verifyBodyCloseBraceStyle(ctx, Messages.WILLSET_DIDSET_BLOCK);
    }

    private void verifySingleSpaceBeforeOpenBrace(ParserRuleContext codeBlockCtx, Token left) {
        Token openBrace = codeBlockCtx.getStart();
        if (checkLeftSpaces(left, openBrace, 1)) {
            printer.error(Rules.BRACE_STYLE, Messages.OPEN_BRACE + Messages.SPACE_BEFORE,
                ListenerUtil.getTokenLocation(openBrace));
        }
    }

    private void verifyCodeBlockOpenBraceIsInline(ParserRuleContext codeBlockCtx, Location constructLocation,
                                                  String constructName) {
        Location openBraceLocation = ListenerUtil.getLocationOfChildToken(codeBlockCtx, 0);
        if (constructLocation.line != openBraceLocation.line) {
            this.printer.warn(Rules.BRACE_STYLE, constructName + Messages.OPEN_BRACE_STYLE, openBraceLocation);
        }
    }

    private void verifyCodeBlockOpenBraceStyle(ParserRuleContext codeBlockCtx, Token construct, String constructName) {
        verifyCodeBlockOpenBraceIsInline(codeBlockCtx, ListenerUtil.getTokenLocation(construct), constructName);
        verifySingleSpaceBeforeOpenBrace(codeBlockCtx, construct);
    }

    private void verifyBodyOpenBraceStyle(ParserRuleContext ctx, String constructName) {
        ParserRuleContext leftSibling = (ParserRuleContext) ParseTreeUtil.getLeftSibling(ctx);
        Location constructLocation = ListenerUtil.getContextStopLocation(leftSibling);
        verifyCodeBlockOpenBraceIsInline(ctx, constructLocation, constructName);
        verifySingleSpaceBeforeOpenBrace(ctx, leftSibling.getStop());
    }

    private void verifyCloseBraceStyle(ParseTree closeBrace, ParseTree closeBraceLeftSibling, String constructName) {
        Token closeBraceToken = ((TerminalNodeImpl)closeBrace).getSymbol();
        Location closeBraceLocation = ListenerUtil.getTokenLocation(closeBraceToken);

        if (commentLeftOfCloseBrace(closeBraceToken)) {
            this.printer.warn(Rules.BRACE_STYLE, constructName + Messages.CLOSE_BRACE_STYLE, closeBraceLocation);
            return;
        }

        Location closeBraceLeftSiblingLocation = ListenerUtil.getParseTreeStopLocation(closeBraceLeftSibling);
        if (closeBraceLocation.line == closeBraceLeftSiblingLocation.line) {
            if (!closeBraceLeftSibling.getText().equals("{")) {
                this.printer.warn(Rules.BRACE_STYLE, constructName + Messages.CLOSE_BRACE_STYLE, closeBraceLocation);
            } else if (closeBraceLocation.column - closeBraceLeftSiblingLocation.column != 1) {
                this.printer.warn(Rules.BRACE_STYLE, Messages.EMPTY_BODY, closeBraceLeftSiblingLocation);
            }
        }
    }

    private void verifyClosureCloseBraceStyle(SwiftParser.ClosureExpressionContext ctx) {
        ParseTree closeBrace = ParseTreeUtil.getLastChild(ctx);
        Token closeBraceToken = ((TerminalNodeImpl) closeBrace).getSymbol();
        Location closeBraceLocation = ListenerUtil.getTokenLocation(closeBraceToken);
        Location openBraceLocation = ListenerUtil.getLocationOfChildToken(ctx, 0);

        if (openBraceLocation.line != closeBraceLocation.line && commentLeftOfCloseBrace(closeBraceToken)) {
            this.printer.warn(Rules.BRACE_STYLE, Messages.CLOSURE + Messages.CLOSE_BRACE_STYLE, closeBraceLocation);
            return;
        }

        Location leftSiblingLocation = ListenerUtil.getParseTreeStopLocation(ParseTreeUtil.getLeftSibling(closeBrace));
        if (leftSiblingLocation.line == closeBraceLocation.line && openBraceLocation.line != closeBraceLocation.line) {
            this.printer.warn(Rules.BRACE_STYLE, Messages.CLOSURE + Messages.CLOSE_BRACE_STYLE, closeBraceLocation);
        }
    }

    private boolean commentLeftOfCloseBrace(Token closeBraceToken) {
        Location closeBraceLocation = ListenerUtil.getTokenLocation(closeBraceToken);
        List<Token> tokens = tokenStream.getHiddenTokensToLeft(closeBraceToken.getTokenIndex());
        // if comments are to the left of }
        if (tokens != null) {
            Token commentToken = getLastCommentToken(tokens);
            if (commentToken != null) {
                int commentEndLine = ListenerUtil.getEndLineOfToken(commentToken);
                if (commentEndLine == closeBraceLocation.line) {
                    return true;
                }
            }
        }
        return false;
    }

    private void verifyBodyCloseBraceStyle(ParserRuleContext bodyCtx, String constructName) {
        ParseTree closeBrace = ParseTreeUtil.getLastChild(bodyCtx);
        ParseTree closeBraceLeftSibling = ParseTreeUtil.getLeftSibling(closeBrace);
        verifyCloseBraceStyle(closeBrace, closeBraceLeftSibling, constructName);
    }

    private Token getLastCommentToken(List<Token> tokens) {
        for (int i = tokens.size() - 1; i >= 0; i--) {
            if (ListenerUtil.isComment(tokens.get(i))) {
                return tokens.get(i);
            }
        }

        return null;
    }

    private boolean checkLeftSpaces(Token left, Token op, int numSpaces) {
        return op.getLine() == left.getLine()
            && op.getCharPositionInLine() - ListenerUtil.getLastCharPositionInLine(left) != numSpaces + 1;
    }
}