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 java.util.Optional;
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.CommonUtil;
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 @StatelessCheck
119 public class NoWhitespaceAfterCheck extends AbstractCheck {
120
121
122
123
124
125 public static final String MSG_KEY = "ws.followed";
126
127
128 private boolean allowLineBreaks = true;
129
130 @Override
131 public int[] getDefaultTokens() {
132 return new int[] {
133 TokenTypes.ARRAY_INIT,
134 TokenTypes.AT,
135 TokenTypes.INC,
136 TokenTypes.DEC,
137 TokenTypes.UNARY_MINUS,
138 TokenTypes.UNARY_PLUS,
139 TokenTypes.BNOT,
140 TokenTypes.LNOT,
141 TokenTypes.DOT,
142 TokenTypes.ARRAY_DECLARATOR,
143 TokenTypes.INDEX_OP,
144 };
145 }
146
147 @Override
148 public int[] getAcceptableTokens() {
149 return new int[] {
150 TokenTypes.ARRAY_INIT,
151 TokenTypes.AT,
152 TokenTypes.INC,
153 TokenTypes.DEC,
154 TokenTypes.UNARY_MINUS,
155 TokenTypes.UNARY_PLUS,
156 TokenTypes.BNOT,
157 TokenTypes.LNOT,
158 TokenTypes.DOT,
159 TokenTypes.TYPECAST,
160 TokenTypes.ARRAY_DECLARATOR,
161 TokenTypes.INDEX_OP,
162 TokenTypes.LITERAL_SYNCHRONIZED,
163 TokenTypes.METHOD_REF,
164 };
165 }
166
167 @Override
168 public int[] getRequiredTokens() {
169 return CommonUtil.EMPTY_INT_ARRAY;
170 }
171
172
173
174
175
176
177
178
179 public void setAllowLineBreaks(boolean allowLineBreaks) {
180 this.allowLineBreaks = allowLineBreaks;
181 }
182
183 @Override
184 public void visitToken(DetailAST ast) {
185 if (shouldCheckWhitespaceAfter(ast)) {
186 final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
187 final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
188 final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
189
190 if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
191 log(ast, MSG_KEY, whitespaceFollowedAst.getText());
192 }
193 }
194 }
195
196
197
198
199
200
201
202
203
204 private static DetailAST getWhitespaceFollowedNode(DetailAST ast) {
205 final DetailAST whitespaceFollowedAst;
206 switch (ast.getType()) {
207 case TokenTypes.TYPECAST:
208 whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
209 break;
210 case TokenTypes.ARRAY_DECLARATOR:
211 whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
212 break;
213 case TokenTypes.INDEX_OP:
214 whitespaceFollowedAst = getIndexOpPreviousElement(ast);
215 break;
216 default:
217 whitespaceFollowedAst = ast;
218 }
219 return whitespaceFollowedAst;
220 }
221
222
223
224
225
226
227
228
229
230 private static boolean shouldCheckWhitespaceAfter(DetailAST ast) {
231 final DetailAST previousSibling = ast.getPreviousSibling();
232 final boolean isSynchronizedMethod = ast.getType() == TokenTypes.LITERAL_SYNCHRONIZED
233 && ast.getFirstChild() == null;
234 return !isSynchronizedMethod
235 && (previousSibling == null || previousSibling.getType() != TokenTypes.ANNOTATIONS);
236 }
237
238
239
240
241
242
243
244 private static int getPositionAfter(DetailAST ast) {
245 final int after;
246
247 if (ast.getType() == TokenTypes.IDENT
248 && ast.getNextSibling() != null
249 && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
250 final DetailAST methodDef = ast.getParent();
251 final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
252 after = endOfParams.getColumnNo() + 1;
253 }
254 else {
255 after = ast.getColumnNo() + ast.getText().length();
256 }
257 return after;
258 }
259
260
261
262
263
264
265
266
267
268
269
270
271 private boolean hasTrailingWhitespace(DetailAST ast,
272 int whitespaceColumnNo, int whitespaceLineNo) {
273 final boolean result;
274 final int astLineNo = ast.getLineNo();
275 final int[] line = getLineCodePoints(astLineNo - 1);
276 if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length) {
277 result = CommonUtil.isCodePointWhitespace(line, whitespaceColumnNo);
278 }
279 else {
280 result = !allowLineBreaks;
281 }
282 return result;
283 }
284
285
286
287
288
289
290
291
292
293
294
295 private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) {
296 final DetailAST previousElement;
297
298 if (ast.getPreviousSibling() != null
299 && ast.getPreviousSibling().getType() == TokenTypes.ARRAY_DECLARATOR) {
300
301 previousElement = getPreviousElementOfMultiDimArray(ast);
302 }
303 else {
304
305 final DetailAST parent = ast.getParent();
306 switch (parent.getType()) {
307
308 case TokenTypes.TYPE_UPPER_BOUNDS:
309 case TokenTypes.TYPE_LOWER_BOUNDS:
310 previousElement = ast.getPreviousSibling();
311 break;
312 case TokenTypes.LITERAL_NEW:
313 case TokenTypes.TYPE_ARGUMENT:
314 case TokenTypes.DOT:
315 previousElement = getTypeLastNode(ast);
316 break;
317
318 case TokenTypes.TYPE:
319 previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
320 break;
321
322 case TokenTypes.METHOD_REF:
323 final DetailAST ident = getIdentLastToken(ast);
324 if (ident == null) {
325
326 previousElement = ast.getParent().getFirstChild();
327 }
328 else {
329 previousElement = ident;
330 }
331 break;
332 default:
333 throw new IllegalStateException("unexpected ast syntax " + parent);
334 }
335 }
336 return previousElement;
337 }
338
339
340
341
342
343
344
345
346 private static DetailAST getPreviousElementOfMultiDimArray(DetailAST leftBracket) {
347 final DetailAST previousRightBracket = leftBracket.getPreviousSibling().getLastChild();
348
349 DetailAST ident = null;
350
351 DetailAST parent = leftBracket.getParent().getParent();
352 while (ident == null) {
353 ident = parent.findFirstToken(TokenTypes.IDENT);
354 parent = parent.getParent();
355 }
356
357 final DetailAST previousElement;
358 if (ident.getColumnNo() > previousRightBracket.getColumnNo()
359 && ident.getColumnNo() < leftBracket.getColumnNo()) {
360
361 previousElement = ident;
362 }
363 else {
364
365 previousElement = previousRightBracket;
366 }
367 return previousElement;
368 }
369
370
371
372
373
374
375
376
377
378
379 private static DetailAST getIndexOpPreviousElement(DetailAST ast) {
380 final DetailAST result;
381 final DetailAST firstChild = ast.getFirstChild();
382 if (firstChild.getType() == TokenTypes.INDEX_OP) {
383
384 result = firstChild.findFirstToken(TokenTypes.RBRACK);
385 }
386 else if (firstChild.getType() == TokenTypes.IDENT) {
387 result = firstChild;
388 }
389 else {
390 final DetailAST ident = getIdentLastToken(ast);
391 if (ident == null) {
392 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
393
394 if (rparen == null) {
395 final DetailAST lastChild = firstChild.getLastChild();
396 result = lastChild.findFirstToken(TokenTypes.RCURLY);
397 }
398
399 else {
400 result = rparen;
401 }
402 }
403 else {
404 result = ident;
405 }
406 }
407 return result;
408 }
409
410
411
412
413
414
415
416
417
418 private static DetailAST getTypeLastNode(DetailAST ast) {
419 final DetailAST typeLastNode;
420 final DetailAST parent = ast.getParent();
421 final boolean isPrecededByTypeArgs =
422 parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS) != null;
423 final Optional<DetailAST> objectArrayType = Optional.ofNullable(getIdentLastToken(ast));
424
425 if (isPrecededByTypeArgs) {
426 typeLastNode = parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
427 .findFirstToken(TokenTypes.GENERIC_END);
428 }
429 else if (objectArrayType.isPresent()) {
430 typeLastNode = objectArrayType.orElseThrow();
431 }
432 else {
433 typeLastNode = parent.getFirstChild();
434 }
435
436 return typeLastNode;
437 }
438
439
440
441
442
443
444
445
446
447
448
449 private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) {
450 final DetailAST previousElement;
451 final DetailAST ident = getIdentLastToken(parent.getParent());
452 final DetailAST lastTypeNode = getTypeLastNode(ast);
453
454
455
456
457
458 if (ident == null || ident.getLineNo() > ast.getLineNo()) {
459 previousElement = lastTypeNode;
460 }
461 else if (ident.getLineNo() < ast.getLineNo()) {
462 previousElement = ident;
463 }
464
465 else {
466 final int instanceOfSize = 13;
467
468 if (ident.getColumnNo() >= ast.getColumnNo() + 2
469
470
471 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) {
472 previousElement = lastTypeNode;
473 }
474 else {
475 previousElement = ident;
476 }
477 }
478 return previousElement;
479 }
480
481
482
483
484
485
486
487
488 private static DetailAST getIdentLastToken(DetailAST ast) {
489 final DetailAST result;
490 final Optional<DetailAST> dot = getPrecedingDot(ast);
491
492 if (dot.isEmpty() || ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
493 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
494 if (methodCall == null) {
495 result = ast.findFirstToken(TokenTypes.IDENT);
496 }
497 else {
498 result = methodCall.findFirstToken(TokenTypes.RPAREN);
499 }
500 }
501
502 else {
503 result = dot.orElseThrow().getFirstChild().getNextSibling();
504 }
505 return result;
506 }
507
508
509
510
511
512
513
514
515 private static Optional<DetailAST> getPrecedingDot(DetailAST leftBracket) {
516 final DetailAST referencedMemberDot = leftBracket.findFirstToken(TokenTypes.DOT);
517 final Optional<DetailAST> result = Optional.ofNullable(referencedMemberDot);
518 return result.or(() -> getReferencedClassDot(leftBracket));
519 }
520
521
522
523
524
525
526
527 private static Optional<DetailAST> getReferencedClassDot(DetailAST leftBracket) {
528 final DetailAST parent = leftBracket.getParent();
529 Optional<DetailAST> classDot = Optional.empty();
530 if (parent.getType() != TokenTypes.ASSIGN) {
531 classDot = Optional.ofNullable(parent.findFirstToken(TokenTypes.DOT));
532 }
533 return classDot;
534 }
535 }