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