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
125
126
127 @StatelessCheck
128 public class LeftCurlyCheck
129 extends AbstractCheck {
130
131
132
133
134
135 public static final String MSG_KEY_LINE_NEW = "line.new";
136
137
138
139
140
141 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
142
143
144
145
146
147 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
148
149
150 private static final String OPEN_CURLY_BRACE = "{";
151
152
153 private boolean ignoreEnums = true;
154
155
156
157
158 private LeftCurlyOption option = LeftCurlyOption.EOL;
159
160
161
162
163
164
165
166
167 public void setOption(String optionStr) {
168 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
169 }
170
171
172
173
174
175
176
177 public void setIgnoreEnums(boolean ignoreEnums) {
178 this.ignoreEnums = ignoreEnums;
179 }
180
181 @Override
182 public int[] getDefaultTokens() {
183 return getAcceptableTokens();
184 }
185
186 @Override
187 public int[] getAcceptableTokens() {
188 return new int[] {
189 TokenTypes.ANNOTATION_DEF,
190 TokenTypes.CLASS_DEF,
191 TokenTypes.CTOR_DEF,
192 TokenTypes.ENUM_CONSTANT_DEF,
193 TokenTypes.ENUM_DEF,
194 TokenTypes.INTERFACE_DEF,
195 TokenTypes.LAMBDA,
196 TokenTypes.LITERAL_CASE,
197 TokenTypes.LITERAL_CATCH,
198 TokenTypes.LITERAL_DEFAULT,
199 TokenTypes.LITERAL_DO,
200 TokenTypes.LITERAL_ELSE,
201 TokenTypes.LITERAL_FINALLY,
202 TokenTypes.LITERAL_FOR,
203 TokenTypes.LITERAL_IF,
204 TokenTypes.LITERAL_SWITCH,
205 TokenTypes.LITERAL_SYNCHRONIZED,
206 TokenTypes.LITERAL_TRY,
207 TokenTypes.LITERAL_WHILE,
208 TokenTypes.METHOD_DEF,
209 TokenTypes.OBJBLOCK,
210 TokenTypes.STATIC_INIT,
211 TokenTypes.RECORD_DEF,
212 TokenTypes.COMPACT_CTOR_DEF,
213 };
214 }
215
216 @Override
217 public int[] getRequiredTokens() {
218 return CommonUtil.EMPTY_INT_ARRAY;
219 }
220
221
222
223
224
225
226
227
228
229
230 @Override
231 public void visitToken(DetailAST ast) {
232 final DetailAST startToken;
233 final DetailAST brace = switch (ast.getType()) {
234 case TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.COMPACT_CTOR_DEF -> {
235 startToken = skipModifierAnnotations(ast);
236 yield ast.findFirstToken(TokenTypes.SLIST);
237 }
238 case TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ANNOTATION_DEF,
239 TokenTypes.ENUM_DEF, TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.RECORD_DEF -> {
240 startToken = skipModifierAnnotations(ast);
241 yield ast.findFirstToken(TokenTypes.OBJBLOCK);
242 }
243 case TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_CATCH,
244 TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_TRY,
245 TokenTypes.LITERAL_FINALLY, TokenTypes.LITERAL_DO,
246 TokenTypes.LITERAL_IF, TokenTypes.STATIC_INIT, TokenTypes.LAMBDA -> {
247 startToken = ast;
248 yield ast.findFirstToken(TokenTypes.SLIST);
249 }
250 case TokenTypes.LITERAL_ELSE -> {
251 startToken = ast;
252 yield getBraceAsFirstChild(ast);
253 }
254 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> {
255 startToken = ast;
256 yield getBraceFromSwitchMember(ast);
257 }
258 default -> {
259
260
261
262
263
264 startToken = ast;
265 yield ast.findFirstToken(TokenTypes.LCURLY);
266 }
267 };
268
269 if (brace != null) {
270 verifyBrace(brace, startToken);
271 }
272 }
273
274
275
276
277
278
279
280
281 @Nullable
282 private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
283 final DetailAST brace;
284 final DetailAST parent = ast.getParent();
285 if (parent.getType() == TokenTypes.SWITCH_RULE) {
286 brace = parent.findFirstToken(TokenTypes.SLIST);
287 }
288 else {
289 brace = getBraceAsFirstChild(ast.getNextSibling());
290 }
291 return brace;
292 }
293
294
295
296
297
298
299
300
301 @Nullable
302 private static DetailAST getBraceAsFirstChild(DetailAST ast) {
303 DetailAST brace = null;
304 if (ast != null) {
305 final DetailAST candidate = ast.getFirstChild();
306 if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
307 brace = candidate;
308 }
309 }
310 return brace;
311 }
312
313
314
315
316
317
318
319 private static DetailAST skipModifierAnnotations(DetailAST ast) {
320 DetailAST resultNode = ast;
321 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
322
323 if (modifiers != null) {
324 final DetailAST lastAnnotation = findLastAnnotation(modifiers);
325
326 if (lastAnnotation != null) {
327 if (lastAnnotation.getNextSibling() == null) {
328 resultNode = modifiers.getNextSibling();
329 }
330 else {
331 resultNode = lastAnnotation.getNextSibling();
332 }
333 }
334 }
335 return resultNode;
336 }
337
338
339
340
341
342
343
344
345 private static DetailAST findLastAnnotation(DetailAST modifiers) {
346 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
347 while (annotation != null && annotation.getNextSibling() != null
348 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
349 annotation = annotation.getNextSibling();
350 }
351 return annotation;
352 }
353
354
355
356
357
358
359
360
361 private void verifyBrace(final DetailAST brace,
362 final DetailAST startToken) {
363 final String braceLine = getLine(brace.getLineNo() - 1);
364
365
366 if (braceLine.length() <= brace.getColumnNo() + 1
367 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
368 if (option == LeftCurlyOption.NL) {
369 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
370 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
371 }
372 }
373 else if (option == LeftCurlyOption.EOL) {
374 validateEol(brace, braceLine);
375 }
376 else if (!TokenUtil.areOnSameLine(startToken, brace)) {
377 validateNewLinePosition(brace, startToken, braceLine);
378 }
379 }
380 }
381
382
383
384
385
386
387
388 private void validateEol(DetailAST brace, String braceLine) {
389 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
390 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
391 }
392 if (!hasLineBreakAfter(brace)) {
393 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
394 }
395 }
396
397
398
399
400
401
402
403
404 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
405
406 if (startToken.getLineNo() + 1 == brace.getLineNo()) {
407 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
408 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
409 }
410 else {
411 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
412 }
413 }
414 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
415 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
416 }
417 }
418
419
420
421
422
423
424
425
426
427 private boolean hasLineBreakAfter(DetailAST leftCurly) {
428 DetailAST nextToken = null;
429 if (leftCurly.getType() == TokenTypes.SLIST) {
430 nextToken = leftCurly.getFirstChild();
431 }
432 else {
433 if (!ignoreEnums
434 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
435 nextToken = leftCurly.getNextSibling();
436 }
437 }
438 return nextToken == null
439 || nextToken.getType() == TokenTypes.RCURLY
440 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
441 }
442
443 }