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