1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.blocks;
21
22 import java.util.Locale;
23
24 import javax.annotation.Nullable;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 @StatelessCheck
125 public class LeftCurlyCheck
126 extends AbstractCheck {
127
128
129
130
131
132 public static final String MSG_KEY_LINE_NEW = "line.new";
133
134
135
136
137
138 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
139
140
141
142
143
144 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
145
146
147 private static final String OPEN_CURLY_BRACE = "{";
148
149
150 private boolean ignoreEnums = true;
151
152
153
154
155 private LeftCurlyOption option = LeftCurlyOption.EOL;
156
157
158
159
160
161
162
163
164 public void setOption(String optionStr) {
165 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
166 }
167
168
169
170
171
172
173
174 public void setIgnoreEnums(boolean ignoreEnums) {
175 this.ignoreEnums = ignoreEnums;
176 }
177
178 @Override
179 public int[] getDefaultTokens() {
180 return getAcceptableTokens();
181 }
182
183 @Override
184 public int[] getAcceptableTokens() {
185 return new int[] {
186 TokenTypes.ANNOTATION_DEF,
187 TokenTypes.CLASS_DEF,
188 TokenTypes.CTOR_DEF,
189 TokenTypes.ENUM_CONSTANT_DEF,
190 TokenTypes.ENUM_DEF,
191 TokenTypes.INTERFACE_DEF,
192 TokenTypes.LAMBDA,
193 TokenTypes.LITERAL_CASE,
194 TokenTypes.LITERAL_CATCH,
195 TokenTypes.LITERAL_DEFAULT,
196 TokenTypes.LITERAL_DO,
197 TokenTypes.LITERAL_ELSE,
198 TokenTypes.LITERAL_FINALLY,
199 TokenTypes.LITERAL_FOR,
200 TokenTypes.LITERAL_IF,
201 TokenTypes.LITERAL_SWITCH,
202 TokenTypes.LITERAL_SYNCHRONIZED,
203 TokenTypes.LITERAL_TRY,
204 TokenTypes.LITERAL_WHILE,
205 TokenTypes.METHOD_DEF,
206 TokenTypes.OBJBLOCK,
207 TokenTypes.STATIC_INIT,
208 TokenTypes.RECORD_DEF,
209 TokenTypes.COMPACT_CTOR_DEF,
210 };
211 }
212
213 @Override
214 public int[] getRequiredTokens() {
215 return CommonUtil.EMPTY_INT_ARRAY;
216 }
217
218
219
220
221
222
223
224
225
226
227 @Override
228 public void visitToken(DetailAST ast) {
229 final DetailAST startToken;
230 final DetailAST brace;
231
232 switch (ast.getType()) {
233 case TokenTypes.CTOR_DEF:
234 case TokenTypes.METHOD_DEF:
235 case TokenTypes.COMPACT_CTOR_DEF:
236 startToken = skipModifierAnnotations(ast);
237 brace = ast.findFirstToken(TokenTypes.SLIST);
238 break;
239 case TokenTypes.INTERFACE_DEF:
240 case TokenTypes.CLASS_DEF:
241 case TokenTypes.ANNOTATION_DEF:
242 case TokenTypes.ENUM_DEF:
243 case TokenTypes.ENUM_CONSTANT_DEF:
244 case TokenTypes.RECORD_DEF:
245 startToken = skipModifierAnnotations(ast);
246 brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
247 break;
248 case TokenTypes.LITERAL_WHILE:
249 case TokenTypes.LITERAL_CATCH:
250 case TokenTypes.LITERAL_SYNCHRONIZED:
251 case TokenTypes.LITERAL_FOR:
252 case TokenTypes.LITERAL_TRY:
253 case TokenTypes.LITERAL_FINALLY:
254 case TokenTypes.LITERAL_DO:
255 case TokenTypes.LITERAL_IF:
256 case TokenTypes.STATIC_INIT:
257 case TokenTypes.LAMBDA:
258 startToken = ast;
259 brace = ast.findFirstToken(TokenTypes.SLIST);
260 break;
261 case TokenTypes.LITERAL_ELSE:
262 startToken = ast;
263 brace = getBraceAsFirstChild(ast);
264 break;
265 case TokenTypes.LITERAL_CASE:
266 case TokenTypes.LITERAL_DEFAULT:
267 startToken = ast;
268 brace = getBraceFromSwitchMember(ast);
269 break;
270 default:
271
272
273
274
275
276 startToken = ast;
277 brace = ast.findFirstToken(TokenTypes.LCURLY);
278 break;
279 }
280
281 if (brace != null) {
282 verifyBrace(brace, startToken);
283 }
284 }
285
286
287
288
289
290
291
292
293 @Nullable
294 private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
295 final DetailAST brace;
296 final DetailAST parent = ast.getParent();
297 if (parent.getType() == TokenTypes.SWITCH_RULE) {
298 brace = parent.findFirstToken(TokenTypes.SLIST);
299 }
300 else {
301 brace = getBraceAsFirstChild(ast.getNextSibling());
302 }
303 return brace;
304 }
305
306
307
308
309
310
311
312
313 @Nullable
314 private static DetailAST getBraceAsFirstChild(DetailAST ast) {
315 DetailAST brace = null;
316 if (ast != null) {
317 final DetailAST candidate = ast.getFirstChild();
318 if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
319 brace = candidate;
320 }
321 }
322 return brace;
323 }
324
325
326
327
328
329
330
331 private static DetailAST skipModifierAnnotations(DetailAST ast) {
332 DetailAST resultNode = ast;
333 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
334
335 if (modifiers != null) {
336 final DetailAST lastAnnotation = findLastAnnotation(modifiers);
337
338 if (lastAnnotation != null) {
339 if (lastAnnotation.getNextSibling() == null) {
340 resultNode = modifiers.getNextSibling();
341 }
342 else {
343 resultNode = lastAnnotation.getNextSibling();
344 }
345 }
346 }
347 return resultNode;
348 }
349
350
351
352
353
354
355
356
357 private static DetailAST findLastAnnotation(DetailAST modifiers) {
358 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
359 while (annotation != null && annotation.getNextSibling() != null
360 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
361 annotation = annotation.getNextSibling();
362 }
363 return annotation;
364 }
365
366
367
368
369
370
371
372
373 private void verifyBrace(final DetailAST brace,
374 final DetailAST startToken) {
375 final String braceLine = getLine(brace.getLineNo() - 1);
376
377
378 if (braceLine.length() <= brace.getColumnNo() + 1
379 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
380 if (option == LeftCurlyOption.NL) {
381 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
382 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
383 }
384 }
385 else if (option == LeftCurlyOption.EOL) {
386 validateEol(brace, braceLine);
387 }
388 else if (!TokenUtil.areOnSameLine(startToken, brace)) {
389 validateNewLinePosition(brace, startToken, braceLine);
390 }
391 }
392 }
393
394
395
396
397
398
399
400 private void validateEol(DetailAST brace, String braceLine) {
401 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
402 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
403 }
404 if (!hasLineBreakAfter(brace)) {
405 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
406 }
407 }
408
409
410
411
412
413
414
415
416 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
417
418 if (startToken.getLineNo() + 1 == brace.getLineNo()) {
419 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
420 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
421 }
422 else {
423 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
424 }
425 }
426 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
427 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
428 }
429 }
430
431
432
433
434
435
436
437
438
439 private boolean hasLineBreakAfter(DetailAST leftCurly) {
440 DetailAST nextToken = null;
441 if (leftCurly.getType() == TokenTypes.SLIST) {
442 nextToken = leftCurly.getFirstChild();
443 }
444 else {
445 if (!ignoreEnums
446 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
447 nextToken = leftCurly.getNextSibling();
448 }
449 }
450 return nextToken == null
451 || nextToken.getType() == TokenTypes.RCURLY
452 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
453 }
454
455 }