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.HashSet;
23 import java.util.Locale;
24 import java.util.Objects;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27
28 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
29 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
30 import com.puppycrawl.tools.checkstyle.api.DetailAST;
31 import com.puppycrawl.tools.checkstyle.api.Scope;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
34 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
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
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 @FileStatefulCheck
145 public class HiddenFieldCheck
146 extends AbstractCheck {
147
148
149
150
151
152 public static final String MSG_KEY = "hidden.field";
153
154
155
156
157
158 private FieldFrame frame;
159
160
161 private Pattern ignoreFormat;
162
163
164
165
166 private boolean ignoreSetter;
167
168
169
170
171
172 private boolean setterCanReturnItsClass;
173
174
175 private boolean ignoreConstructorParameter;
176
177
178 private boolean ignoreAbstractMethods;
179
180 @Override
181 public int[] getDefaultTokens() {
182 return getAcceptableTokens();
183 }
184
185 @Override
186 public int[] getAcceptableTokens() {
187 return new int[] {
188 TokenTypes.VARIABLE_DEF,
189 TokenTypes.PARAMETER_DEF,
190 TokenTypes.CLASS_DEF,
191 TokenTypes.ENUM_DEF,
192 TokenTypes.ENUM_CONSTANT_DEF,
193 TokenTypes.PATTERN_VARIABLE_DEF,
194 TokenTypes.LAMBDA,
195 TokenTypes.RECORD_DEF,
196 TokenTypes.RECORD_COMPONENT_DEF,
197 };
198 }
199
200 @Override
201 public int[] getRequiredTokens() {
202 return new int[] {
203 TokenTypes.CLASS_DEF,
204 TokenTypes.ENUM_DEF,
205 TokenTypes.ENUM_CONSTANT_DEF,
206 TokenTypes.RECORD_DEF,
207 };
208 }
209
210 @Override
211 public void beginTree(DetailAST rootAST) {
212 frame = new FieldFrame(null, true, null);
213 }
214
215 @Override
216 public void visitToken(DetailAST ast) {
217 final int type = ast.getType();
218 switch (type) {
219 case TokenTypes.VARIABLE_DEF,
220 TokenTypes.PARAMETER_DEF,
221 TokenTypes.PATTERN_VARIABLE_DEF,
222 TokenTypes.RECORD_COMPONENT_DEF -> processVariable(ast);
223 case TokenTypes.LAMBDA -> processLambda(ast);
224 default -> visitOtherTokens(ast, type);
225 }
226 }
227
228
229
230
231
232
233
234
235
236 private void processLambda(DetailAST ast) {
237 final DetailAST firstChild = ast.getFirstChild();
238 if (TokenUtil.isOfType(firstChild, TokenTypes.IDENT)) {
239 final String untypedLambdaParameterName = firstChild.getText();
240 if (frame.containsStaticField(untypedLambdaParameterName)
241 || isInstanceField(firstChild, untypedLambdaParameterName)) {
242 log(firstChild, MSG_KEY, untypedLambdaParameterName);
243 }
244 }
245 }
246
247
248
249
250
251
252
253
254 private void visitOtherTokens(DetailAST ast, int type) {
255
256
257
258
259
260 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS);
261 final boolean isStaticInnerType =
262 typeMods != null
263 && typeMods.findFirstToken(TokenTypes.LITERAL_STATIC) != null
264
265 || ast.getType() == TokenTypes.RECORD_DEF;
266 final String frameName;
267
268 if (type == TokenTypes.CLASS_DEF
269 || type == TokenTypes.ENUM_DEF) {
270 frameName = ast.findFirstToken(TokenTypes.IDENT).getText();
271 }
272 else {
273 frameName = null;
274 }
275 final FieldFrame newFrame = new FieldFrame(frame, isStaticInnerType, frameName);
276
277
278 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
279
280 if (objBlock != null) {
281 DetailAST child = objBlock.getFirstChild();
282 while (child != null) {
283 if (child.getType() == TokenTypes.VARIABLE_DEF) {
284 final String name =
285 child.findFirstToken(TokenTypes.IDENT).getText();
286 final DetailAST mods =
287 child.findFirstToken(TokenTypes.MODIFIERS);
288 if (mods.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
289 newFrame.addInstanceField(name);
290 }
291 else {
292 newFrame.addStaticField(name);
293 }
294 }
295 child = child.getNextSibling();
296 }
297 }
298 if (ast.getType() == TokenTypes.RECORD_DEF) {
299 final DetailAST recordComponents =
300 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS);
301
302
303 TokenUtil.forEachChild(recordComponents,
304 TokenTypes.RECORD_COMPONENT_DEF, node -> {
305 final String name = node.findFirstToken(TokenTypes.IDENT).getText();
306 newFrame.addInstanceField(name);
307 });
308 }
309
310 frame = newFrame;
311 }
312
313 @Override
314 public void leaveToken(DetailAST ast) {
315 if (ast.getType() == TokenTypes.CLASS_DEF
316 || ast.getType() == TokenTypes.ENUM_DEF
317 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF
318 || ast.getType() == TokenTypes.RECORD_DEF) {
319
320 frame = frame.getParent();
321 }
322 }
323
324
325
326
327
328
329
330
331 private void processVariable(DetailAST ast) {
332 if (!ScopeUtil.isInInterfaceOrAnnotationBlock(ast)
333 && !CheckUtil.isReceiverParameter(ast)
334 && (ScopeUtil.isLocalVariableDef(ast)
335 || ast.getType() == TokenTypes.PARAMETER_DEF
336 || ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF)) {
337
338 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT);
339 final String name = nameAST.getText();
340
341 if ((frame.containsStaticField(name) || isInstanceField(ast, name))
342 && !isMatchingRegexp(name)
343 && !isIgnoredParam(ast, name)) {
344 log(nameAST, MSG_KEY, name);
345 }
346 }
347 }
348
349
350
351
352
353
354
355
356 private boolean isIgnoredParam(DetailAST ast, String name) {
357 return isIgnoredSetterParam(ast, name)
358 || isIgnoredConstructorParam(ast)
359 || isIgnoredParamOfAbstractMethod(ast);
360 }
361
362
363
364
365
366
367
368
369 private boolean isInstanceField(DetailAST ast, String name) {
370 return !isInStatic(ast) && frame.containsInstanceField(name);
371 }
372
373
374
375
376
377
378
379 private boolean isMatchingRegexp(String name) {
380 return ignoreFormat != null && ignoreFormat.matcher(name).find();
381 }
382
383
384
385
386
387
388
389
390 private static boolean isInStatic(DetailAST ast) {
391 DetailAST parent = ast.getParent();
392 boolean inStatic = false;
393
394 while (parent != null && !inStatic) {
395 if (parent.getType() == TokenTypes.STATIC_INIT) {
396 inStatic = true;
397 }
398 else if (parent.getType() == TokenTypes.METHOD_DEF
399 && !ScopeUtil.isInScope(parent, Scope.ANONINNER)
400 || parent.getType() == TokenTypes.VARIABLE_DEF) {
401 final DetailAST mods =
402 parent.findFirstToken(TokenTypes.MODIFIERS);
403 inStatic = mods.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
404 break;
405 }
406 else {
407 parent = parent.getParent();
408 }
409 }
410 return inStatic;
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427 private boolean isIgnoredSetterParam(DetailAST ast, String name) {
428 boolean isIgnoredSetterParam = false;
429 if (ignoreSetter) {
430 final DetailAST parametersAST = ast.getParent();
431 final DetailAST methodAST = parametersAST.getParent();
432 if (parametersAST.getChildCount() == 1
433 && methodAST.getType() == TokenTypes.METHOD_DEF
434 && isSetterMethod(methodAST, name)) {
435 isIgnoredSetterParam = true;
436 }
437 }
438 return isIgnoredSetterParam;
439 }
440
441
442
443
444
445
446
447
448
449
450 private boolean isSetterMethod(DetailAST aMethodAST, String aName) {
451 final String methodName =
452 aMethodAST.findFirstToken(TokenTypes.IDENT).getText();
453 boolean isSetterMethod = false;
454
455 if (("set" + capitalize(aName)).equals(methodName)) {
456
457
458
459 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE);
460 final String returnType = typeAST.getFirstChild().getText();
461 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) != null
462 || setterCanReturnItsClass && frame.isEmbeddedIn(returnType)) {
463
464
465
466
467
468
469
470
471
472
473
474 isSetterMethod = true;
475 }
476 }
477
478 return isSetterMethod;
479 }
480
481
482
483
484
485
486
487
488 private static String capitalize(final String name) {
489 String setterName = name;
490
491
492
493 if (name.length() == 1 || !Character.isUpperCase(name.charAt(1))) {
494 setterName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
495 }
496 return setterName;
497 }
498
499
500
501
502
503
504
505
506
507 private boolean isIgnoredConstructorParam(DetailAST ast) {
508 boolean result = false;
509 if (ignoreConstructorParameter
510 && ast.getType() == TokenTypes.PARAMETER_DEF) {
511 final DetailAST parametersAST = ast.getParent();
512 final DetailAST constructorAST = parametersAST.getParent();
513 result = constructorAST.getType() == TokenTypes.CTOR_DEF;
514 }
515 return result;
516 }
517
518
519
520
521
522
523
524
525
526 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) {
527 boolean result = false;
528 if (ignoreAbstractMethods) {
529 final DetailAST method = ast.getParent().getParent();
530 if (method.getType() == TokenTypes.METHOD_DEF) {
531 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS);
532 result = mods.findFirstToken(TokenTypes.ABSTRACT) != null;
533 }
534 }
535 return result;
536 }
537
538
539
540
541
542
543
544 public void setIgnoreFormat(Pattern pattern) {
545 ignoreFormat = pattern;
546 }
547
548
549
550
551
552
553
554
555 public void setIgnoreSetter(boolean ignoreSetter) {
556 this.ignoreSetter = ignoreSetter;
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570 public void setSetterCanReturnItsClass(
571 boolean aSetterCanReturnItsClass) {
572 setterCanReturnItsClass = aSetterCanReturnItsClass;
573 }
574
575
576
577
578
579
580
581
582 public void setIgnoreConstructorParameter(
583 boolean ignoreConstructorParameter) {
584 this.ignoreConstructorParameter = ignoreConstructorParameter;
585 }
586
587
588
589
590
591
592
593
594 public void setIgnoreAbstractMethods(
595 boolean ignoreAbstractMethods) {
596 this.ignoreAbstractMethods = ignoreAbstractMethods;
597 }
598
599
600
601
602 private static final class FieldFrame {
603
604
605 private final String frameName;
606
607
608 private final boolean staticType;
609
610
611 private final FieldFrame parent;
612
613
614 private final Set<String> instanceFields = new HashSet<>();
615
616
617 private final Set<String> staticFields = new HashSet<>();
618
619
620
621
622
623
624
625
626 private FieldFrame(FieldFrame parent, boolean staticType, String frameName) {
627 this.parent = parent;
628 this.staticType = staticType;
629 this.frameName = frameName;
630 }
631
632
633
634
635
636
637 public void addInstanceField(String field) {
638 instanceFields.add(field);
639 }
640
641
642
643
644
645
646 public void addStaticField(String field) {
647 staticFields.add(field);
648 }
649
650
651
652
653
654
655
656 public boolean containsInstanceField(String field) {
657 FieldFrame currentParent = parent;
658 boolean contains = instanceFields.contains(field);
659 boolean isStaticType = staticType;
660 while (!isStaticType && !contains) {
661 contains = currentParent.instanceFields.contains(field);
662 isStaticType = currentParent.staticType;
663 currentParent = currentParent.parent;
664 }
665 return contains;
666 }
667
668
669
670
671
672
673
674 public boolean containsStaticField(String field) {
675 FieldFrame currentParent = parent;
676 boolean contains = staticFields.contains(field);
677 while (currentParent != null && !contains) {
678 contains = currentParent.staticFields.contains(field);
679 currentParent = currentParent.parent;
680 }
681 return contains;
682 }
683
684
685
686
687
688
689 public FieldFrame getParent() {
690 return parent;
691 }
692
693
694
695
696
697
698
699
700
701
702
703 private boolean isEmbeddedIn(String classOrEnumName) {
704 FieldFrame currentFrame = this;
705 boolean isEmbeddedIn = false;
706 while (currentFrame != null) {
707 if (Objects.equals(currentFrame.frameName, classOrEnumName)) {
708 isEmbeddedIn = true;
709 break;
710 }
711 currentFrame = currentFrame.parent;
712 }
713 return isEmbeddedIn;
714 }
715
716 }
717
718 }