View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.coding.UnusedLocalVariableCheck.MSG_UNUSED_LOCAL_VARIABLE;
24  
25  import java.io.File;
26  import java.lang.reflect.Constructor;
27  import java.lang.reflect.Method;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Optional;
31  
32  import org.junit.jupiter.api.AfterEach;
33  import org.junit.jupiter.api.Test;
34  
35  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
36  import com.puppycrawl.tools.checkstyle.Checker;
37  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
38  import com.puppycrawl.tools.checkstyle.JavaParser;
39  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
40  import com.puppycrawl.tools.checkstyle.api.DetailAST;
41  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
42  import com.puppycrawl.tools.checkstyle.api.Violation;
43  import com.puppycrawl.tools.checkstyle.checks.coding.UnusedLocalVariableCheck;
44  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck;
45  import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
46  import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck;
47  import com.puppycrawl.tools.checkstyle.checks.sizes.ParameterNumberCheck;
48  import com.puppycrawl.tools.checkstyle.checks.whitespace.AbstractParenPadCheck;
49  import com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck;
50  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
51  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
52  
53  public class SuppressWarningsHolderTest extends AbstractModuleTestSupport {
54  
55      @Override
56      protected String getPackageLocation() {
57          return "com/puppycrawl/tools/checkstyle/checks/suppresswarningsholder";
58      }
59  
60      @AfterEach
61      public void cleanUp() {
62          // clear cache that may have been set by tests
63  
64          new SuppressWarningsHolder().beginTree(null);
65  
66          final Map<String, String> map = TestUtil.getInternalStaticState(
67                  SuppressWarningsHolder.class, "CHECK_ALIAS_MAP");
68          map.clear();
69      }
70  
71      @Test
72      public void testGet() {
73          final SuppressWarningsHolder checkObj = new SuppressWarningsHolder();
74          final int[] expected = {TokenTypes.ANNOTATION};
75          assertWithMessage("Required token array differs from expected")
76              .that(checkObj.getRequiredTokens())
77              .isEqualTo(expected);
78          assertWithMessage("Required token array differs from expected")
79              .that(checkObj.getAcceptableTokens())
80              .isEqualTo(expected);
81      }
82  
83      @Test
84      public void testOnComplexAnnotations() throws Exception {
85          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
86  
87          verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder.java"), expected);
88      }
89  
90      @Test
91      public void testOnComplexAnnotationsNonConstant() throws Exception {
92          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
93  
94          verifyWithInlineConfigParser(
95                  getNonCompilablePath("InputSuppressWarningsHolderNonConstant.java"), expected);
96      }
97  
98      @Test
99      public void testCustomAnnotation() throws Exception {
100         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
101 
102         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder5.java"), expected);
103     }
104 
105     @Test
106     public void testAll() throws Exception {
107         final String[] expected = {
108             "21:23: "
109                     + getCheckMessage(TypecastParenPadCheck.class,
110                             AbstractParenPadCheck.MSG_WS_NOT_PRECEDED, ")"),
111         };
112 
113         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder6.java"), expected);
114     }
115 
116     @Test
117     public void testGetDefaultAlias() {
118         assertWithMessage("Default alias differs from expected")
119             .that(SuppressWarningsHolder.getDefaultAlias("SomeName"))
120             .isEqualTo("somename");
121         assertWithMessage("Default alias differs from expected")
122             .that(SuppressWarningsHolder.getDefaultAlias("SomeNameCheck"))
123             .isEqualTo("somename");
124     }
125 
126     @Test
127     public void testSetAliasListEmpty() {
128         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
129         holder.setAliasList("");
130         assertWithMessage("Empty alias list should not be set")
131             .that(SuppressWarningsHolder.getAlias(""))
132             .isEqualTo("");
133     }
134 
135     @Test
136     public void testSetAliasListCorrect() {
137         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
138         holder.setAliasList("alias=value");
139         assertWithMessage("Alias differs from expected")
140             .that(SuppressWarningsHolder.getAlias("alias"))
141             .isEqualTo("value");
142     }
143 
144     @Test
145     public void testSetAliasListWrong() {
146         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
147 
148         try {
149             holder.setAliasList("=SomeAlias");
150             assertWithMessage("Exception expected").fail();
151         }
152         catch (IllegalArgumentException ex) {
153             assertWithMessage("Error message is unexpected")
154                 .that(ex.getMessage())
155                 .isEqualTo("'=' expected in alias list item: =SomeAlias");
156         }
157     }
158 
159     @Test
160     public void testIsSuppressed() throws Exception {
161         populateHolder("MockEntry", 100, 100, 350, 350);
162         final AuditEvent event = createAuditEvent("check", 100, 10);
163 
164         assertWithMessage("Event is not suppressed")
165                 .that(SuppressWarningsHolder.isSuppressed(event))
166                 .isFalse();
167     }
168 
169     @Test
170     public void testIsSuppressedByName() throws Exception {
171         populateHolder("check", 100, 100, 350, 350);
172         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
173         final AuditEvent event = createAuditEvent("id", 110, 10);
174         holder.setAliasList(MemberNameCheck.class.getName() + "=check");
175 
176         assertWithMessage("Event is not suppressed")
177                 .that(SuppressWarningsHolder.isSuppressed(event))
178                 .isTrue();
179     }
180 
181     @Test
182     public void testIsSuppressedByModuleId() throws Exception {
183         populateHolder("check", 100, 100, 350, 350);
184         final AuditEvent event = createAuditEvent("check", 350, 350);
185 
186         assertWithMessage("Event is not suppressed")
187                 .that(SuppressWarningsHolder.isSuppressed(event))
188                 .isTrue();
189     }
190 
191     @Test
192     public void testIsSuppressedAfterEventEnd() throws Exception {
193         populateHolder("check", 100, 100, 350, 350);
194         final AuditEvent event = createAuditEvent("check", 350, 352);
195 
196         assertWithMessage("Event is not suppressed")
197                 .that(SuppressWarningsHolder.isSuppressed(event))
198                 .isFalse();
199     }
200 
201     @Test
202     public void testIsSuppressedAfterEventEnd2() throws Exception {
203         populateHolder("check", 100, 100, 350, 350);
204         final AuditEvent event = createAuditEvent("check", 400, 10);
205 
206         assertWithMessage("Event is not suppressed")
207                 .that(SuppressWarningsHolder.isSuppressed(event))
208                 .isFalse();
209     }
210 
211     @Test
212     public void testIsSuppressedAfterEventStart() throws Exception {
213         populateHolder("check", 100, 100, 350, 350);
214         final AuditEvent event = createAuditEvent("check", 100, 100);
215 
216         assertWithMessage("Event is not suppressed")
217                 .that(SuppressWarningsHolder.isSuppressed(event))
218                 .isTrue();
219     }
220 
221     @Test
222     public void testIsSuppressedAfterEventStart2() throws Exception {
223         populateHolder("check", 100, 100, 350, 350);
224         final AuditEvent event = createAuditEvent("check", 100, 0);
225 
226         assertWithMessage("Event is not suppressed")
227                 .that(SuppressWarningsHolder.isSuppressed(event))
228                 .isTrue();
229     }
230 
231     @Test
232     public void testIsSuppressedWithAllArgument() throws Exception {
233         populateHolder("all", 100, 100, 350, 350);
234 
235         final Checker source = new Checker();
236         final Violation firstViolationForTest =
237             new Violation(100, 10, null, null, null, "id", MemberNameCheck.class, "msg");
238         final AuditEvent firstEventForTest =
239             new AuditEvent(source, "fileName", firstViolationForTest);
240         assertWithMessage("Event is suppressed")
241                 .that(SuppressWarningsHolder.isSuppressed(firstEventForTest))
242                 .isFalse();
243 
244         final Violation secondViolationForTest =
245             new Violation(100, 150, null, null, null, "id", MemberNameCheck.class, "msg");
246         final AuditEvent secondEventForTest =
247             new AuditEvent(source, "fileName", secondViolationForTest);
248         assertWithMessage("Event is not suppressed")
249                 .that(SuppressWarningsHolder.isSuppressed(secondEventForTest))
250                 .isTrue();
251 
252         final Violation thirdViolationForTest =
253             new Violation(200, 1, null, null, null, "id", MemberNameCheck.class, "msg");
254         final AuditEvent thirdEventForTest =
255             new AuditEvent(source, "fileName", thirdViolationForTest);
256         assertWithMessage("Event is not suppressed")
257                 .that(SuppressWarningsHolder.isSuppressed(thirdEventForTest))
258                 .isTrue();
259     }
260 
261     @Test
262     public void testAnnotationInTry() throws Exception {
263         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
264 
265         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder2.java"), expected);
266     }
267 
268     @Test
269     public void testEmptyAnnotation() throws Exception {
270         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
271 
272         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder3.java"), expected);
273     }
274 
275     @Test
276     public void testGetAllAnnotationValuesWrongArg() throws ReflectiveOperationException {
277         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
278         final Method getAllAnnotationValues = holder.getClass()
279                 .getDeclaredMethod("getAllAnnotationValues", DetailAST.class);
280         getAllAnnotationValues.setAccessible(true);
281 
282         final DetailAstImpl methodDef = new DetailAstImpl();
283         methodDef.setType(TokenTypes.METHOD_DEF);
284         methodDef.setText("Method Def");
285         methodDef.setLineNo(0);
286         methodDef.setColumnNo(0);
287 
288         final DetailAstImpl lparen = new DetailAstImpl();
289         lparen.setType(TokenTypes.LPAREN);
290 
291         final DetailAstImpl parent = new DetailAstImpl();
292         parent.addChild(lparen);
293         parent.addChild(methodDef);
294 
295         try {
296             getAllAnnotationValues.invoke(holder, parent);
297             assertWithMessage("Exception expected").fail();
298         }
299         catch (ReflectiveOperationException ex) {
300             assertWithMessage("Error type is unexpected")
301                     .that(ex)
302                     .hasCauseThat()
303                     .isInstanceOf(IllegalArgumentException.class);
304             assertWithMessage("Error message is unexpected")
305                 .that(ex)
306                 .hasCauseThat()
307                 .hasMessageThat()
308                 .isEqualTo("Unexpected AST: Method Def[0x0]");
309         }
310     }
311 
312     @Test
313     public void testGetAnnotationValuesWrongArg() throws ReflectiveOperationException {
314         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
315         final Method getAllAnnotationValues = holder.getClass()
316                 .getDeclaredMethod("getAnnotationValues", DetailAST.class);
317         getAllAnnotationValues.setAccessible(true);
318 
319         final DetailAstImpl methodDef = new DetailAstImpl();
320         methodDef.setType(TokenTypes.METHOD_DEF);
321         methodDef.setText("Method Def");
322         methodDef.setLineNo(0);
323         methodDef.setColumnNo(0);
324 
325         try {
326             getAllAnnotationValues.invoke(holder, methodDef);
327             assertWithMessage("Exception expected").fail();
328         }
329         catch (ReflectiveOperationException ex) {
330             assertWithMessage("Error type is unexpected")
331                     .that(ex)
332                     .hasCauseThat()
333                     .isInstanceOf(IllegalArgumentException.class);
334             assertWithMessage("Error message is unexpected")
335                 .that(ex)
336                 .hasCauseThat()
337                 .hasMessageThat()
338                 .isEqualTo("Expression or annotation array initializer AST expected: "
339                         + "Method Def[0x0]");
340         }
341     }
342 
343     @Test
344     public void testGetAnnotationTargetWrongArg() throws ReflectiveOperationException {
345         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
346         final Method getAnnotationTarget = holder.getClass()
347                 .getDeclaredMethod("getAnnotationTarget", DetailAST.class);
348         getAnnotationTarget.setAccessible(true);
349 
350         final DetailAstImpl methodDef = new DetailAstImpl();
351         methodDef.setType(TokenTypes.METHOD_DEF);
352         methodDef.setText("Method Def");
353 
354         final DetailAstImpl parent = new DetailAstImpl();
355         parent.setType(TokenTypes.ASSIGN);
356         parent.setText("Parent ast");
357         parent.addChild(methodDef);
358         parent.setLineNo(0);
359         parent.setColumnNo(0);
360 
361         try {
362             getAnnotationTarget.invoke(holder, methodDef);
363             assertWithMessage("Exception expected").fail();
364         }
365         catch (ReflectiveOperationException ex) {
366             assertWithMessage("Error type is unexpected")
367                     .that(ex)
368                     .hasCauseThat()
369                     .isInstanceOf(IllegalArgumentException.class);
370             assertWithMessage("Error message is unexpected")
371                 .that(ex)
372                 .hasCauseThat()
373                 .hasMessageThat()
374                 .isEqualTo("Unexpected container AST: Parent ast[0x0]");
375         }
376     }
377 
378     @Test
379     public void testAstWithoutChildren() {
380         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
381         final DetailAstImpl methodDef = new DetailAstImpl();
382         methodDef.setType(TokenTypes.METHOD_DEF);
383 
384         try {
385             holder.visitToken(methodDef);
386             assertWithMessage("Exception expected").fail();
387         }
388         catch (IllegalArgumentException ex) {
389             assertWithMessage("Error message is unexpected")
390                 .that(ex.getMessage())
391                 .isEqualTo("Identifier AST expected, but get null.");
392         }
393     }
394 
395     @Test
396     public void testAnnotationWithFullName() throws Exception {
397         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
398 
399         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder4.java"), expected);
400     }
401 
402     @Test
403     public void testSuppressWarningsAsAnnotationProperty() throws Exception {
404         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
405 
406         verifyWithInlineConfigParser(getPath("InputSuppressWarningsHolder7.java"), expected);
407     }
408 
409     @Test
410     @SuppressWarnings("unchecked")
411     public void testClearState() throws Exception {
412         final SuppressWarningsHolder check = new SuppressWarningsHolder();
413 
414         final Optional<DetailAST> annotationDef = TestUtil.findTokenInAstByPredicate(
415                 JavaParser.parseFile(
416                     new File(getPath("InputSuppressWarningsHolder.java")),
417                     JavaParser.Options.WITHOUT_COMMENTS),
418             ast -> ast.getType() == TokenTypes.ANNOTATION);
419 
420         assertWithMessage("Ast should contain ANNOTATION")
421                 .that(annotationDef.isPresent())
422                 .isTrue();
423         assertWithMessage("State is not cleared on beginTree")
424                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, annotationDef.get(),
425                         "ENTRIES",
426                         entries -> ((ThreadLocal<List<Object>>) entries).get().isEmpty()))
427                 .isTrue();
428     }
429 
430     private static void populateHolder(String checkName, int firstLine,
431                                                          int firstColumn, int lastLine,
432                                                          int lastColumn) throws Exception {
433         final Class<?> entry = Class
434                 .forName("com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder$Entry");
435         final Constructor<?> entryConstr = entry.getDeclaredConstructor(String.class, int.class,
436                 int.class, int.class, int.class);
437         entryConstr.setAccessible(true);
438 
439         final Object entryInstance = entryConstr.newInstance(checkName, firstLine,
440                 firstColumn, lastLine, lastColumn);
441 
442         final ThreadLocal<List<Object>> entries = TestUtil
443                 .getInternalStaticState(SuppressWarningsHolder.class, "ENTRIES");
444         entries.get().add(entryInstance);
445     }
446 
447     private static AuditEvent createAuditEvent(String moduleId, int line, int column) {
448         final Checker source = new Checker();
449         final Violation violation = new Violation(line, column, null, null, null,
450                 moduleId, MemberNameCheck.class, "violation");
451         return new AuditEvent(source, "filename", violation);
452     }
453 
454     @Test
455     public void testSuppressWarningsTextBlocks() throws Exception {
456         final String pattern = "^[a-z][a-zA-Z0-9]*$";
457 
458         final String[] expected = {
459             "31:12: " + getCheckMessage(MemberNameCheck.class,
460                 AbstractNameCheck.MSG_INVALID_PATTERN, "STRING3", pattern),
461             "33:12: " + getCheckMessage(MemberNameCheck.class,
462                 AbstractNameCheck.MSG_INVALID_PATTERN, "STRING4", pattern),
463             "61:12: " + getCheckMessage(MemberNameCheck.class,
464                 AbstractNameCheck.MSG_INVALID_PATTERN, "STRING8", pattern),
465             };
466 
467         verifyWithInlineConfigParser(
468                 getNonCompilablePath("InputSuppressWarningsHolderTextBlocks.java"), expected);
469 
470     }
471 
472     @Test
473     public void testWithAndWithoutCheckSuffixDifferentCases() throws Exception {
474         final String pattern = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";
475         final String[] expected = {
476             "16:30: " + getCheckMessage(ConstantNameCheck.class,
477                 AbstractNameCheck.MSG_INVALID_PATTERN, "a", pattern),
478         };
479 
480         verifyWithInlineConfigParser(
481                 getPath("InputSuppressWarningsHolderWithAndWithoutCheckSuffixDifferentCases.java"),
482                 expected);
483     }
484 
485     @Test
486     public void testAliasList() throws Exception {
487         final String[] expected = {
488             "16:17: " + getCheckMessage(ParameterNumberCheck.class,
489                     ParameterNumberCheck.MSG_KEY, 7, 8),
490             "28:17: " + getCheckMessage(ParameterNumberCheck.class,
491                     ParameterNumberCheck.MSG_KEY, 7, 8),
492         };
493         verifyWithInlineConfigParser(
494                 getPath("InputSuppressWarningsHolderAlias.java"),
495                 expected);
496     }
497 
498     @Test
499     public void testAliasList2() throws Exception {
500         final String pattern = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";
501         final String[] expected = {
502             "16:29: " + getCheckMessage(ConstantNameCheck.class,
503                 AbstractNameCheck.MSG_INVALID_PATTERN, "a", pattern),
504             "19:30: " + getCheckMessage(ConstantNameCheck.class,
505                 AbstractNameCheck.MSG_INVALID_PATTERN, "b", pattern),
506         };
507 
508         verifyWithInlineConfigParser(
509                 getPath("InputSuppressWarningsHolderAlias2.java"),
510                 expected);
511     }
512 
513     @Test
514     public void testIdent() throws Exception {
515         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
516         verifyWithInlineConfigParser(
517                 getNonCompilablePath("InputSuppressWarningsHolder1.java"),
518                 expected);
519     }
520 
521     @Test
522     public void testIdent2() throws Exception {
523         final String[] expected = {
524             "37:9: " + getCheckMessage(UnusedLocalVariableCheck.class,
525                     MSG_UNUSED_LOCAL_VARIABLE, "a"),
526             "42:9: " + getCheckMessage(UnusedLocalVariableCheck.class,
527                     MSG_UNUSED_LOCAL_VARIABLE, "a"),
528         };
529         verifyWithInlineConfigParser(
530                 getNonCompilablePath("InputSuppressWarningsHolder2.java"),
531                 expected);
532     }
533 
534     @Test
535     public void test3() throws Exception {
536         final String pattern = "^[a-z][a-zA-Z0-9]*$";
537 
538         final String[] expected = {
539             "18:16: " + getCheckMessage(MemberNameCheck.class,
540                     AbstractNameCheck.MSG_INVALID_PATTERN, "K", pattern),
541         };
542         verifyWithInlineConfigParser(
543                 getPath("InputSuppressWarningsHolder8.java"),
544                 expected);
545     }
546 }