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