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+ }
0 commit comments