1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.filters;
21  
22  import static com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.fail;
26  
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.List;
30  import java.util.stream.Collectors;
31  
32  import org.junit.Assert;
33  import org.junit.Test;
34  import org.powermock.reflect.Whitebox;
35  
36  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
37  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
38  import com.puppycrawl.tools.checkstyle.TreeWalker;
39  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.api.Configuration;
42  import com.puppycrawl.tools.checkstyle.api.FileContents;
43  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
44  import com.puppycrawl.tools.checkstyle.checks.coding.IllegalCatchCheck;
45  import com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck;
46  import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
47  import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck;
48  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
49  import nl.jqno.equalsverifier.EqualsVerifier;
50  import nl.jqno.equalsverifier.EqualsVerifierReport;
51  
52  public class SuppressionCommentFilterTest
53      extends AbstractModuleTestSupport {
54  
55      private static final String[] ALL_MESSAGES = {
56          "13:17: "
57              + getCheckMessage(AbstractNameCheck.class,
58                  MSG_INVALID_PATTERN, "I", "^[a-z][a-zA-Z0-9]*$"),
59          "16:17: "
60              + getCheckMessage(AbstractNameCheck.class,
61                  MSG_INVALID_PATTERN, "J", "^[a-z][a-zA-Z0-9]*$"),
62          "19:17: "
63              + getCheckMessage(AbstractNameCheck.class,
64                  MSG_INVALID_PATTERN, "K", "^[a-z][a-zA-Z0-9]*$"),
65          "22:17: "
66              + getCheckMessage(AbstractNameCheck.class,
67                  MSG_INVALID_PATTERN, "L", "^[a-z][a-zA-Z0-9]*$"),
68          "23:30: "
69              + getCheckMessage(AbstractNameCheck.class,
70                  MSG_INVALID_PATTERN, "m", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
71          "27:17: "
72              + getCheckMessage(AbstractNameCheck.class,
73                  MSG_INVALID_PATTERN, "M2", "^[a-z][a-zA-Z0-9]*$"),
74          "28:30: "
75              + getCheckMessage(AbstractNameCheck.class,
76                  MSG_INVALID_PATTERN, "n", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
77          "32:17: "
78              + getCheckMessage(AbstractNameCheck.class,
79                  MSG_INVALID_PATTERN, "P", "^[a-z][a-zA-Z0-9]*$"),
80          "35:17: "
81              + getCheckMessage(AbstractNameCheck.class,
82                  MSG_INVALID_PATTERN, "Q", "^[a-z][a-zA-Z0-9]*$"),
83          "38:17: "
84              + getCheckMessage(AbstractNameCheck.class,
85                  MSG_INVALID_PATTERN, "R", "^[a-z][a-zA-Z0-9]*$"),
86          "39:30: "
87              + getCheckMessage(AbstractNameCheck.class,
88                  MSG_INVALID_PATTERN, "s", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
89          "43:17: "
90              + getCheckMessage(AbstractNameCheck.class,
91                  MSG_INVALID_PATTERN, "T", "^[a-z][a-zA-Z0-9]*$"),
92          "64:23: "
93              + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
94          "71:11: "
95              + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
96          "77:11: "
97              + getCheckMessage(IllegalCatchCheck.class,
98                  IllegalCatchCheck.MSG_KEY, "RuntimeException"),
99          "78:11: "
100             + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
101         "86:31: "
102             + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
103     };
104 
105     @Override
106     protected String getPackageLocation() {
107         return "com/puppycrawl/tools/checkstyle/filters/suppressioncommentfilter";
108     }
109 
110     @Test
111     public void testNone() throws Exception {
112         final DefaultConfiguration filterConfig = null;
113         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
114         verifySuppressed(filterConfig, suppressed);
115     }
116 
117     //Suppress all checks between default comments
118     @Test
119     public void testDefault() throws Exception {
120         final DefaultConfiguration filterConfig =
121             createModuleConfig(SuppressionCommentFilter.class);
122         final String[] suppressed = {
123             "16:17: "
124                 + getCheckMessage(AbstractNameCheck.class,
125                     MSG_INVALID_PATTERN, "J", "^[a-z][a-zA-Z0-9]*$"),
126             "43:17: "
127                 + getCheckMessage(AbstractNameCheck.class,
128                     MSG_INVALID_PATTERN, "T", "^[a-z][a-zA-Z0-9]*$"),
129             "64:23: "
130                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
131             "71:11: "
132                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
133             "86:31: "
134                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
135         };
136         verifySuppressed(filterConfig, suppressed);
137     }
138 
139     @Test
140     public void testCheckC() throws Exception {
141         final DefaultConfiguration filterConfig =
142             createModuleConfig(SuppressionCommentFilter.class);
143         filterConfig.addAttribute("checkC", "false");
144         final String[] suppressed = {
145             "43:17: "
146                 + getCheckMessage(AbstractNameCheck.class,
147                     MSG_INVALID_PATTERN, "T", "^[a-z][a-zA-Z0-9]*$"),
148             "64:23: "
149                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
150             "71:11: "
151                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
152         };
153         verifySuppressed(filterConfig, suppressed);
154     }
155 
156     @Test
157     public void testCheckCpp() throws Exception {
158         final DefaultConfiguration filterConfig =
159             createModuleConfig(SuppressionCommentFilter.class);
160         filterConfig.addAttribute("checkCPP", "false");
161         final String[] suppressed = {
162             "16:17: "
163                 + getCheckMessage(AbstractNameCheck.class,
164                     MSG_INVALID_PATTERN, "J", "^[a-z][a-zA-Z0-9]*$"),
165             "86:31: "
166                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
167         };
168         verifySuppressed(filterConfig, suppressed);
169     }
170 
171     //Suppress all checks between CS_OFF and CS_ON
172     @Test
173     public void testOffFormat() throws Exception {
174         final DefaultConfiguration filterConfig =
175             createModuleConfig(SuppressionCommentFilter.class);
176         filterConfig.addAttribute("offCommentFormat", "CS_OFF");
177         filterConfig.addAttribute("onCommentFormat", "CS_ON");
178         final String[] suppressed = {
179             "32:17: "
180                 + getCheckMessage(AbstractNameCheck.class,
181                     MSG_INVALID_PATTERN, "P", "^[a-z][a-zA-Z0-9]*$"),
182             "38:17: "
183                 + getCheckMessage(AbstractNameCheck.class,
184                     MSG_INVALID_PATTERN, "R", "^[a-z][a-zA-Z0-9]*$"),
185             "39:30: "
186                 + getCheckMessage(AbstractNameCheck.class,
187                     MSG_INVALID_PATTERN, "s", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
188             "42:17: "
189                 + getCheckMessage(AbstractNameCheck.class,
190                     MSG_INVALID_PATTERN, "T", "^[a-z][a-zA-Z0-9]*$"),
191         };
192         verifySuppressed(filterConfig, suppressed);
193     }
194 
195     //Test suppression of checks of only one type
196     //Suppress only ConstantNameCheck between CS_OFF and CS_ON
197     @Test
198     public void testOffFormatCheck() throws Exception {
199         final DefaultConfiguration filterConfig =
200             createModuleConfig(SuppressionCommentFilter.class);
201         filterConfig.addAttribute("offCommentFormat", "CS_OFF");
202         filterConfig.addAttribute("onCommentFormat", "CS_ON");
203         filterConfig.addAttribute("checkFormat", "ConstantNameCheck");
204         final String[] suppressed = {
205             "39:30: "
206                 + getCheckMessage(AbstractNameCheck.class,
207                     MSG_INVALID_PATTERN, "s", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
208         };
209         verifySuppressed(filterConfig, suppressed);
210     }
211 
212     @Test
213     public void testArgumentSuppression() throws Exception {
214         final DefaultConfiguration filterConfig =
215             createModuleConfig(SuppressionCommentFilter.class);
216         filterConfig.addAttribute("offCommentFormat", "IllegalCatchCheck OFF\\: (\\w+)");
217         filterConfig.addAttribute("onCommentFormat", "IllegalCatchCheck ON\\: (\\w+)");
218         filterConfig.addAttribute("checkFormat", "IllegalCatchCheck");
219         // -@cs[CheckstyleTestMakeup] need to test dynamic property
220         filterConfig.addAttribute("messageFormat",
221                 "^" + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "$1")
222                         + "*$");
223         final String[] suppressed = {
224             "78:11: "
225                 + getCheckMessage(IllegalCatchCheck.class, IllegalCatchCheck.MSG_KEY, "Exception"),
226         };
227         verifySuppressed(filterConfig, suppressed);
228     }
229 
230     @Test
231     public void testExpansion() throws Exception {
232         final DefaultConfiguration filterConfig =
233             createModuleConfig(SuppressionCommentFilter.class);
234         filterConfig.addAttribute("offCommentFormat", "CSOFF\\: ([\\w\\|]+)");
235         filterConfig.addAttribute("onCommentFormat", "CSON\\: ([\\w\\|]+)");
236         filterConfig.addAttribute("checkFormat", "$1");
237         final String[] suppressed = {
238             "22:17: "
239                 + getCheckMessage(AbstractNameCheck.class,
240                     MSG_INVALID_PATTERN, "L", "^[a-z][a-zA-Z0-9]*$"),
241             "23:30: "
242                 + getCheckMessage(AbstractNameCheck.class,
243                     MSG_INVALID_PATTERN, "m", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
244             "28:30: "
245                 + getCheckMessage(AbstractNameCheck.class,
246                     MSG_INVALID_PATTERN, "n", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
247         };
248         verifySuppressed(filterConfig, suppressed);
249     }
250 
251     @Test
252     public void testMessage() throws Exception {
253         final DefaultConfiguration filterConfig =
254             createModuleConfig(SuppressionCommentFilter.class);
255         filterConfig.addAttribute("onCommentFormat", "UNUSED ON\\: (\\w+)");
256         filterConfig.addAttribute("offCommentFormat", "UNUSED OFF\\: (\\w+)");
257         filterConfig.addAttribute("checkFormat", "Unused");
258         filterConfig.addAttribute("messageFormat", "^Unused \\w+ '$1'.$");
259         final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
260         verifySuppressed(filterConfig, suppressed);
261     }
262 
263     private void verifySuppressed(Configuration moduleConfig,
264             String... aSuppressed)
265             throws Exception {
266         verifySuppressed(moduleConfig, getPath("InputSuppressionCommentFilter.java"),
267                ALL_MESSAGES, aSuppressed);
268     }
269 
270     private void verifySuppressed(Configuration moduleConfig, String fileName,
271             String[] expectedViolations, String... suppressedViolations) throws Exception {
272         final DefaultConfiguration memberNameCheckConfig =
273                 createModuleConfig(MemberNameCheck.class);
274         memberNameCheckConfig.addAttribute("id", "ignore");
275 
276         final DefaultConfiguration constantNameCheckConfig =
277             createModuleConfig(ConstantNameCheck.class);
278         constantNameCheckConfig.addAttribute("id", null);
279 
280         final DefaultConfiguration treewalkerConfig = createModuleConfig(TreeWalker.class);
281         treewalkerConfig.addChild(memberNameCheckConfig);
282         treewalkerConfig.addChild(constantNameCheckConfig);
283         treewalkerConfig.addChild(createModuleConfig(IllegalCatchCheck.class));
284 
285         if (moduleConfig != null) {
286             treewalkerConfig.addChild(moduleConfig);
287         }
288 
289         final DefaultConfiguration checkerConfig = createRootConfig(treewalkerConfig);
290 
291         verify(checkerConfig, fileName,
292                 removeSuppressed(expectedViolations, suppressedViolations));
293     }
294 
295     private static String[] removeSuppressed(String[] from, String... remove) {
296         final Collection<String> coll = Arrays.stream(from).collect(Collectors.toList());
297         coll.removeAll(Arrays.asList(remove));
298         return coll.toArray(CommonUtil.EMPTY_STRING_ARRAY);
299     }
300 
301     @Test
302     public void testEqualsAndHashCodeOfTagClass() {
303         final EqualsVerifierReport ev = EqualsVerifier.forClass(SuppressionCommentFilter.Tag.class)
304                 .usingGetClass().report();
305         assertEquals("Error: " + ev.getMessage(), EqualsVerifierReport.SUCCESS, ev);
306     }
307 
308     @Test
309     public void testToStringOfTagClass() {
310         final SuppressionCommentFilter.Tag tag = new SuppressionCommentFilter.Tag(
311                 0, 1, "text",
312                 SuppressionCommentFilter.TagType.OFF, new SuppressionCommentFilter()
313         );
314 
315         assertEquals("Invalid toString result",
316             "Tag[text='text', line=0, column=1, type=OFF,"
317                     + " tagCheckRegexp=.*, tagMessageRegexp=null]", tag.toString());
318     }
319 
320     @Test
321     public void testInvalidCheckFormat() throws Exception {
322         final DefaultConfiguration filterConfig =
323             createModuleConfig(SuppressionCommentFilter.class);
324         filterConfig.addAttribute("checkFormat", "e[l");
325 
326         try {
327             final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
328             verifySuppressed(filterConfig, suppressed);
329             fail("Exception is expected");
330         }
331         catch (CheckstyleException ex) {
332             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
333             assertEquals("Invalid exception message",
334                 "unable to parse expanded comment e[l", cause.getMessage());
335         }
336     }
337 
338     @Test
339     public void testInvalidMessageFormat() throws Exception {
340         final DefaultConfiguration filterConfig =
341             createModuleConfig(SuppressionCommentFilter.class);
342         filterConfig.addAttribute("messageFormat", "e[l");
343 
344         try {
345             final String[] suppressed = CommonUtil.EMPTY_STRING_ARRAY;
346             verifySuppressed(filterConfig, suppressed);
347             fail("Exception is expected");
348         }
349         catch (CheckstyleException ex) {
350             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
351             assertEquals("Invalid exception message",
352                 "unable to parse expanded comment e[l", cause.getMessage());
353         }
354     }
355 
356     @Test
357     public void testAcceptNullLocalizedMessage() {
358         final SuppressionCommentFilter filter = new SuppressionCommentFilter();
359         final TreeWalkerAuditEvent auditEvent = new TreeWalkerAuditEvent(null, null, null, null);
360         Assert.assertTrue("Filter should accept audit event", filter.accept(auditEvent));
361         Assert.assertNull("File name should not be null", auditEvent.getFileName());
362     }
363 
364     @Test
365     public void testSuppressById() throws Exception {
366         final DefaultConfiguration filterConfig =
367             createModuleConfig(SuppressionCommentFilter.class);
368         filterConfig.addAttribute("offCommentFormat", "CSOFF (\\w+) \\(\\w+\\)");
369         filterConfig.addAttribute("onCommentFormat", "CSON (\\w+)");
370         filterConfig.addAttribute("checkFormat", "$1");
371         final String[] suppressedViolationMessages = {
372             "6:17: "
373                 + getCheckMessage(AbstractNameCheck.class,
374                     MSG_INVALID_PATTERN, "A1", "^[a-z][a-zA-Z0-9]*$"),
375             "12:9: "
376                 + getCheckMessage(AbstractNameCheck.class,
377                     MSG_INVALID_PATTERN, "line_length", "^[a-z][a-zA-Z0-9]*$"),
378             };
379         final String[] expectedViolationMessages = {
380             "6:17: "
381                 + getCheckMessage(AbstractNameCheck.class,
382                     MSG_INVALID_PATTERN, "A1", "^[a-z][a-zA-Z0-9]*$"),
383             "9:30: "
384                 + getCheckMessage(AbstractNameCheck.class,
385                     MSG_INVALID_PATTERN, "abc", "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"),
386             "12:9: "
387                 + getCheckMessage(AbstractNameCheck.class,
388                     MSG_INVALID_PATTERN, "line_length", "^[a-z][a-zA-Z0-9]*$"),
389             "15:18: "
390                 + getCheckMessage(AbstractNameCheck.class,
391                     MSG_INVALID_PATTERN, "ID", "^[a-z][a-zA-Z0-9]*$"),
392             };
393 
394         verifySuppressed(filterConfig, getPath("InputSuppressionCommentFilterSuppressById.java"),
395                 expectedViolationMessages, suppressedViolationMessages);
396     }
397 
398     @Test
399     public void testFindNearestMatchDontAllowSameColumn() {
400         final SuppressionCommentFilter suppressionCommentFilter = new SuppressionCommentFilter();
401         final FileContents contents =
402                 new FileContents("filename", "//CHECKSTYLE:OFF: ConstantNameCheck", "line2");
403         contents.reportSingleLineComment(1, 0);
404         final TreeWalkerAuditEvent dummyEvent = new TreeWalkerAuditEvent(contents, "filename",
405                 new LocalizedMessage(1, null, null, null, null, Object.class, null), null);
406         final boolean result = suppressionCommentFilter.accept(dummyEvent);
407         assertFalse("Filter should not accept event", result);
408     }
409 
410     @Test
411     public void testTagsAreClearedEachRun() {
412         final SuppressionCommentFilter suppressionCommentFilter = new SuppressionCommentFilter();
413         final FileContents contents =
414                 new FileContents("filename", "//CHECKSTYLE:OFF", "line2");
415         contents.reportSingleLineComment(1, 0);
416         final TreeWalkerAuditEvent dummyEvent = new TreeWalkerAuditEvent(contents, "filename",
417                 new LocalizedMessage(1, null, null, null, null, Object.class, null), null);
418         suppressionCommentFilter.accept(dummyEvent);
419         final FileContents contents2 =
420                 new FileContents("filename2", "some line", "//CHECKSTYLE:OFF");
421         contents2.reportSingleLineComment(2, 0);
422         final TreeWalkerAuditEvent dummyEvent2 = new TreeWalkerAuditEvent(contents2, "filename",
423                 new LocalizedMessage(1, null, null, null, null, Object.class, null), null);
424         suppressionCommentFilter.accept(dummyEvent2);
425         final List<SuppressionCommentFilter.Tag> tags =
426                 Whitebox.getInternalState(suppressionCommentFilter, "tags");
427         assertEquals("Invalid tags size", 1, tags.size());
428     }
429 
430 }