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