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