Skip to content

Commit 840eb70

Browse files
committed
Merge branch 'dev'
2 parents a3c8263 + a7ec229 commit 840eb70

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package checkstyle.checks;
2+
3+
import haxeparser.Data.TypeDecl;
4+
import haxeparser.Data.TypeDef;
5+
import haxeparser.Data.Definition;
6+
import haxeparser.Data.ClassFlag;
7+
import checkstyle.LintMessage.SeverityLevel;
8+
import haxe.macro.Expr;
9+
10+
using Lambda;
11+
12+
@name("CyclomaticComplexity")
13+
@desc("McCabe simplified cyclomatic complexity check")
14+
class CyclomaticComplexityCheck extends Check {
15+
16+
public var thresholds:Array<Threshold>;
17+
18+
override function _actualRun() {
19+
_checker.ast.decls.map(function(type:TypeDecl):Null<Definition<ClassFlag, Array<Field>>> {
20+
return switch (type.decl) {
21+
case TypeDef.EClass(definition): definition;
22+
default: null;
23+
}
24+
}).filter(function(definition):Bool {
25+
return definition != null;
26+
}).iter(checkFields);
27+
}
28+
29+
function checkFields(definition:Definition<ClassFlag, Array<Field>>) {
30+
definition.data.map(function(field:Field):Null<Target> {
31+
return switch (field.kind) {
32+
case FieldType.FFun(f): {name:field.name, expr:f.expr, pos:field.pos};
33+
default: null;
34+
}
35+
}).filter(function(f:Null<Target>):Bool {
36+
return f != null;
37+
}).iter(calculateComplexity);
38+
}
39+
40+
function calculateComplexity(method:Target) {
41+
var complexity:Int = 1 + evaluateExpr(method.expr);
42+
43+
var risk:Null<Threshold> = thresholds.filter(function(t:Threshold):Bool {
44+
return complexity >= t.complexity;
45+
}).pop();
46+
47+
if (risk != null) {
48+
notify(method, complexity, risk);
49+
}
50+
}
51+
52+
// This would not pass the cyclomatic complexity test.
53+
function evaluateExpr(e:Expr):Int {
54+
if (e == null) {
55+
return 0;
56+
}
57+
return switch(e.expr) {
58+
case ExprDef.EArray(e1, e2) : evaluateExpr(e1) + evaluateExpr(e2);
59+
case ExprDef.EBinop(op, e1, e2) : evaluateExpr(e1) + evaluateExpr(e2) + switch(op) {
60+
case Binop.OpBoolAnd : 1;
61+
case Binop.OpBoolOr : 1;
62+
default : 0;
63+
};
64+
case ExprDef.EParenthesis(e) : evaluateExpr(e);
65+
case ExprDef.EObjectDecl(fields) :
66+
fields.map(function(f):Expr {
67+
return f.expr;
68+
}).fold(function(e:Expr, total:Int):Int {
69+
return total + evaluateExpr(e);
70+
}, 0);
71+
case ExprDef.EArrayDecl(values) :
72+
values.fold(function(e:Expr, total:Int):Int {
73+
return total + evaluateExpr(e);
74+
}, 0);
75+
case ExprDef.EBlock(exprs) :
76+
exprs.fold(function(e:Expr, total:Int):Int {
77+
return total + evaluateExpr(e);
78+
}, 0);
79+
case ExprDef.EFor(it, e) : 1 + evaluateExpr(it) + evaluateExpr(e);
80+
case ExprDef.EIn(e1, e2) : evaluateExpr(e1) + evaluateExpr(e2);
81+
case ExprDef.EIf(econd, eif, eelse) : 1 + evaluateExpr(econd) + evaluateExpr(eif) + evaluateExpr(eelse);
82+
case ExprDef.EWhile(econd, e, _) : 1 + evaluateExpr(econd) + evaluateExpr(e);
83+
case ExprDef.ESwitch(e, cases, def) :
84+
evaluateExpr(def) + cases.map(function(c:Case):Expr {
85+
return c.expr;
86+
}).fold(function(e:Expr, total:Int):Int {
87+
return total + 1 + evaluateExpr(e);
88+
}, 0);
89+
case ExprDef.ETry(e, catches) :
90+
catches.map(function(c:Catch):Expr {
91+
return c.expr;
92+
}).fold(function(e:Expr, total:Int):Int {
93+
return total + 1 + evaluateExpr(e);
94+
}, 0);
95+
case ExprDef.EReturn(e) : (e != null) ? evaluateExpr(e) : 0;
96+
case ExprDef.EUntyped(e) : evaluateExpr(e);
97+
case ExprDef.EThrow(e) : evaluateExpr(e);
98+
case ExprDef.ECast(e, _) : evaluateExpr(e);
99+
case ExprDef.EDisplay(e, _) : evaluateExpr(e);
100+
case ExprDef.ETernary(econd, eif, eelse) : 1 + evaluateExpr(econd) + evaluateExpr(eif) + evaluateExpr(eelse);
101+
case ExprDef.ECheckType(e, _) : evaluateExpr(e);
102+
default: 0;
103+
}
104+
}
105+
106+
function notify(method:Target, complexity:Int, risk:Threshold) {
107+
logPos('Function \"${method.name}\" is too complex (score: $complexity).', method.pos, Reflect.field(SeverityLevel, risk.severity));
108+
}
109+
}
110+
111+
typedef Target = {
112+
var name:String;
113+
var expr:Expr;
114+
var pos:Position;
115+
}
116+
typedef Threshold = {
117+
var severity:String;
118+
var complexity:Int;
119+
}

resources/config.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@
1919
"emptyBlockCheck": false
2020
}
2121
},
22+
{
23+
"type": "CyclomaticComplexity",
24+
"props": {
25+
"thresholds": [
26+
{
27+
"severity" : "WARNING",
28+
"complexity" : 11
29+
},
30+
{
31+
"severity" : "ERROR",
32+
"complexity" : 21
33+
}
34+
]
35+
}
36+
},
2237
{
2338
"type": "EmptyLines",
2439
"props": {

run.n

3.12 KB
Binary file not shown.

0 commit comments

Comments
 (0)