Skip to content

Commit

Permalink
Merge pull request #201 from HaxeCheckstyle/whitespace
Browse files Browse the repository at this point in the history
added OperatorWhitespaceCheck, see #135
  • Loading branch information
AlexHaxe committed Mar 19, 2016
2 parents 566b7f3 + 92e8701 commit 9cb3ce2
Show file tree
Hide file tree
Showing 15 changed files with 647 additions and 10 deletions.
13 changes: 13 additions & 0 deletions checkstyle.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,19 @@
},
"type": "NestedTryDepth"
},
{
"props": {
"assignOpPolicy": "around",
"unaryOpPolicy": "none",
"arithmeticOpPolicy": "around",
"compareOpPolicy": "around",
"bitwiseOpPolicy": "around",
"boolOpPolicy": "around",
"intervalOpPolicy": "none",
"severity": "INFO"
},
"type": "OperatorWhitespace"
},
{
"props": {
"tokens": [
Expand Down
13 changes: 13 additions & 0 deletions resources/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,19 @@
},
"type": "NullableParameter"
},
{
"props": {
"unaryOpPolicy": "none",
"boolOpPolicy": "around",
"intervalOpPolicy": "none",
"assignOpPolicy": "around",
"bitwiseOpPolicy": "around",
"arithmeticOpPolicy": "around",
"compareOpPolicy": "around",
"severity": "IGNORE"
},
"type": "OperatorWhitespace"
},
{
"props": {
"tokens": [
Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/Checker.hx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class Checker {

function findLineSeparator() {
var code = file.content;
for (i in 0 ... code.length) {
for (i in 0...code.length) {
var char = code.charAt(i);
if (char == "\r" || char == "\n") {
lineSeparator = char;
Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/ChecksInfo.hx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ChecksInfo {
for (cl in checksClasses) {
if (ignoreClass(cl)) continue;
var names:Array<Dynamic> = getCheckNameFromClass(cl);
for (i in 0 ... names.length) {
for (i in 0...names.length) {
var desc = getCheckDescription(cl);
checkInfos[names[i]] = {
name: names[i],
Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/checks/Check.hx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class Check {

function isLineSuppressed(i:Int):Bool {
var pos:Int = 0;
for (j in 0 ... i + 1) pos += checker.lines[j].length;
for (j in 0...i + 1) pos += checker.lines[j].length;
return isCharPosSuppressed(pos);
}

Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/checks/coding/DefaultComesLastCheck.hx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class DefaultComesLastCheck extends Check {
if (tokens[tokens.length - 1].is(Kwd(KwdDefault))) continue;

var defaultExists = false;
for (i in 0 ... tokens.length) {
for (i in 0...tokens.length) {
if (tokens[i].is(Kwd(KwdDefault)) && i < tokens.length - 1) {
logPos("Default should be last label in the switch", token.pos);
continue;
Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/checks/size/LineLengthCheck.hx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class LineLengthCheck extends Check {

override function actualRun() {
var ignoreRE = new EReg(ignorePattern, "");
for (i in 0 ... checker.lines.length) {
for (i in 0...checker.lines.length) {
var line = checker.lines[i];
if (line.length > max) {
if (ignoreRE.match(line) || isLineSuppressed(i)) continue;
Expand Down
2 changes: 1 addition & 1 deletion src/checkstyle/checks/whitespace/EmptyLinesCheck.hx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class EmptyLinesCheck extends LineCheckBase {
var start = 0;
var end = 0;

for (i in 0 ... checker.lines.length) {
for (i in 0...checker.lines.length) {
var line = checker.lines[i];
if (isMultineString(line)) continue;
if (~/^\s*$/.match(line)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class IndentationCharacterCheck extends LineCheckBase {
override function actualRun() {
var ignoreRE = new EReg(ignorePattern, "");
var re = (character == TAB) ? ~/^\t*(\S.*| \*.*)?$/ : ~/^ *(\S.*)?$/;
for (i in 0 ... checker.lines.length) {
for (i in 0...checker.lines.length) {
var line = checker.lines[i];
if (ignoreRE.match(line) || isLineSuppressed(i)) continue;
if (isMultineString(line)) continue;
Expand Down
232 changes: 232 additions & 0 deletions src/checkstyle/checks/whitespace/OperatorWhitespaceCheck.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package checkstyle.checks.whitespace;

import checkstyle.Checker.LinePos;
import checkstyle.token.TokenTree;
import checkstyle.utils.TokenTreeCheckUtils;
import haxeparser.Data;
import haxe.macro.Expr;

using checkstyle.utils.ArrayUtils;

@name("OperatorWhitespace")
@desc("Checks that whitespace is present or absent around a operators.")
class OperatorWhitespaceCheck extends Check {

// =, +=, -=, *=, /=, <<=, >>=, >>>=, &=, |=, ^=
public var assignOpPolicy:WhitespacePolicy;
// ++, --, !
public var unaryOpPolicy:WhitespaceUnaryPolicy;
// +, -, *, /, %
public var arithmeticOpPolicy:WhitespacePolicy;
// ==, !=, <, <=, >, >=
public var compareOpPolicy:WhitespacePolicy;
// ~, &, |, ^, <<, >>, >>>
public var bitwiseOpPolicy:WhitespacePolicy;
// &&, ||
public var boolOpPolicy:WhitespacePolicy;
// ...
public var intervalOpPolicy:WhitespacePolicy;

public function new() {
super(TOKEN);
assignOpPolicy = AROUND;
unaryOpPolicy = NONE;
arithmeticOpPolicy = AROUND;
compareOpPolicy = AROUND;
bitwiseOpPolicy = AROUND;
boolOpPolicy = AROUND;
intervalOpPolicy = NONE;

categories = [Category.STYLE, Category.CLARITY];
}

override function actualRun() {
var root:TokenTree = checker.getTokenTree();

checkAssignOps(root);
checkUnaryOps(root);
checkArithmeticOps(root);
checkCompareOps(root);
checkBitwiseOps(root);
checkBoolOps(root);
checkIntervalOps(root);
}

function checkAssignOps(root:TokenTree) {
if ((assignOpPolicy == null) || (assignOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Binop(OpAssign),
Binop(OpAssignOp(OpAdd)),
Binop(OpAssignOp(OpSub)),
Binop(OpAssignOp(OpMult)),
Binop(OpAssignOp(OpDiv)),
Binop(OpAssignOp(OpMod)),
Binop(OpAssignOp(OpShl)),
Binop(OpAssignOp(OpShr)),
Binop(OpAssignOp(OpUShr)),
Binop(OpAssignOp(OpOr)),
Binop(OpAssignOp(OpAnd)),
Binop(OpAssignOp(OpXor))
], ALL);

checkTokenList(tokens, assignOpPolicy);
}

function checkUnaryOps(root:TokenTree) {
if ((unaryOpPolicy == null) || (unaryOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Unop(OpNot),
Unop(OpIncrement),
Unop(OpDecrement)
], ALL);

for (token in tokens) {
if (isPosSuppressed(token.pos)) continue;
checkUnaryWhitespace(token, unaryOpPolicy);
}
}

function checkArithmeticOps(root:TokenTree) {
if ((arithmeticOpPolicy == null) || (arithmeticOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Binop(OpAdd),
Binop(OpSub),
Binop(OpMult),
Binop(OpDiv),
Binop(OpMod)
], ALL);
checkTokenList(tokens, arithmeticOpPolicy);
}

function checkCompareOps(root:TokenTree) {
if ((compareOpPolicy == null) || (compareOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Binop(OpGt),
Binop(OpLt),
Binop(OpGte),
Binop(OpLte),
Binop(OpEq),
Binop(OpNotEq)
], ALL);
checkTokenList(tokens, compareOpPolicy);
}

function checkBitwiseOps(root:TokenTree) {
if ((bitwiseOpPolicy == null) || (bitwiseOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Binop(OpAnd),
Binop(OpOr),
Binop(OpXor),
Binop(OpShl),
Binop(OpShr),
Binop(OpUShr)
], ALL);
checkTokenList(tokens, bitwiseOpPolicy);
}

function checkBoolOps(root:TokenTree) {
if ((boolOpPolicy == null) || (boolOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filter([
Binop(OpBoolAnd),
Binop(OpBoolOr)
], ALL);
checkTokenList(tokens, boolOpPolicy);
}

function checkIntervalOps(root:TokenTree) {
if ((intervalOpPolicy == null) || (intervalOpPolicy == IGNORE)) return;
var tokens:Array<TokenTree> = root.filterCallback(function(token:TokenTree, depth:Int):FilterResult {
if (token.tok == null) return GO_DEEPER;
return switch (token.tok) {
case Binop(OpInterval): FOUND_SKIP_SUBTREE;
case IntInterval(_): FOUND_SKIP_SUBTREE;
default: GO_DEEPER;
}
});
checkTokenList(tokens, intervalOpPolicy);
}

function checkTokenList(tokens:Array<TokenTree>, policy:WhitespacePolicy) {
for (token in tokens) {
if (isPosSuppressed(token.pos)) continue;
if (TokenTreeCheckUtils.isImportMult(token)) continue;
if (TokenTreeCheckUtils.isTypeParameter(token)) continue;
if (TokenTreeCheckUtils.filterOpSub(token)) continue;
checkWhitespace(token, policy);
}
}

function checkWhitespace(tok:TokenTree, policy:WhitespacePolicy) {
var linePos:LinePos = checker.getLinePos(tok.pos.min);
var tokLen:Int = TokenDefPrinter.print(tok.tok).length;
if (tok.tok.match(IntInterval(_))) {
linePos = checker.getLinePos(tok.pos.max - 3);
tokLen = 3;
}
var line:String = checker.lines[linePos.line];
var before:String = line.substr(0, linePos.ofs);
var after:String = line.substr(linePos.ofs + tokLen);

var whitespaceBefore:Bool = ~/^(.*\s|)$/.match(before);
var whitespaceAfter:Bool = ~/^(\s.*|)$/.match(after);

switch (policy) {
case BEFORE:
if (whitespaceBefore && !whitespaceAfter) return;
case AFTER:
if (!whitespaceBefore && whitespaceAfter) return;
case NONE:
if (!whitespaceBefore && !whitespaceAfter) return;
case AROUND:
if (whitespaceBefore && whitespaceAfter) return;
case IGNORE:
return;
default:
return;
}
logPos('OperatorWhitespace policy "$policy" violated by "${TokenDefPrinter.print(tok.tok)}"', tok.pos);
}

function checkUnaryWhitespace(tok:TokenTree, policy:WhitespaceUnaryPolicy) {
var linePos:LinePos = checker.getLinePos(tok.pos.min);
var tokLen:Int = TokenDefPrinter.print(tok.tok).length;
var line:String = checker.lines[linePos.line];
var before:String = line.substr(0, linePos.ofs);
var after:String = line.substr(linePos.ofs + tokLen);

var whitespaceBefore:Bool = ~/^(.*\s|)$/.match(before);
var whitespaceAfter:Bool = ~/^(\s.*|)$/.match(after);

var leftSide:Bool = TokenTreeCheckUtils.isUnaryLeftSided(tok);

switch (policy) {
case INNER:
if (leftSide && whitespaceAfter) return;
if (!leftSide && whitespaceBefore) return;
case NONE:
if (leftSide && !whitespaceAfter) return;
if (!leftSide && !whitespaceBefore) return;
case IGNORE:
return;
default:
return;
}
logPos('OperatorWhitespace policy "$policy" violated by "${TokenDefPrinter.print(tok.tok)}"', tok.pos);
}
}

@:enum
abstract WhitespacePolicy(String) {
var BEFORE = "before";
var AFTER = "after";
var AROUND = "around";
var NONE = "none";
var IGNORE = "ignore";
}

@:enum
abstract WhitespaceUnaryPolicy(String) {
var INNER = "inner";
var NONE = "none";
var IGNORE = "ignore";
}
2 changes: 1 addition & 1 deletion src/checkstyle/checks/whitespace/TabForAligningCheck.hx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TabForAligningCheck extends LineCheckBase {
override function actualRun() {
var ignoreRE = new EReg(ignorePattern, "");
var re = ~/^\s*\S[^\t]*\t/;
for (i in 0 ... checker.lines.length) {
for (i in 0...checker.lines.length) {
var line = checker.lines[i];
if (ignoreRE.match(line)) continue;
if (isMultineString(line)) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class TrailingWhitespaceCheck extends LineCheckBase {

override function actualRun() {
var re = ~/\s+$/;
for (i in 0 ... checker.lines.length) {
for (i in 0...checker.lines.length) {
var line = checker.lines[i];
if (isMultineString(line)) continue;
if (re.match(line)) log("Trailing whitespace", i + 1, line.length);
Expand Down
11 changes: 11 additions & 0 deletions src/checkstyle/utils/TokenTreeCheckUtils.hx
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,15 @@ class TokenTreeCheckUtils {
default: false;
}
}

public static function isUnaryLeftSided(tok:TokenTree):Bool {
var child:TokenTree = tok.getFirstChild();

if (child == null) return false;
return switch (child.tok) {
case Const(_): true;
case POpen: true;
default: false;
}
}
}
2 changes: 1 addition & 1 deletion test/TestMain.hx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class TestMain {
for (cls in classes) {
var coverageData = [null];
var results:CoverageResult = cls.getResults();
for (i in 1 ... results.l) coverageData[i] = 1;
for (i in 1...results.l) coverageData[i] = 1;
var c = cls.name.replace(".", "/") + ".hx";

var missingStatements:Array<Statement> = cls.getMissingStatements();
Expand Down
Loading

0 comments on commit 9cb3ce2

Please sign in to comment.