Skip to content

Commit

Permalink
Merge pull request #30 from AlexHaxe/RightCurly
Browse files Browse the repository at this point in the history
added RightCurlyCheck
  • Loading branch information
AlexHaxe committed Nov 12, 2015
2 parents bdcc191 + feefb13 commit 0e5a1b9
Show file tree
Hide file tree
Showing 7 changed files with 747 additions and 1 deletion.
1 change: 1 addition & 0 deletions checkstyle/Checker.hx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Checker {
}

public function getLinePos(off:Int):LinePos {
if (off == file.content.length) off--;
for (i in 0...linesIdx.length) {
if (linesIdx[i].l <= off && linesIdx[i].r >= off) {
return { line:i, ofs: off - linesIdx[i].l };
Expand Down
14 changes: 13 additions & 1 deletion checkstyle/checks/LeftCurlyCheck.hx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ class LeftCurlyCheck extends Check {
if (cases.length > 0) {
firstCase = cases[0].values[0];
}
for (c in cases) {
checkBlocks(c.expr, isListWrapped(c.values));
}
checkBlocks(edef);
if (firstCase == null) {
checkLinesBetween(e.pos.min, e.pos.max, isWrapped(expr), e.pos);
return;
Expand Down Expand Up @@ -189,13 +193,22 @@ class LeftCurlyCheck extends Check {
(functionDef.indexOf('\r') >= 0);
}

function isListWrapped(es:Array<Expr>):Bool {
if (es == null) return false;
if (es.length <= 0) return false;
var posMin:Int = es[0].pos.min;
var posMax:Int = es[es.length - 1].pos.max;
return (checker.getLinePos(posMin).line != checker.getLinePos(posMax).line);
}

function isWrapped(e:Expr):Bool {
if (e == null) return false;
return (checker.getLinePos(e.pos.min).line != checker.getLinePos(e.pos.max).line);
}

function checkBlocks(e:Expr, wrapped:Bool = false) {
if ((e == null) || (e.expr == null)) return;
if (checker.file.content.charAt(e.pos.min) != "{") return;

switch(e.expr) {
case EBlock(_):
Expand All @@ -216,7 +229,6 @@ class LeftCurlyCheck extends Check {
checkLeftCurly(line, wrapped, pos);
}

@SuppressWarnings("checkstyle:BlockFormat")
function checkLeftCurly(line:String, wrapped:Bool = false, pos:Position) {
var lineLength:Int = line.length;

Expand Down
236 changes: 236 additions & 0 deletions checkstyle/checks/RightCurlyCheck.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package checkstyle.checks;

import checkstyle.Checker.LinePos;
import checkstyle.LintMessage.SeverityLevel;
import haxeparser.Data;
import haxe.macro.Expr;

@name("RightCurly")
@desc("Checks for placement of right curly braces")
class RightCurlyCheck extends Check {

public static inline var CLASS_DEF:String = "CLASS_DEF";
public static inline var ENUM_DEF:String = "ENUM_DEF";
public static inline var ABSTRACT_DEF:String = "ABSTRACT_DEF";
public static inline var TYPEDEF_DEF:String = "TYPEDEF_DEF";
public static inline var INTERFACE_DEF:String = "INTERFACE_DEF";

public static inline var OBJECT_DECL:String = "OBJECT_DECL";
public static inline var FUNCTION:String = "FUNCTION";
public static inline var FOR:String = "FOR";
public static inline var IF:String = "IF";
public static inline var WHILE:String = "WHILE";
public static inline var SWITCH:String = "SWITCH";
public static inline var TRY:String = "TRY";
public static inline var CATCH:String = "CATCH";

public static inline var SAME:String = "same";
public static inline var ALONE:String = "alone";
public static inline var ALONE_OR_SINGLELINE:String = "aloneorsingle";

var sameRegex:EReg;

public var tokens:Array<String>;
public var option:String;

public function new() {
super();
tokens = [
CLASS_DEF,
ENUM_DEF,
ABSTRACT_DEF,
TYPEDEF_DEF,
INTERFACE_DEF,
OBJECT_DECL,
FUNCTION,
FOR,
IF,
WHILE,
SWITCH,
TRY,
CATCH
];
option = ALONE_OR_SINGLELINE;

// only else and catch allowed on same line after a right curly
sameRegex = ~/^\s*(else|catch)/;
}

function hasToken(token:String):Bool {
if (tokens.length == 0) return true;
if (tokens.indexOf(token) > -1) return true;
return false;
}

override function actualRun() {
walkDecl();
walkFile();
}

function walkDecl() {
for (td in checker.ast.decls) {
switch(td.decl) {
case EClass(d):
checkFields(d.data);
if (d.flags.indexOf(HInterface) > -1 && !hasToken(INTERFACE_DEF)) return;
if (d.flags.indexOf(HInterface) < 0 && !hasToken(CLASS_DEF)) return;
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
case EEnum(d):
if (!hasToken(ENUM_DEF)) return;
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
case EAbstract(d):
checkFields(d.data);
if (!hasToken(ABSTRACT_DEF)) return;
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
case ETypedef (d):
checkTypeDef(td);
default:
}
}
}

function checkFields(fields:Array<Field>) {
for (field in fields) {
if (isCheckSuppressed(field)) return;
switch (field.kind) {
case FFun(f):
if (!hasToken(FUNCTION)) return;
if (f.expr == null) return;
checkBlocks(f.expr, isSingleLine (f.expr.pos.min, f.expr.pos.max));
default:
}
}
}

function checkTypeDef(td:TypeDecl) {
var firstPos:Position = null;
var maxPos:Int = td.pos.max;

ComplexTypeUtils.walkTypeDecl(td, function(t:ComplexType, name:String, pos:Position) {
if (firstPos == null) {
if (pos != td.pos) firstPos = pos;
}
if (pos.max > maxPos) maxPos = pos.max;
if (!hasToken(OBJECT_DECL)) return;
switch(t) {
case TAnonymous(_):
checkPos(pos, isSingleLine(pos.min, pos.max));
default:
}
});
if (firstPos == null) return;
if (!hasToken(TYPEDEF_DEF)) return;
// td.pos only holds pos info about the type itself
// members only hold pos info about themself
// so real pos.max is pos.max of last member + 1
td.pos.max = maxPos + 1;
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
}

function walkFile() {
ExprUtils.walkFile(checker.ast, function(e) {
if (isPosSuppressed(e.pos)) return;
switch(e.expr) {
case EObjectDecl(fields):
if (!hasToken(OBJECT_DECL)) return;
if (fields.length <= 0) return;
var lastExpr:Expr = fields[fields.length - 1].expr;
checkBlocks(e, isSingleLine(e.pos.min, e.pos.max));
case EFunction(_, f):
if (!hasToken(FUNCTION)) return;
checkBlocks(f.expr, isSingleLine(e.pos.min, f.expr.pos.max));
case EFor(it, expr):
if (!hasToken(FOR)) return;
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
case EIf(econd, eif, eelse):
if (!hasToken(IF)) return;
var singleLine:Bool;
if (eelse != null) singleLine = isSingleLine(e.pos.min, eelse.pos.max);
else singleLine = isSingleLine (e.pos.min, eif.pos.max);
checkBlocks(eif, singleLine);
if (eelse != null) checkBlocks(eelse, singleLine);
case EWhile(econd, expr, _):
if (!hasToken(WHILE)) return;
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
case ESwitch(expr, cases, edef):
if (!hasToken(SWITCH)) return;
checkPos(e.pos, isSingleLine(e.pos.min, e.pos.max));
for (c in cases) {
if (c.expr != null) checkBlocks(c.expr, isSingleLine(c.expr.pos.min, c.expr.pos.max));
}
if ((edef == null) || (edef.expr == null)) return;
checkBlocks (edef, isSingleLine(edef.pos.min, edef.pos.max));
case ETry(expr, catches):
if (!hasToken(TRY)) return;
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
for (ecatch in catches) {
checkBlocks(ecatch.expr, isSingleLine(e.pos.min, ecatch.expr.pos.max));
}
default:
}
});
}

function checkPos(pos:Position, singleLine:Bool) {
var bracePos:Int = checker.file.content.lastIndexOf("}", pos.max);
if (bracePos < 0 || bracePos < pos.min) return;

var linePos:Int = checker.getLinePos(pos.max).line;
var line:String = checker.lines[linePos];
checkRightCurly(line, singleLine, pos);
}

function checkBlocks(e:Expr, singleLine:Bool) {
if ((e == null) || (e.expr == null)) return;
var bracePos:Int = checker.file.content.lastIndexOf("}", e.pos.max);
if (bracePos < 0 || bracePos < e.pos.min) return;

switch(e.expr) {
case EBlock(_), EObjectDecl(_):
var linePos:Int = checker.getLinePos(e.pos.max).line;
var line:String = checker.lines[linePos];
checkRightCurly(line, singleLine, e.pos);
default:
}
}

function isSingleLine(start:Int, end:Int):Bool {
var startLine:Int = checker.getLinePos(start).line;
var endLine:Int = checker.getLinePos(end).line;
return startLine == endLine;
}

function checkRightCurly(line:String, singleLine:Bool, pos:Position) {
try {
var linePos:LinePos = checker.getLinePos(pos.max);
var afterCurly:String = checker.lines[linePos.line].substr(linePos.ofs);
var needsSameOption:Bool = sameRegex.match(afterCurly);
var shouldHaveSameOption:Bool = false;
if (checker.lines.length > linePos.line + 1) {
var nextLine:String = checker.lines[linePos.line + 1];
shouldHaveSameOption = sameRegex.match(nextLine);
}
// adjust to show correct line number in log message
pos.min = pos.max;

logErrorIf (singleLine && (option != ALONE_OR_SINGLELINE), 'Right curly should not be on same line as left curly', pos);
if (singleLine) return;

var curlyAlone:Bool = ~/^\s*\}[\);\s]*(|\/\/.*)$/.match(line);
logErrorIf (!curlyAlone && (option == ALONE_OR_SINGLELINE || option == ALONE), 'Right curly should be alone on a new line', pos);
logErrorIf (curlyAlone && needsSameOption, 'Right curly should be alone on a new line', pos);
logErrorIf (needsSameOption && (option != SAME), 'Right curly must not be on same line as following block', pos);
logErrorIf (shouldHaveSameOption && (option == SAME), 'Right curly should be on same line as following block (e.g. "} else" or "} catch")', pos);
}
catch (e:String) {
// one of the error messages fired -> do nothing
}
}

function logErrorIf(condition:Bool, msg:String, pos:Position) {
if (condition) {
logPos(msg, pos, Reflect.field(SeverityLevel, severity));
throw "exit";
}
}
}
23 changes: 23 additions & 0 deletions resources/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,29 @@
"allowEmptyReturn": true
}
},
{
"type": "RightCurly",
"props": {
"severity": "WARNING",
"option": "aloneorsingle",
"tokens": [
"CLASS_DEF",
"ENUM_DEF",
"ABSTRACT_DEF",
"TYPEDEF_DEF",
"CLASS_DEF",
"INTERFACE_DEF",
"OBJECT_DECL",
"FUNCTION",
"FOR",
"IF",
"WHILE",
"SWITCH",
"TRY",
"CATCH"
]
}
},
{
"type": "Spacing",
"props": {
Expand Down
Loading

0 comments on commit 0e5a1b9

Please sign in to comment.