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