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 javax.annotation.Nullable;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
29 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 @StatelessCheck
50 public class GoogleRightCurlyCheck extends AbstractCheck {
51
52
53
54
55
56 public static final String MSG_KEY_LINE_ALONE = "line.alone";
57
58
59
60
61
62 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
63
64
65
66
67
68 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
69
70
71
72
73
74 public static final String MSG_KEY_CONCISE_BLOCK = "empty.block.concise";
75
76
77
78
79
80 public static final String MSG_KEY_LINE_SAME = "line.same";
81
82
83
84
85 public GoogleRightCurlyCheck() {
86
87 }
88
89 @Override
90 public int[] getDefaultTokens() {
91 return getRequiredTokens();
92 }
93
94 @Override
95 public int[] getAcceptableTokens() {
96 return new int[] {
97 TokenTypes.LITERAL_IF,
98 TokenTypes.LITERAL_ELSE,
99 TokenTypes.LITERAL_TRY,
100 TokenTypes.LITERAL_CATCH,
101 TokenTypes.LITERAL_FINALLY,
102 TokenTypes.LITERAL_DO,
103 TokenTypes.CLASS_DEF,
104 TokenTypes.INTERFACE_DEF,
105 TokenTypes.RECORD_DEF,
106 TokenTypes.ANNOTATION_DEF,
107 TokenTypes.ENUM_DEF,
108 TokenTypes.METHOD_DEF,
109 TokenTypes.CTOR_DEF,
110 TokenTypes.COMPACT_CTOR_DEF,
111 TokenTypes.LITERAL_FOR,
112 TokenTypes.LITERAL_WHILE,
113 TokenTypes.LITERAL_SWITCH,
114 TokenTypes.LITERAL_CASE,
115 TokenTypes.LITERAL_DEFAULT,
116 TokenTypes.STATIC_INIT,
117 TokenTypes.INSTANCE_INIT,
118 TokenTypes.LITERAL_SYNCHRONIZED,
119 };
120 }
121
122 @Override
123 public int[] getRequiredTokens() {
124 return getAcceptableTokens();
125 }
126
127 @Override
128 public boolean isCommentNodesRequired() {
129 return true;
130 }
131
132 @Override
133 public void visitToken(DetailAST ast) {
134 DetailAST rightCurly = null;
135 switch (ast.getType()) {
136 case TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF,
137 TokenTypes.ANNOTATION_DEF, TokenTypes.RECORD_DEF, TokenTypes.ENUM_DEF -> {
138 final DetailAST child =
139 NullUtil.notNull(ast.findFirstToken(TokenTypes.OBJBLOCK));
140 rightCurly = NullUtil.notNull(child.findFirstToken(TokenTypes.RCURLY));
141 }
142
143 case TokenTypes.LITERAL_SWITCH -> rightCurly = ast.getLastChild();
144
145 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> handleCaseAndDefault(ast);
146
147 default -> {
148 final DetailAST child = ast.findFirstToken(TokenTypes.SLIST);
149 if (child != null) {
150 rightCurly = child.getLastChild();
151 }
152 }
153 }
154 if (rightCurly != null) {
155 checkRightBrace(ast, rightCurly);
156 }
157 }
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 private void handleCaseAndDefault(DetailAST ast) {
173 DetailAST startToken = ast;
174 if (ast.getParent().getType() == TokenTypes.CASE_GROUP) {
175 final DetailAST nextSibling = startToken.getNextSibling();
176 if (nextSibling != null) {
177 startToken = nextSibling.findFirstToken(TokenTypes.SLIST);
178 }
179 }
180 for (DetailAST current = startToken; current != null;
181 current = current.getNextSibling()) {
182 if (current.getType() == TokenTypes.SLIST) {
183 final DetailAST rightBrace = NullUtil.notNull(current.getLastChild());
184 checkRightBrace(ast, rightBrace);
185 }
186 }
187 }
188
189
190
191
192
193
194
195 private void logViolations(String message, DetailAST brace) {
196 log(brace, message, brace.getText(), brace.getColumnNo() + 1);
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210
211 private void checkRightBrace(DetailAST currentBlock, DetailAST brace) {
212 final DetailAST nextToken = getNextToken(brace);
213 final boolean hasContentAround = contentAround(brace, nextToken);
214 if (nextToken != null && isPartOfMultiBlock(currentBlock, nextToken)) {
215 checkMultiBlockStatement(currentBlock, brace, nextToken);
216 }
217 else if (currentBlock.getParent().getType() == TokenTypes.LITERAL_ELSE
218 || TokenUtil.isOfType(currentBlock, TokenTypes.LITERAL_ELSE,
219 TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_FINALLY)) {
220 if (hasContentAround) {
221 logViolations(MSG_KEY_LINE_ALONE, brace);
222 }
223 }
224 else if (isEmpty(brace)) {
225 verifyEmptyBlock(brace, nextToken);
226 }
227 else if (hasContentAround) {
228 logViolations(MSG_KEY_LINE_ALONE, brace);
229 }
230 }
231
232
233
234
235
236
237
238
239
240 private void checkMultiBlockStatement(DetailAST currentBlock, DetailAST brace,
241 DetailAST nextBlock) {
242 if (TokenUtil.areOnSameLine(brace, nextBlock)) {
243 if (hasContentOnLeftSide(brace) && !(currentBlock.getType() == TokenTypes.LITERAL_DO
244 && isEmpty(brace))) {
245 logViolations(MSG_KEY_LINE_BREAK_BEFORE, brace);
246 }
247 }
248 else {
249 logViolations(MSG_KEY_LINE_SAME, brace);
250 }
251 }
252
253
254
255
256
257
258
259 private void verifyEmptyBlock(DetailAST brace, @Nullable DetailAST nextToken) {
260 if (isNotConcise(brace)) {
261 logViolations(MSG_KEY_CONCISE_BLOCK, brace);
262 }
263 else if (nextToken != null
264 && hasContentOnRightSide(brace, nextToken)) {
265 logViolations(MSG_KEY_LINE_BREAK_AFTER, brace);
266 }
267 }
268
269
270
271
272
273
274
275
276 private static boolean contentAround(DetailAST brace, @Nullable DetailAST nextToken) {
277 return nextToken != null
278 && hasContentOnRightSide(brace, nextToken)
279 || hasContentOnLeftSide(brace);
280 }
281
282
283
284
285
286
287
288
289
290
291
292 private static boolean isPartOfMultiBlock(DetailAST currentBlock, DetailAST nextBlock) {
293 final int nextBlockType = nextBlock.getType();
294 return switch (currentBlock.getType()) {
295 case TokenTypes.LITERAL_IF ->
296 nextBlockType == TokenTypes.LITERAL_ELSE;
297 case TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_CATCH ->
298 nextBlockType == TokenTypes.LITERAL_CATCH
299 || nextBlockType == TokenTypes.LITERAL_FINALLY;
300 case TokenTypes.LITERAL_DO -> true;
301 default -> false;
302 };
303 }
304
305
306
307
308
309
310
311 private static boolean hasContentOnLeftSide(DetailAST brace) {
312 DetailAST previousToken = brace.getPreviousSibling();
313 if (previousToken == null) {
314 previousToken = brace.getParent();
315 }
316 if (previousToken.getType() != TokenTypes.SLIST) {
317 while (previousToken.hasChildren()) {
318 previousToken = previousToken.getLastChild();
319 }
320 }
321
322 return TokenUtil.areOnSameLine(brace, previousToken)
323 && !TokenUtil.isOfType(brace.getPreviousSibling(),
324 TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.COMMA);
325 }
326
327
328
329
330
331
332
333
334
335
336 private static boolean hasContentOnRightSide(DetailAST brace, DetailAST nextToken) {
337 final boolean nextIsValid = TokenUtil.isOfType(nextToken.getPreviousSibling(),
338 TokenTypes.EXPR, TokenTypes.VARIABLE_DEF, TokenTypes.ELIST, TokenTypes.LAMBDA)
339 || TokenUtil.isCommentType(nextToken.getType());
340 return !nextIsValid && TokenUtil.areOnSameLine(brace, nextToken);
341 }
342
343
344
345
346
347
348
349 private static boolean isEmpty(DetailAST brace) {
350 final DetailAST previousSibling = brace.getPreviousSibling();
351 return previousSibling == null || previousSibling.getType() == TokenTypes.LCURLY;
352 }
353
354
355
356
357
358
359
360 private static boolean isNotConcise(DetailAST brace) {
361 final DetailAST lcurly = brace.getParent();
362 return lcurly.getLineNo() == brace.getLineNo()
363 && lcurly.getColumnNo() + 1 != brace.getColumnNo();
364 }
365
366
367
368
369
370
371
372 @Nullable
373 private static DetailAST getNextToken(DetailAST node) {
374 DetailAST current = node;
375 DetailAST nextToken = null;
376 while (current != null && nextToken == null) {
377 nextToken = current.getNextSibling();
378 current = current.getParent();
379 }
380 return nextToken;
381 }
382
383 }