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