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.whitespace;
21
22 import com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
27
28
29
30
31
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 @StatelessCheck
88 public class WhitespaceAroundCheck extends AbstractCheck {
89
90
91
92
93
94 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
95
96
97
98
99
100 public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
101
102
103 private boolean allowEmptyConstructors;
104
105 private boolean allowEmptyMethods;
106
107 private boolean allowEmptyTypes;
108
109 private boolean allowEmptyLoops;
110
111 private boolean allowEmptyLambdas;
112
113 private boolean allowEmptyCatches;
114
115 private boolean allowEmptySwitchBlockStatements;
116
117
118
119
120
121 private boolean ignoreEnhancedForColon = true;
122
123 @Override
124 public int[] getDefaultTokens() {
125 return new int[] {
126 TokenTypes.ASSIGN,
127 TokenTypes.BAND,
128 TokenTypes.BAND_ASSIGN,
129 TokenTypes.BOR,
130 TokenTypes.BOR_ASSIGN,
131 TokenTypes.BSR,
132 TokenTypes.BSR_ASSIGN,
133 TokenTypes.BXOR,
134 TokenTypes.BXOR_ASSIGN,
135 TokenTypes.COLON,
136 TokenTypes.DIV,
137 TokenTypes.DIV_ASSIGN,
138 TokenTypes.DO_WHILE,
139 TokenTypes.EQUAL,
140 TokenTypes.GE,
141 TokenTypes.GT,
142 TokenTypes.LAMBDA,
143 TokenTypes.LAND,
144 TokenTypes.LCURLY,
145 TokenTypes.LE,
146 TokenTypes.LITERAL_CATCH,
147 TokenTypes.LITERAL_DO,
148 TokenTypes.LITERAL_ELSE,
149 TokenTypes.LITERAL_FINALLY,
150 TokenTypes.LITERAL_FOR,
151 TokenTypes.LITERAL_IF,
152 TokenTypes.LITERAL_RETURN,
153 TokenTypes.LITERAL_SWITCH,
154 TokenTypes.LITERAL_SYNCHRONIZED,
155 TokenTypes.LITERAL_TRY,
156 TokenTypes.LITERAL_WHILE,
157 TokenTypes.LOR,
158 TokenTypes.LT,
159 TokenTypes.MINUS,
160 TokenTypes.MINUS_ASSIGN,
161 TokenTypes.MOD,
162 TokenTypes.MOD_ASSIGN,
163 TokenTypes.NOT_EQUAL,
164 TokenTypes.PLUS,
165 TokenTypes.PLUS_ASSIGN,
166 TokenTypes.QUESTION,
167 TokenTypes.RCURLY,
168 TokenTypes.SL,
169 TokenTypes.SLIST,
170 TokenTypes.SL_ASSIGN,
171 TokenTypes.SR,
172 TokenTypes.SR_ASSIGN,
173 TokenTypes.STAR,
174 TokenTypes.STAR_ASSIGN,
175 TokenTypes.LITERAL_ASSERT,
176 TokenTypes.TYPE_EXTENSION_AND,
177 TokenTypes.LITERAL_WHEN,
178 };
179 }
180
181 @Override
182 public int[] getAcceptableTokens() {
183 return new int[] {
184 TokenTypes.ASSIGN,
185 TokenTypes.ARRAY_INIT,
186 TokenTypes.BAND,
187 TokenTypes.BAND_ASSIGN,
188 TokenTypes.BOR,
189 TokenTypes.BOR_ASSIGN,
190 TokenTypes.BSR,
191 TokenTypes.BSR_ASSIGN,
192 TokenTypes.BXOR,
193 TokenTypes.BXOR_ASSIGN,
194 TokenTypes.COLON,
195 TokenTypes.DIV,
196 TokenTypes.DIV_ASSIGN,
197 TokenTypes.DO_WHILE,
198 TokenTypes.EQUAL,
199 TokenTypes.GE,
200 TokenTypes.GT,
201 TokenTypes.LAMBDA,
202 TokenTypes.LAND,
203 TokenTypes.LCURLY,
204 TokenTypes.LE,
205 TokenTypes.LITERAL_CATCH,
206 TokenTypes.LITERAL_DO,
207 TokenTypes.LITERAL_ELSE,
208 TokenTypes.LITERAL_FINALLY,
209 TokenTypes.LITERAL_FOR,
210 TokenTypes.LITERAL_IF,
211 TokenTypes.LITERAL_RETURN,
212 TokenTypes.LITERAL_SWITCH,
213 TokenTypes.LITERAL_SYNCHRONIZED,
214 TokenTypes.LITERAL_TRY,
215 TokenTypes.LITERAL_WHILE,
216 TokenTypes.LOR,
217 TokenTypes.LT,
218 TokenTypes.MINUS,
219 TokenTypes.MINUS_ASSIGN,
220 TokenTypes.MOD,
221 TokenTypes.MOD_ASSIGN,
222 TokenTypes.NOT_EQUAL,
223 TokenTypes.PLUS,
224 TokenTypes.PLUS_ASSIGN,
225 TokenTypes.QUESTION,
226 TokenTypes.RCURLY,
227 TokenTypes.SL,
228 TokenTypes.SLIST,
229 TokenTypes.SL_ASSIGN,
230 TokenTypes.SR,
231 TokenTypes.SR_ASSIGN,
232 TokenTypes.STAR,
233 TokenTypes.STAR_ASSIGN,
234 TokenTypes.LITERAL_ASSERT,
235 TokenTypes.TYPE_EXTENSION_AND,
236 TokenTypes.WILDCARD_TYPE,
237 TokenTypes.GENERIC_START,
238 TokenTypes.GENERIC_END,
239 TokenTypes.ELLIPSIS,
240 TokenTypes.LITERAL_WHEN,
241 };
242 }
243
244 @Override
245 public int[] getRequiredTokens() {
246 return CommonUtil.EMPTY_INT_ARRAY;
247 }
248
249
250
251
252
253
254
255 public void setAllowEmptyMethods(boolean allow) {
256 allowEmptyMethods = allow;
257 }
258
259
260
261
262
263
264
265 public void setAllowEmptyConstructors(boolean allow) {
266 allowEmptyConstructors = allow;
267 }
268
269
270
271
272
273
274
275
276
277 public void setIgnoreEnhancedForColon(boolean ignore) {
278 ignoreEnhancedForColon = ignore;
279 }
280
281
282
283
284
285
286
287 public void setAllowEmptyTypes(boolean allow) {
288 allowEmptyTypes = allow;
289 }
290
291
292
293
294
295
296
297 public void setAllowEmptyLoops(boolean allow) {
298 allowEmptyLoops = allow;
299 }
300
301
302
303
304
305
306
307 public void setAllowEmptyLambdas(boolean allow) {
308 allowEmptyLambdas = allow;
309 }
310
311
312
313
314
315
316
317 public void setAllowEmptyCatches(boolean allow) {
318 allowEmptyCatches = allow;
319 }
320
321
322
323
324
325
326
327 public void setAllowEmptySwitchBlockStatements(boolean allow) {
328 allowEmptySwitchBlockStatements = allow;
329 }
330
331 @Override
332 public void visitToken(DetailAST ast) {
333 final int currentType = ast.getType();
334 if (!isNotRelevantSituation(ast, currentType)) {
335 final int[] line = getLineCodePoints(ast.getLineNo() - 1);
336 final int before = ast.getColumnNo() - 1;
337 final int after = ast.getColumnNo() + ast.getText().length();
338
339 if (before >= 0 && shouldCheckSeparationFromPreviousToken(ast)
340 && !CommonUtil.isCodePointWhitespace(line, before)) {
341 log(ast, MSG_WS_NOT_PRECEDED, ast.getText());
342 }
343
344 if (after < line.length) {
345 final char nextChar = Character.toChars(line[after])[0];
346 if (shouldCheckSeparationFromNextToken(ast, nextChar)
347 && !Character.isWhitespace(nextChar)) {
348 log(ast, MSG_WS_NOT_FOLLOWED, ast.getText());
349 }
350 }
351 }
352 }
353
354
355
356
357
358
359
360
361 private boolean isNotRelevantSituation(DetailAST ast, int currentType) {
362 final int parentType = ast.getParent().getType();
363 return switch (parentType) {
364 case TokenTypes.DOT -> currentType == TokenTypes.STAR;
365 case TokenTypes.LITERAL_DEFAULT, TokenTypes.LITERAL_CASE, TokenTypes.CASE_GROUP -> true;
366 case TokenTypes.FOR_EACH_CLAUSE -> ignoreEnhancedForColon;
367 case TokenTypes.EXPR -> currentType == TokenTypes.LITERAL_SWITCH;
368 case TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT ->
369 currentType == TokenTypes.RCURLY;
370 default -> isEmptyBlock(ast, parentType)
371 || allowEmptyTypes && isEmptyType(ast);
372 };
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 private static boolean shouldCheckSeparationFromPreviousToken(DetailAST ast) {
393 return !isPartOfDoubleBraceInitializerForPreviousToken(ast);
394 }
395
396
397
398
399
400
401
402
403
404
405
406 private boolean shouldCheckSeparationFromNextToken(DetailAST ast, char nextChar) {
407 return !isEmptyCtorBlockCheckedFromSlist(ast)
408 && !(ast.getType() == TokenTypes.LITERAL_RETURN
409 && ast.getFirstChild().getType() == TokenTypes.SEMI)
410 && ast.getType() != TokenTypes.ARRAY_INIT
411 && !isAnonymousInnerClassEnd(ast.getType(), nextChar)
412 && !isPartOfDoubleBraceInitializerForNextToken(ast);
413 }
414
415
416
417
418
419
420
421
422 private static boolean isAnonymousInnerClassEnd(int currentType, char nextChar) {
423 return currentType == TokenTypes.RCURLY
424 && (nextChar == ')'
425 || nextChar == ';'
426 || nextChar == ','
427 || nextChar == '.');
428 }
429
430
431
432
433
434
435
436
437 private boolean isEmptyBlock(DetailAST ast, int parentType) {
438 return isEmptyMethodBlock(ast, parentType)
439 || isEmptyCtorBlockCheckedFromRcurly(ast)
440 || isEmptyLoop(ast, parentType)
441 || isEmptyLambda(ast, parentType)
442 || isEmptyCatch(ast, parentType)
443 || isEmptySwitchBlockStatement(ast);
444 }
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459 private static boolean isEmptyBlock(DetailAST ast, int parentType, int match) {
460 final boolean result;
461 final int type = ast.getType();
462 if (type == TokenTypes.RCURLY) {
463 final DetailAST parent = ast.getParent();
464 final DetailAST grandParent = ast.getParent().getParent();
465 result = parent.getFirstChild().getType() == TokenTypes.RCURLY
466 && grandParent.getType() == match;
467 }
468 else {
469 result = type == TokenTypes.SLIST
470 && parentType == match
471 && ast.getFirstChild().getType() == TokenTypes.RCURLY;
472 }
473 return result;
474 }
475
476
477
478
479
480
481
482
483
484
485 private boolean isEmptyMethodBlock(DetailAST ast, int parentType) {
486 return allowEmptyMethods
487 && isEmptyBlock(ast, parentType, TokenTypes.METHOD_DEF);
488 }
489
490
491
492
493
494
495
496
497
498 private boolean isEmptyCtorBlockCheckedFromRcurly(DetailAST ast) {
499 final DetailAST parent = ast.getParent();
500 final DetailAST grandParent = ast.getParent().getParent();
501 return allowEmptyConstructors
502 && parent.getFirstChild().getType() == TokenTypes.RCURLY
503 && (grandParent.getType() == TokenTypes.CTOR_DEF
504 || grandParent.getType() == TokenTypes.COMPACT_CTOR_DEF);
505
506 }
507
508
509
510
511
512
513
514
515
516 private boolean isEmptyCtorBlockCheckedFromSlist(DetailAST ast) {
517 return allowEmptyConstructors
518 && (ast.getParent().getType() == TokenTypes.CTOR_DEF
519 || ast.getParent().getType() == TokenTypes.COMPACT_CTOR_DEF)
520 && ast.getFirstChild().getType() == TokenTypes.RCURLY;
521 }
522
523
524
525
526
527
528
529
530
531 private boolean isEmptyLoop(DetailAST ast, int parentType) {
532 return allowEmptyLoops
533 && (isEmptyBlock(ast, parentType, TokenTypes.LITERAL_FOR)
534 || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_WHILE)
535 || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_DO));
536 }
537
538
539
540
541
542
543
544
545
546
547 private boolean isEmptyLambda(DetailAST ast, int parentType) {
548 return allowEmptyLambdas && isEmptyBlock(ast, parentType, TokenTypes.LAMBDA);
549 }
550
551
552
553
554
555
556
557
558
559
560 private boolean isEmptyCatch(DetailAST ast, int parentType) {
561 return allowEmptyCatches && isEmptyBlock(ast, parentType, TokenTypes.LITERAL_CATCH);
562 }
563
564
565
566
567
568
569
570
571
572 private boolean isEmptySwitchBlockStatement(DetailAST ast) {
573 final boolean isEmptySwitchBlockStatement;
574
575 if (allowEmptySwitchBlockStatements) {
576 final DetailAST parent = ast.getParent();
577 final DetailAST grandParent = parent.getParent();
578
579 final boolean isEmptyCaseInSwitchRule =
580 isEmptyBlock(ast, parent.getType(), TokenTypes.SWITCH_RULE);
581
582 final boolean isEmptyCaseGroupCheckedFromLcurly =
583 isEmptyBlock(ast, grandParent.getType(), TokenTypes.CASE_GROUP);
584
585 final boolean isEmptyCaseGroupCheckedFromRcurly =
586 parent.getFirstChild().getType() == TokenTypes.RCURLY
587 && grandParent.getParent().getType() == TokenTypes.CASE_GROUP;
588
589 isEmptySwitchBlockStatement = isEmptyCaseInSwitchRule
590 || isEmptyCaseGroupCheckedFromLcurly || isEmptyCaseGroupCheckedFromRcurly;
591 }
592 else {
593 isEmptySwitchBlockStatement = false;
594 }
595
596 return isEmptySwitchBlockStatement;
597 }
598
599
600
601
602
603
604
605
606
607
608
609 private static boolean isEmptyType(DetailAST ast) {
610 final int type = ast.getType();
611 final DetailAST nextSibling = ast.getNextSibling();
612 final DetailAST previousSibling = ast.getPreviousSibling();
613 return type == TokenTypes.LCURLY
614 && nextSibling.getType() == TokenTypes.RCURLY
615 || previousSibling != null
616 && previousSibling.getType() == TokenTypes.LCURLY;
617 }
618
619
620
621
622
623
624
625
626 private static boolean isPartOfDoubleBraceInitializerForPreviousToken(DetailAST ast) {
627 final boolean initializerBeginsAfterClassBegins =
628 ast.getParent().getType() == TokenTypes.INSTANCE_INIT;
629 final boolean classEndsAfterInitializerEnds = ast.getPreviousSibling() != null
630 && ast.getPreviousSibling().getType() == TokenTypes.INSTANCE_INIT;
631 return initializerBeginsAfterClassBegins || classEndsAfterInitializerEnds;
632 }
633
634
635
636
637
638
639
640
641
642
643 private static boolean isPartOfDoubleBraceInitializerForNextToken(DetailAST ast) {
644 final boolean classBeginBeforeInitializerBegin = ast.getType() == TokenTypes.LCURLY
645 && ast.getNextSibling().getType() == TokenTypes.INSTANCE_INIT;
646 final boolean initializerEndsBeforeClassEnds =
647 ast.getParent().getParent().getType() == TokenTypes.INSTANCE_INIT
648 && ast.getParent().getParent().getNextSibling().getType() == TokenTypes.RCURLY;
649 return classBeginBeforeInitializerBegin || initializerEndsBeforeClassEnds;
650 }
651
652 }