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