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.coding;
21
22 import java.util.AbstractMap.SimpleEntry;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import com.puppycrawl.tools.checkstyle.StatelessCheck;
31 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.FullIdent;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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 @StatelessCheck
87 public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
88
89
90
91
92 public static final String MSG_KEY = "variable.declaration.usage.distance";
93
94
95
96
97 public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
98
99
100
101
102
103 private static final int DEFAULT_DISTANCE = 3;
104
105
106
107
108
109 private int allowedDistance = DEFAULT_DISTANCE;
110
111
112
113
114
115 private Pattern ignoreVariablePattern = Pattern.compile("");
116
117
118
119
120
121 private boolean validateBetweenScopes;
122
123
124 private boolean ignoreFinal = true;
125
126
127
128
129
130
131
132
133
134
135 public void setAllowedDistance(int allowedDistance) {
136 this.allowedDistance = allowedDistance;
137 }
138
139
140
141
142
143
144
145 public void setIgnoreVariablePattern(Pattern pattern) {
146 ignoreVariablePattern = pattern;
147 }
148
149
150
151
152
153
154
155
156
157
158 public void setValidateBetweenScopes(boolean validateBetweenScopes) {
159 this.validateBetweenScopes = validateBetweenScopes;
160 }
161
162
163
164
165
166
167
168
169 public void setIgnoreFinal(boolean ignoreFinal) {
170 this.ignoreFinal = ignoreFinal;
171 }
172
173 @Override
174 public int[] getDefaultTokens() {
175 return getRequiredTokens();
176 }
177
178 @Override
179 public int[] getAcceptableTokens() {
180 return getRequiredTokens();
181 }
182
183 @Override
184 public int[] getRequiredTokens() {
185 return new int[] {TokenTypes.VARIABLE_DEF};
186 }
187
188 @Override
189 public void visitToken(DetailAST ast) {
190 final int parentType = ast.getParent().getType();
191 final DetailAST modifiers = ast.getFirstChild();
192
193 if (parentType != TokenTypes.OBJBLOCK
194 && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
195 final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
196
197 if (!isVariableMatchesIgnorePattern(variable.getText())) {
198 final DetailAST semicolonAst = ast.getNextSibling();
199 final Entry<DetailAST, Integer> entry;
200 if (validateBetweenScopes) {
201 entry = calculateDistanceBetweenScopes(semicolonAst, variable);
202 }
203 else {
204 entry = calculateDistanceInSingleScope(semicolonAst, variable);
205 }
206 final DetailAST variableUsageAst = entry.getKey();
207 final int dist = entry.getValue();
208 if (dist > allowedDistance
209 && !isInitializationSequence(variableUsageAst, variable.getText())) {
210 if (ignoreFinal) {
211 log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
212 }
213 else {
214 log(ast, MSG_KEY, variable.getText(), dist, allowedDistance);
215 }
216 }
217 }
218 }
219 }
220
221
222
223
224
225
226
227
228 private static String getInstanceName(DetailAST methodCallAst) {
229 final String methodCallName =
230 FullIdent.createFullIdentBelow(methodCallAst).getText();
231 final int lastDotIndex = methodCallName.lastIndexOf('.');
232 String instanceName = "";
233 if (lastDotIndex != -1) {
234 instanceName = methodCallName.substring(0, lastDotIndex);
235 }
236 return instanceName;
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250 private static boolean isInitializationSequence(
251 DetailAST variableUsageAst, String variableName) {
252 boolean result = true;
253 boolean isUsedVariableDeclarationFound = false;
254 DetailAST currentSiblingAst = variableUsageAst;
255 String initInstanceName = "";
256
257 while (result && !isUsedVariableDeclarationFound && currentSiblingAst != null) {
258 if (currentSiblingAst.getType() == TokenTypes.EXPR
259 && currentSiblingAst.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
260 final DetailAST methodCallAst = currentSiblingAst.getFirstChild();
261 final String instanceName = getInstanceName(methodCallAst);
262 if (instanceName.isEmpty()) {
263 result = false;
264 }
265 else if (!instanceName.equals(initInstanceName)) {
266 if (initInstanceName.isEmpty()) {
267 initInstanceName = instanceName;
268 }
269 else {
270 result = false;
271 }
272 }
273
274 }
275 else if (currentSiblingAst.getType() == TokenTypes.VARIABLE_DEF) {
276 final String currentVariableName =
277 currentSiblingAst.findFirstToken(TokenTypes.IDENT).getText();
278 isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
279 }
280 else {
281 result = currentSiblingAst.getType() == TokenTypes.SEMI;
282 }
283 currentSiblingAst = currentSiblingAst.getPreviousSibling();
284 }
285 return result;
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301 private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
302 DetailAST semicolonAst, DetailAST variableIdentAst) {
303 int dist = 0;
304 boolean firstUsageFound = false;
305 DetailAST currentAst = semicolonAst;
306 DetailAST variableUsageAst = null;
307
308 while (!firstUsageFound && currentAst != null) {
309 if (currentAst.getFirstChild() != null) {
310 if (isChild(currentAst, variableIdentAst)) {
311 dist = getDistToVariableUsageInChildNode(currentAst, dist);
312 variableUsageAst = currentAst;
313 firstUsageFound = true;
314 }
315 else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
316 dist++;
317 }
318 }
319 currentAst = currentAst.getNextSibling();
320 }
321
322 return new SimpleEntry<>(variableUsageAst, dist);
323 }
324
325
326
327
328
329
330
331
332 private static int getDistToVariableUsageInChildNode(DetailAST childNode,
333 int currentDistToVarUsage) {
334 DetailAST examineNode = childNode;
335 if (examineNode.getType() == TokenTypes.LABELED_STAT) {
336 examineNode = examineNode.getFirstChild().getNextSibling();
337 }
338
339 int resultDist = currentDistToVarUsage;
340
341 switch (examineNode.getType()) {
342 case TokenTypes.SLIST:
343 resultDist = 0;
344 break;
345 case TokenTypes.LITERAL_FOR:
346 case TokenTypes.LITERAL_WHILE:
347 case TokenTypes.LITERAL_DO:
348 case TokenTypes.LITERAL_IF:
349 case TokenTypes.LITERAL_SWITCH:
350
351
352 resultDist++;
353 break;
354 default:
355 if (childNode.findFirstToken(TokenTypes.SLIST) == null) {
356 resultDist++;
357 }
358 else {
359 resultDist = 0;
360 }
361 }
362 return resultDist;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376 private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
377 DetailAST ast, DetailAST variable) {
378 int dist = 0;
379 DetailAST currentScopeAst = ast;
380 DetailAST variableUsageAst = null;
381 while (currentScopeAst != null) {
382 final Entry<List<DetailAST>, Integer> searchResult =
383 searchVariableUsageExpressions(variable, currentScopeAst);
384
385 currentScopeAst = null;
386
387 final List<DetailAST> variableUsageExpressions = searchResult.getKey();
388 dist += searchResult.getValue();
389
390
391
392 if (variableUsageExpressions.size() == 1) {
393 final DetailAST blockWithVariableUsage = variableUsageExpressions
394 .get(0);
395 DetailAST exprWithVariableUsage = null;
396 switch (blockWithVariableUsage.getType()) {
397 case TokenTypes.VARIABLE_DEF:
398 case TokenTypes.EXPR:
399 dist++;
400 break;
401 case TokenTypes.LITERAL_FOR:
402 case TokenTypes.LITERAL_WHILE:
403 case TokenTypes.LITERAL_DO:
404 exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks(
405 blockWithVariableUsage, variable);
406 break;
407 case TokenTypes.LITERAL_IF:
408 exprWithVariableUsage = getFirstNodeInsideIfBlock(
409 blockWithVariableUsage, variable);
410 break;
411 case TokenTypes.LITERAL_SWITCH:
412 exprWithVariableUsage = getFirstNodeInsideSwitchBlock(
413 blockWithVariableUsage, variable);
414 break;
415 case TokenTypes.LITERAL_TRY:
416 exprWithVariableUsage =
417 getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage,
418 variable);
419 break;
420 default:
421 exprWithVariableUsage = blockWithVariableUsage.getFirstChild();
422 }
423 currentScopeAst = exprWithVariableUsage;
424 variableUsageAst = blockWithVariableUsage;
425 }
426
427
428 else if (variableUsageExpressions.isEmpty()) {
429 variableUsageAst = null;
430 }
431
432
433 else {
434 dist++;
435 variableUsageAst = variableUsageExpressions.get(0);
436 }
437 }
438 return new SimpleEntry<>(variableUsageAst, dist);
439 }
440
441
442
443
444
445
446
447
448
449 private static Entry<List<DetailAST>, Integer>
450 searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
451 final List<DetailAST> variableUsageExpressions = new ArrayList<>();
452 int distance = 0;
453 DetailAST currentStatementAst = statementAst;
454 while (currentStatementAst != null) {
455 if (currentStatementAst.getFirstChild() != null) {
456 if (isChild(currentStatementAst, variableAst)) {
457 variableUsageExpressions.add(currentStatementAst);
458 }
459
460 else if (variableUsageExpressions.isEmpty()
461 && !isZeroDistanceToken(currentStatementAst.getType())) {
462 distance++;
463 }
464 }
465 currentStatementAst = currentStatementAst.getNextSibling();
466 }
467 return new SimpleEntry<>(variableUsageExpressions, distance);
468 }
469
470
471
472
473
474
475
476
477
478
479
480
481
482 private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
483 DetailAST block, DetailAST variable) {
484 DetailAST firstNodeInsideBlock = null;
485
486 if (!isVariableInOperatorExpr(block, variable)) {
487 final DetailAST currentNode;
488
489
490 if (block.getType() == TokenTypes.LITERAL_DO) {
491 currentNode = block.getFirstChild();
492 }
493
494 else {
495
496
497 currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
498 }
499
500 final int currentNodeType = currentNode.getType();
501
502 if (currentNodeType != TokenTypes.EXPR) {
503 firstNodeInsideBlock = currentNode;
504 }
505 }
506
507 return firstNodeInsideBlock;
508 }
509
510
511
512
513
514
515
516
517
518
519
520
521
522 private static DetailAST getFirstNodeInsideIfBlock(
523 DetailAST block, DetailAST variable) {
524 DetailAST firstNodeInsideBlock = null;
525
526 if (!isVariableInOperatorExpr(block, variable)) {
527 final Optional<DetailAST> slistToken = TokenUtil
528 .findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST);
529 final DetailAST lastNode = block.getLastChild();
530 DetailAST previousNode = lastNode.getPreviousSibling();
531
532 if (slistToken.isEmpty()
533 && lastNode.getType() == TokenTypes.LITERAL_ELSE) {
534
535
536
537 previousNode = previousNode.getPreviousSibling();
538 }
539
540 final List<DetailAST> variableUsageExpressions = new ArrayList<>();
541 if (isChild(previousNode, variable)) {
542 variableUsageExpressions.add(previousNode);
543 }
544
545 if (isChild(lastNode, variable)) {
546 variableUsageExpressions.add(lastNode);
547 }
548
549
550
551
552
553 if (variableUsageExpressions.size() == 1) {
554 firstNodeInsideBlock = variableUsageExpressions.get(0);
555 }
556 }
557
558 return firstNodeInsideBlock;
559 }
560
561
562
563
564
565
566
567
568
569
570
571
572
573 private static DetailAST getFirstNodeInsideSwitchBlock(
574 DetailAST block, DetailAST variable) {
575 final List<DetailAST> variableUsageExpressions =
576 getVariableUsageExpressionsInsideSwitchBlock(block, variable);
577
578
579
580
581
582 DetailAST firstNodeInsideBlock = null;
583 if (variableUsageExpressions.size() == 1) {
584 firstNodeInsideBlock = variableUsageExpressions.get(0);
585 }
586
587 return firstNodeInsideBlock;
588 }
589
590
591
592
593
594
595
596
597
598 private static List<DetailAST> getVariableUsageExpressionsInsideSwitchBlock(DetailAST block,
599 DetailAST variable) {
600 final Optional<DetailAST> firstToken = TokenUtil.findFirstTokenByPredicate(block, child -> {
601 return child.getType() == TokenTypes.SWITCH_RULE
602 || child.getType() == TokenTypes.CASE_GROUP;
603 });
604
605 final List<DetailAST> variableUsageExpressions = new ArrayList<>();
606
607 firstToken.ifPresent(token -> {
608 TokenUtil.forEachChild(block, token.getType(), child -> {
609 final DetailAST lastNodeInCaseGroup = child.getLastChild();
610 if (isChild(lastNodeInCaseGroup, variable)) {
611 variableUsageExpressions.add(lastNodeInCaseGroup);
612 }
613 });
614 });
615
616 return variableUsageExpressions;
617 }
618
619
620
621
622
623
624
625
626
627
628
629
630
631 private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
632 DetailAST block, DetailAST variable) {
633 DetailAST currentNode = block.getFirstChild();
634 final List<DetailAST> variableUsageExpressions =
635 new ArrayList<>();
636
637
638 if (isChild(currentNode, variable)) {
639 variableUsageExpressions.add(currentNode);
640 }
641
642
643 currentNode = currentNode.getNextSibling();
644
645
646 while (currentNode != null
647 && currentNode.getType() == TokenTypes.LITERAL_CATCH) {
648 final DetailAST catchBlock = currentNode.getLastChild();
649
650 if (isChild(catchBlock, variable)) {
651 variableUsageExpressions.add(catchBlock);
652 }
653 currentNode = currentNode.getNextSibling();
654 }
655
656
657 if (currentNode != null) {
658 final DetailAST finalBlock = currentNode.getLastChild();
659
660 if (isChild(finalBlock, variable)) {
661 variableUsageExpressions.add(finalBlock);
662 }
663 }
664
665 DetailAST variableUsageNode = null;
666
667
668
669
670
671 if (variableUsageExpressions.size() == 1) {
672 variableUsageNode = variableUsageExpressions.get(0).getFirstChild();
673 }
674
675 return variableUsageNode;
676 }
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693 private static boolean isVariableInOperatorExpr(
694 DetailAST operator, DetailAST variable) {
695 boolean isVarInOperatorDeclaration = false;
696
697 DetailAST ast = operator.findFirstToken(TokenTypes.LPAREN);
698
699
700 while (ast.getType() != TokenTypes.RPAREN) {
701 if (isChild(ast, variable)) {
702 isVarInOperatorDeclaration = true;
703 break;
704 }
705 ast = ast.getNextSibling();
706 }
707
708 return isVarInOperatorDeclaration;
709 }
710
711
712
713
714
715
716
717
718
719
720 private static boolean isChild(DetailAST parent, DetailAST ast) {
721 boolean isChild = false;
722 DetailAST curNode = parent.getFirstChild();
723
724 while (curNode != null) {
725 if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) {
726 isChild = true;
727 break;
728 }
729
730 DetailAST toVisit = curNode.getFirstChild();
731 while (toVisit == null) {
732 toVisit = curNode.getNextSibling();
733 curNode = curNode.getParent();
734
735 if (curNode == parent) {
736 break;
737 }
738 }
739
740 curNode = toVisit;
741 }
742
743 return isChild;
744 }
745
746
747
748
749
750
751
752
753 private boolean isVariableMatchesIgnorePattern(String variable) {
754 final Matcher matcher = ignoreVariablePattern.matcher(variable);
755 return matcher.matches();
756 }
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 private static boolean isZeroDistanceToken(int type) {
784 return type == TokenTypes.VARIABLE_DEF
785 || type == TokenTypes.TYPE
786 || type == TokenTypes.MODIFIERS
787 || type == TokenTypes.RESOURCE
788 || type == TokenTypes.EXTENDS_CLAUSE
789 || type == TokenTypes.IMPLEMENTS_CLAUSE;
790 }
791
792 }