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