HaxeCheckstyle/haxe-checkstyle

View on GitHub
src/checkstyle/checks/whitespace/SpacingCheck.hx

Summary

Maintainability
Test Coverage
package checkstyle.checks.whitespace;

import haxe.macro.Printer;

/**
    Spacing check on if, for, while, switch, try statements and around operators.
**/
@name("Spacing")
@desc("Spacing check on if, for, while, switch, try statements and around operators.")
class SpacingCheck extends Check {
    /**
        require space around Binop operators ("+", "-", "/", etc.)
    **/
    public var spaceAroundBinop:Bool;

    /**
        enforce no space around Unop operators ("++", "--", "!", "-", "~")
    **/
    public var noSpaceAroundUnop:Bool;

    /**
        policy for if statements (space between "if" and "(")
        - should = require space between statement and condition
        - shouldNot = no space should between statement and condition
        - any = ignored by space check
    **/
    public var spaceIfCondition:SpacingPolicy;

    /**
        policy for for statements (space between "for" and "(")
        - should = require space between statement and condition
        - shouldNot = no space should between statement and condition
        - any = ignored by space check
    **/
    public var spaceForLoop:SpacingPolicy;

    /**
        policy for while statements (space between while" and "(")
        - should = require space between statement and condition
        - shouldNot = no space should between statement and condition
        - any = ignored by space check
    **/
    public var spaceWhileLoop:SpacingPolicy;

    /**
        policy for switch statements (space between "switch" and "(")
        - should = require space between statement and condition
        - shouldNot = no space should between statement and condition
        - any = ignored by space check
    **/
    public var spaceSwitchCase:SpacingPolicy;

    /**
        policy for catch statements (space between "catch" and "(")
        - should = require space between statement and condition
        - shouldNot = no space should between statement and condition
        - any = ignored by space check
    **/
    public var spaceCatch:SpacingPolicy;

    /**
        exclude range operator "..." from "spaceAroundBinop"
    **/
    public var ignoreRangeOperator:Bool;

    public function new() {
        super(TOKEN);
        spaceIfCondition = SHOULD;
        spaceForLoop = SHOULD;
        spaceWhileLoop = SHOULD;
        spaceSwitchCase = SHOULD;
        spaceCatch = SHOULD;
        spaceAroundBinop = true;
        noSpaceAroundUnop = true;
        ignoreRangeOperator = true;
        categories = [Category.STYLE, Category.CLARITY];
    }

    override function actualRun() {
        var root:TokenTree = checker.getTokenTree();
        var acceptableTokens:Array<TokenTree> = root.filterCallback(function(token:TokenTree, depth:Int):FilterResult {
            return switch (token.tok) {
                case Kwd(KwdIf), Kwd(KwdFor), Kwd(KwdWhile), Kwd(KwdSwitch), Kwd(KwdCatch):
                    FoundGoDeeper;
                default:
                    GoDeeper;
            }
        });

        for (token in acceptableTokens) {
            var firstChild:TokenTree = token.getFirstChild();
            switch (token.tok) {
                case Kwd(KwdIf):
                    checkSpaceBetweenExpressions(token.toString(), token, firstChild, spaceIfCondition);
                case Kwd(KwdFor):
                    checkSpaceBetweenExpressions(token.toString(), token, firstChild, spaceForLoop);
                case Kwd(KwdWhile):
                    checkSpaceBetweenExpressions(token.toString(), token, firstChild, spaceWhileLoop);
                case Kwd(KwdSwitch):
                    checkSpaceBetweenExpressions(token.toString(), token, firstChild, spaceSwitchCase);
                case Kwd(KwdCatch):
                    checkSpaceBetweenExpressions(token.toString(), token, firstChild, spaceCatch);
                case _:
            }
        }

        var lastExpr = null;

        if (checker.ast == null) return;
        checker.ast.walkFile(function(e) {
            if (lastExpr == null) {
                lastExpr = e;
                return;
            }

            switch (e.expr) {
                case EBinop(bo, l, r) if (spaceAroundBinop):
                    if (ignoreRangeOperator && binopString(bo) == "...") return;
                    if (r.pos.min - l.pos.max < binopSize(bo) + 2) logPos('No space around "${binopString(bo)}"', e.pos);
                case EUnop(uo, post, e2) if (noSpaceAroundUnop):
                    var dist = 0;
                    if (post) dist = e.pos.max - e2.pos.max;
                    else dist = e2.pos.min - e.pos.min;
                    if (dist > unopSize(uo)) logPos('Space around "${unopString(uo)}"', e.pos);
                default:
            }

            lastExpr = e;
        });
    }

    function checkSpaceBetweenExpressions(name:String, e1:TokenTree, e2:TokenTree, directive:SpacingPolicy) {
        if (e2 == null) return;
        switch (directive) {
            case ANY:
            case SHOULD_NOT:
                if (e2.pos.max - e1.pos.max > 1) logRange('Space between "$name" and "("', e2.pos.max, e2.pos.min);
            case SHOULD:
                if (e2.pos.max - e1.pos.max == 1) logRange('No space between "$name" and "("', e1.pos.max, e2.pos.min);
        }
    }

    function binopSize(bo:Binop):Int {
        return binopString(bo).length;
    }

    function binopString(bo:Binop):String {
        return (new Printer()).printBinop(bo);
    }

    function unopSize(uo:Unop):Int {
        return unopString(uo).length;
    }

    function unopString(uo:Unop):String {
        return (new Printer()).printUnop(uo);
    }

    override public function detectableInstances():DetectableInstances {
        return [{
            fixed: [],
            properties: [{
                propertyName: "spaceIfCondition",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "spaceForLoop",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "spaceWhileLoop",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "spaceWhileLoop",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "spaceSwitchCase",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "spaceCatch",
                values: [SHOULD, SHOULD_NOT, ANY]
            }, {
                propertyName: "ignoreRangeOperator",
                values: [true, false]
            }, {
                propertyName: "spaceAroundBinop",
                values: [true, false]
            }, {
                propertyName: "noSpaceAroundUnop",
                values: [true, false]
            }]
        }];
    }
}

/**
    - should = require space between statement and condition
    - shouldNot = no space should between statement and condition
    - any = ignored by space check
**/
enum abstract SpacingPolicy(String) {
    var SHOULD = "should";
    var SHOULD_NOT = "should_not";
    var ANY = "any";
}