Skip to content

Commit 0e5a1b9

Browse files
committed
Merge pull request #30 from AlexHaxe/RightCurly
added RightCurlyCheck
2 parents bdcc191 + feefb13 commit 0e5a1b9

File tree

7 files changed

+747
-1
lines changed

7 files changed

+747
-1
lines changed

checkstyle/Checker.hx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Checker {
5252
}
5353

5454
public function getLinePos(off:Int):LinePos {
55+
if (off == file.content.length) off--;
5556
for (i in 0...linesIdx.length) {
5657
if (linesIdx[i].l <= off && linesIdx[i].r >= off) {
5758
return { line:i, ofs: off - linesIdx[i].l };

checkstyle/checks/LeftCurlyCheck.hx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ class LeftCurlyCheck extends Check {
155155
if (cases.length > 0) {
156156
firstCase = cases[0].values[0];
157157
}
158+
for (c in cases) {
159+
checkBlocks(c.expr, isListWrapped(c.values));
160+
}
161+
checkBlocks(edef);
158162
if (firstCase == null) {
159163
checkLinesBetween(e.pos.min, e.pos.max, isWrapped(expr), e.pos);
160164
return;
@@ -189,13 +193,22 @@ class LeftCurlyCheck extends Check {
189193
(functionDef.indexOf('\r') >= 0);
190194
}
191195

196+
function isListWrapped(es:Array<Expr>):Bool {
197+
if (es == null) return false;
198+
if (es.length <= 0) return false;
199+
var posMin:Int = es[0].pos.min;
200+
var posMax:Int = es[es.length - 1].pos.max;
201+
return (checker.getLinePos(posMin).line != checker.getLinePos(posMax).line);
202+
}
203+
192204
function isWrapped(e:Expr):Bool {
193205
if (e == null) return false;
194206
return (checker.getLinePos(e.pos.min).line != checker.getLinePos(e.pos.max).line);
195207
}
196208

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

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

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

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package checkstyle.checks;
2+
3+
import checkstyle.Checker.LinePos;
4+
import checkstyle.LintMessage.SeverityLevel;
5+
import haxeparser.Data;
6+
import haxe.macro.Expr;
7+
8+
@name("RightCurly")
9+
@desc("Checks for placement of right curly braces")
10+
class RightCurlyCheck extends Check {
11+
12+
public static inline var CLASS_DEF:String = "CLASS_DEF";
13+
public static inline var ENUM_DEF:String = "ENUM_DEF";
14+
public static inline var ABSTRACT_DEF:String = "ABSTRACT_DEF";
15+
public static inline var TYPEDEF_DEF:String = "TYPEDEF_DEF";
16+
public static inline var INTERFACE_DEF:String = "INTERFACE_DEF";
17+
18+
public static inline var OBJECT_DECL:String = "OBJECT_DECL";
19+
public static inline var FUNCTION:String = "FUNCTION";
20+
public static inline var FOR:String = "FOR";
21+
public static inline var IF:String = "IF";
22+
public static inline var WHILE:String = "WHILE";
23+
public static inline var SWITCH:String = "SWITCH";
24+
public static inline var TRY:String = "TRY";
25+
public static inline var CATCH:String = "CATCH";
26+
27+
public static inline var SAME:String = "same";
28+
public static inline var ALONE:String = "alone";
29+
public static inline var ALONE_OR_SINGLELINE:String = "aloneorsingle";
30+
31+
var sameRegex:EReg;
32+
33+
public var tokens:Array<String>;
34+
public var option:String;
35+
36+
public function new() {
37+
super();
38+
tokens = [
39+
CLASS_DEF,
40+
ENUM_DEF,
41+
ABSTRACT_DEF,
42+
TYPEDEF_DEF,
43+
INTERFACE_DEF,
44+
OBJECT_DECL,
45+
FUNCTION,
46+
FOR,
47+
IF,
48+
WHILE,
49+
SWITCH,
50+
TRY,
51+
CATCH
52+
];
53+
option = ALONE_OR_SINGLELINE;
54+
55+
// only else and catch allowed on same line after a right curly
56+
sameRegex = ~/^\s*(else|catch)/;
57+
}
58+
59+
function hasToken(token:String):Bool {
60+
if (tokens.length == 0) return true;
61+
if (tokens.indexOf(token) > -1) return true;
62+
return false;
63+
}
64+
65+
override function actualRun() {
66+
walkDecl();
67+
walkFile();
68+
}
69+
70+
function walkDecl() {
71+
for (td in checker.ast.decls) {
72+
switch(td.decl) {
73+
case EClass(d):
74+
checkFields(d.data);
75+
if (d.flags.indexOf(HInterface) > -1 && !hasToken(INTERFACE_DEF)) return;
76+
if (d.flags.indexOf(HInterface) < 0 && !hasToken(CLASS_DEF)) return;
77+
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
78+
case EEnum(d):
79+
if (!hasToken(ENUM_DEF)) return;
80+
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
81+
case EAbstract(d):
82+
checkFields(d.data);
83+
if (!hasToken(ABSTRACT_DEF)) return;
84+
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
85+
case ETypedef (d):
86+
checkTypeDef(td);
87+
default:
88+
}
89+
}
90+
}
91+
92+
function checkFields(fields:Array<Field>) {
93+
for (field in fields) {
94+
if (isCheckSuppressed(field)) return;
95+
switch (field.kind) {
96+
case FFun(f):
97+
if (!hasToken(FUNCTION)) return;
98+
if (f.expr == null) return;
99+
checkBlocks(f.expr, isSingleLine (f.expr.pos.min, f.expr.pos.max));
100+
default:
101+
}
102+
}
103+
}
104+
105+
function checkTypeDef(td:TypeDecl) {
106+
var firstPos:Position = null;
107+
var maxPos:Int = td.pos.max;
108+
109+
ComplexTypeUtils.walkTypeDecl(td, function(t:ComplexType, name:String, pos:Position) {
110+
if (firstPos == null) {
111+
if (pos != td.pos) firstPos = pos;
112+
}
113+
if (pos.max > maxPos) maxPos = pos.max;
114+
if (!hasToken(OBJECT_DECL)) return;
115+
switch(t) {
116+
case TAnonymous(_):
117+
checkPos(pos, isSingleLine(pos.min, pos.max));
118+
default:
119+
}
120+
});
121+
if (firstPos == null) return;
122+
if (!hasToken(TYPEDEF_DEF)) return;
123+
// td.pos only holds pos info about the type itself
124+
// members only hold pos info about themself
125+
// so real pos.max is pos.max of last member + 1
126+
td.pos.max = maxPos + 1;
127+
checkPos(td.pos, isSingleLine (td.pos.min, td.pos.max));
128+
}
129+
130+
function walkFile() {
131+
ExprUtils.walkFile(checker.ast, function(e) {
132+
if (isPosSuppressed(e.pos)) return;
133+
switch(e.expr) {
134+
case EObjectDecl(fields):
135+
if (!hasToken(OBJECT_DECL)) return;
136+
if (fields.length <= 0) return;
137+
var lastExpr:Expr = fields[fields.length - 1].expr;
138+
checkBlocks(e, isSingleLine(e.pos.min, e.pos.max));
139+
case EFunction(_, f):
140+
if (!hasToken(FUNCTION)) return;
141+
checkBlocks(f.expr, isSingleLine(e.pos.min, f.expr.pos.max));
142+
case EFor(it, expr):
143+
if (!hasToken(FOR)) return;
144+
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
145+
case EIf(econd, eif, eelse):
146+
if (!hasToken(IF)) return;
147+
var singleLine:Bool;
148+
if (eelse != null) singleLine = isSingleLine(e.pos.min, eelse.pos.max);
149+
else singleLine = isSingleLine (e.pos.min, eif.pos.max);
150+
checkBlocks(eif, singleLine);
151+
if (eelse != null) checkBlocks(eelse, singleLine);
152+
case EWhile(econd, expr, _):
153+
if (!hasToken(WHILE)) return;
154+
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
155+
case ESwitch(expr, cases, edef):
156+
if (!hasToken(SWITCH)) return;
157+
checkPos(e.pos, isSingleLine(e.pos.min, e.pos.max));
158+
for (c in cases) {
159+
if (c.expr != null) checkBlocks(c.expr, isSingleLine(c.expr.pos.min, c.expr.pos.max));
160+
}
161+
if ((edef == null) || (edef.expr == null)) return;
162+
checkBlocks (edef, isSingleLine(edef.pos.min, edef.pos.max));
163+
case ETry(expr, catches):
164+
if (!hasToken(TRY)) return;
165+
checkBlocks(expr, isSingleLine(e.pos.min, expr.pos.max));
166+
for (ecatch in catches) {
167+
checkBlocks(ecatch.expr, isSingleLine(e.pos.min, ecatch.expr.pos.max));
168+
}
169+
default:
170+
}
171+
});
172+
}
173+
174+
function checkPos(pos:Position, singleLine:Bool) {
175+
var bracePos:Int = checker.file.content.lastIndexOf("}", pos.max);
176+
if (bracePos < 0 || bracePos < pos.min) return;
177+
178+
var linePos:Int = checker.getLinePos(pos.max).line;
179+
var line:String = checker.lines[linePos];
180+
checkRightCurly(line, singleLine, pos);
181+
}
182+
183+
function checkBlocks(e:Expr, singleLine:Bool) {
184+
if ((e == null) || (e.expr == null)) return;
185+
var bracePos:Int = checker.file.content.lastIndexOf("}", e.pos.max);
186+
if (bracePos < 0 || bracePos < e.pos.min) return;
187+
188+
switch(e.expr) {
189+
case EBlock(_), EObjectDecl(_):
190+
var linePos:Int = checker.getLinePos(e.pos.max).line;
191+
var line:String = checker.lines[linePos];
192+
checkRightCurly(line, singleLine, e.pos);
193+
default:
194+
}
195+
}
196+
197+
function isSingleLine(start:Int, end:Int):Bool {
198+
var startLine:Int = checker.getLinePos(start).line;
199+
var endLine:Int = checker.getLinePos(end).line;
200+
return startLine == endLine;
201+
}
202+
203+
function checkRightCurly(line:String, singleLine:Bool, pos:Position) {
204+
try {
205+
var linePos:LinePos = checker.getLinePos(pos.max);
206+
var afterCurly:String = checker.lines[linePos.line].substr(linePos.ofs);
207+
var needsSameOption:Bool = sameRegex.match(afterCurly);
208+
var shouldHaveSameOption:Bool = false;
209+
if (checker.lines.length > linePos.line + 1) {
210+
var nextLine:String = checker.lines[linePos.line + 1];
211+
shouldHaveSameOption = sameRegex.match(nextLine);
212+
}
213+
// adjust to show correct line number in log message
214+
pos.min = pos.max;
215+
216+
logErrorIf (singleLine && (option != ALONE_OR_SINGLELINE), 'Right curly should not be on same line as left curly', pos);
217+
if (singleLine) return;
218+
219+
var curlyAlone:Bool = ~/^\s*\}[\);\s]*(|\/\/.*)$/.match(line);
220+
logErrorIf (!curlyAlone && (option == ALONE_OR_SINGLELINE || option == ALONE), 'Right curly should be alone on a new line', pos);
221+
logErrorIf (curlyAlone && needsSameOption, 'Right curly should be alone on a new line', pos);
222+
logErrorIf (needsSameOption && (option != SAME), 'Right curly must not be on same line as following block', pos);
223+
logErrorIf (shouldHaveSameOption && (option == SAME), 'Right curly should be on same line as following block (e.g. "} else" or "} catch")', pos);
224+
}
225+
catch (e:String) {
226+
// one of the error messages fired -> do nothing
227+
}
228+
}
229+
230+
function logErrorIf(condition:Bool, msg:String, pos:Position) {
231+
if (condition) {
232+
logPos(msg, pos, Reflect.field(SeverityLevel, severity));
233+
throw "exit";
234+
}
235+
}
236+
}

resources/config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,29 @@
235235
"allowEmptyReturn": true
236236
}
237237
},
238+
{
239+
"type": "RightCurly",
240+
"props": {
241+
"severity": "WARNING",
242+
"option": "aloneorsingle",
243+
"tokens": [
244+
"CLASS_DEF",
245+
"ENUM_DEF",
246+
"ABSTRACT_DEF",
247+
"TYPEDEF_DEF",
248+
"CLASS_DEF",
249+
"INTERFACE_DEF",
250+
"OBJECT_DECL",
251+
"FUNCTION",
252+
"FOR",
253+
"IF",
254+
"WHILE",
255+
"SWITCH",
256+
"TRY",
257+
"CATCH"
258+
]
259+
}
260+
},
238261
{
239262
"type": "Spacing",
240263
"props": {

0 commit comments

Comments
 (0)