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.api;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
24  
25  import java.io.File;
26  import java.nio.charset.Charset;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.SortedSet;
34  
35  import org.junit.jupiter.api.Test;
36  
37  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
38  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
39  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
40  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
41  
42  public class AbstractCheckTest extends AbstractModuleTestSupport {
43  
44      @Override
45      protected String getPackageLocation() {
46          return "com/puppycrawl/tools/checkstyle/api/abstractcheck";
47      }
48  
49      @Test
50      public void testGetRequiredTokens() {
51          final AbstractCheck check = new AbstractCheck() {
52              @Override
53              public int[] getDefaultTokens() {
54                  return CommonUtil.EMPTY_INT_ARRAY;
55              }
56  
57              @Override
58              public int[] getAcceptableTokens() {
59                  return getDefaultTokens();
60              }
61  
62              @Override
63              public int[] getRequiredTokens() {
64                  return getDefaultTokens();
65              }
66          };
67          // Eventually it will become clear abstract method
68          assertWithMessage("Invalid number of tokens, should be empty")
69                  .that(check.getRequiredTokens())
70                  .isEmpty();
71      }
72  
73      @Test
74      public void testGetAcceptable() {
75          final AbstractCheck check = new AbstractCheck() {
76              @Override
77              public int[] getDefaultTokens() {
78                  return CommonUtil.EMPTY_INT_ARRAY;
79              }
80  
81              @Override
82              public int[] getAcceptableTokens() {
83                  return getDefaultTokens();
84              }
85  
86              @Override
87              public int[] getRequiredTokens() {
88                  return getDefaultTokens();
89              }
90          };
91          // Eventually it will become clear abstract method
92          assertWithMessage("Invalid number of tokens, should be empty")
93                  .that(check.getAcceptableTokens())
94                  .isEmpty();
95      }
96  
97      @Test
98      public void testCommentNodes() {
99          final AbstractCheck check = new AbstractCheck() {
100             @Override
101             public int[] getDefaultTokens() {
102                 return CommonUtil.EMPTY_INT_ARRAY;
103             }
104 
105             @Override
106             public int[] getAcceptableTokens() {
107                 return getDefaultTokens();
108             }
109 
110             @Override
111             public int[] getRequiredTokens() {
112                 return getDefaultTokens();
113             }
114         };
115 
116         assertWithMessage("unexpected result")
117                 .that(check.isCommentNodesRequired())
118                 .isFalse();
119     }
120 
121     @Test
122     public void testTokenNames() {
123         final AbstractCheck check = new AbstractCheck() {
124             @Override
125             public int[] getDefaultTokens() {
126                 return CommonUtil.EMPTY_INT_ARRAY;
127             }
128 
129             @Override
130             public int[] getAcceptableTokens() {
131                 return getDefaultTokens();
132             }
133 
134             @Override
135             public int[] getRequiredTokens() {
136                 return getDefaultTokens();
137             }
138         };
139 
140         check.setTokens("IDENT, EXPR, ELIST");
141         assertWithMessage("unexpected result")
142                 .that(check.getTokenNames())
143                 .containsExactly("IDENT, EXPR, ELIST");
144     }
145 
146     @Test
147     public void testVisitToken() {
148         final VisitCounterCheck check = new VisitCounterCheck();
149         // Eventually it will become clear abstract method
150         check.visitToken(null);
151 
152         assertWithMessage("expected call count")
153                 .that(check.count)
154                 .isEqualTo(1);
155     }
156 
157     @Test
158     public void testGetLine() throws Exception {
159         final AbstractCheck check = new AbstractCheck() {
160             @Override
161             public int[] getDefaultTokens() {
162                 return CommonUtil.EMPTY_INT_ARRAY;
163             }
164 
165             @Override
166             public int[] getAcceptableTokens() {
167                 return getDefaultTokens();
168             }
169 
170             @Override
171             public int[] getRequiredTokens() {
172                 return getDefaultTokens();
173             }
174         };
175         check.setFileContents(new FileContents(new FileText(
176             new File(getPath("InputAbstractCheckTestFileContents.java")),
177             Charset.defaultCharset().name())));
178 
179         assertWithMessage("Invalid line content")
180                 .that(check.getLine(9))
181                 .isEqualTo(" * I'm a javadoc");
182     }
183 
184     @Test
185     public void testGetLineCodePoints() throws Exception {
186         final AbstractCheck check = new AbstractCheck() {
187             @Override
188             public int[] getDefaultTokens() {
189                 return CommonUtil.EMPTY_INT_ARRAY;
190             }
191 
192             @Override
193             public int[] getAcceptableTokens() {
194                 return getDefaultTokens();
195             }
196 
197             @Override
198             public int[] getRequiredTokens() {
199                 return getDefaultTokens();
200             }
201         };
202         final FileContents fileContents = new FileContents(new FileText(
203                 new File(getPath("InputAbstractCheckTestFileContents.java")),
204                 Charset.defaultCharset().name()));
205         check.setFileContents(fileContents);
206 
207         final int[] expectedCodePoints = "    public int getVariable() {".codePoints().toArray();
208         assertWithMessage("Invalid line content")
209                 .that(check.getLineCodePoints(18))
210                 .isEqualTo(expectedCodePoints);
211     }
212 
213     @Test
214     public void testGetTabWidth() {
215         final AbstractCheck check = new AbstractCheck() {
216             @Override
217             public int[] getDefaultTokens() {
218                 return CommonUtil.EMPTY_INT_ARRAY;
219             }
220 
221             @Override
222             public int[] getAcceptableTokens() {
223                 return getDefaultTokens();
224             }
225 
226             @Override
227             public int[] getRequiredTokens() {
228                 return getDefaultTokens();
229             }
230         };
231         final int tabWidth = 4;
232         check.setTabWidth(tabWidth);
233 
234         assertWithMessage("Invalid tab width")
235                 .that(check.getTabWidth())
236                 .isEqualTo(tabWidth);
237     }
238 
239     @Test
240     public void testFileContents() {
241         final AbstractCheck check = new AbstractCheck() {
242             @Override
243             public int[] getDefaultTokens() {
244                 return CommonUtil.EMPTY_INT_ARRAY;
245             }
246 
247             @Override
248             public int[] getAcceptableTokens() {
249                 return getDefaultTokens();
250             }
251 
252             @Override
253             public int[] getRequiredTokens() {
254                 return getDefaultTokens();
255             }
256         };
257         final String[] lines = {"test"};
258         final FileContents fileContents = new FileContents(
259                 new FileText(new File("filename"), Arrays.asList(lines)));
260         check.setFileContents(fileContents);
261 
262         assertWithMessage("Invalid file contents")
263                 .that(check.getFileContents())
264                 .isEqualTo(fileContents);
265         assertWithMessage("Invalid lines")
266                 .that(check.getLines())
267                 .isEqualTo(lines);
268     }
269 
270     @Test
271     public void testGetAcceptableTokens() {
272         final int[] defaultTokens = {TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
273         final int[] acceptableTokens = {TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
274         final int[] requiredTokens = {TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
275         final AbstractCheck check = new AbstractCheck() {
276             @Override
277             public int[] getDefaultTokens() {
278                 return defaultTokens;
279             }
280 
281             @Override
282             public int[] getAcceptableTokens() {
283                 return acceptableTokens;
284             }
285 
286             @Override
287             public int[] getRequiredTokens() {
288                 return requiredTokens;
289             }
290         };
291 
292         assertWithMessage("Invalid default tokens")
293                 .that(check.getDefaultTokens())
294                 .isEqualTo(defaultTokens);
295         assertWithMessage("Invalid acceptable tokens")
296                 .that(check.getAcceptableTokens())
297                 .isEqualTo(acceptableTokens);
298         assertWithMessage("Invalid required tokens")
299                 .that(check.getRequiredTokens())
300                 .isEqualTo(requiredTokens);
301     }
302 
303     @Test
304     public void testClearViolations() {
305         final AbstractCheck check = new DummyAbstractCheck();
306 
307         check.log(1, "key", "args");
308         assertWithMessage("Invalid violation size")
309                 .that(check.getViolations())
310                 .hasSize(1);
311         check.clearViolations();
312         assertWithMessage("Invalid violation size")
313                 .that(check.getViolations())
314                 .isEmpty();
315     }
316 
317     @Test
318     public void testLineColumnLog() throws Exception {
319         final ViolationCheck check = new ViolationCheck();
320         check.configure(new DefaultConfiguration("check"));
321         final File file = new File("fileName");
322         final FileText theText = new FileText(file, Collections.singletonList("test123"));
323 
324         check.setFileContents(new FileContents(theText));
325         check.clearViolations();
326         check.visitToken(null);
327 
328         final SortedSet<Violation> internalViolations = check.getViolations();
329 
330         assertWithMessage("Internal violation should only have 2")
331                 .that(internalViolations)
332                 .hasSize(2);
333 
334         final Iterator<Violation> iterator = internalViolations.iterator();
335 
336         final Violation firstViolation = iterator.next();
337         assertWithMessage("expected line")
338                 .that(firstViolation.getLineNo())
339                 .isEqualTo(1);
340         assertWithMessage("expected column")
341                 .that(firstViolation.getColumnNo())
342                 .isEqualTo(0);
343 
344         final Violation secondViolation = iterator.next();
345         assertWithMessage("expected line")
346                 .that(secondViolation.getLineNo())
347                 .isEqualTo(1);
348         assertWithMessage("expected column")
349                 .that(secondViolation.getColumnNo())
350                 .isEqualTo(6);
351     }
352 
353     @Test
354     public void testAstLog() throws Exception {
355         final ViolationAstCheck check = new ViolationAstCheck();
356         check.configure(new DefaultConfiguration("check"));
357         final File file = new File("fileName");
358         final FileText theText = new FileText(file, Collections.singletonList("test123"));
359 
360         check.setFileContents(new FileContents(theText));
361         check.clearViolations();
362 
363         final DetailAstImpl ast = new DetailAstImpl();
364         ast.setLineNo(1);
365         ast.setColumnNo(4);
366         check.visitToken(ast);
367 
368         final SortedSet<Violation> internalViolations = check.getViolations();
369 
370         assertWithMessage("Internal violation should only have 1")
371                 .that(internalViolations)
372                 .hasSize(1);
373 
374         final Violation firstViolation = internalViolations.iterator().next();
375         assertWithMessage("expected line")
376                 .that(firstViolation.getLineNo())
377                 .isEqualTo(1);
378         assertWithMessage("expected column")
379                 .that(firstViolation.getColumnNo())
380                 .isEqualTo(5);
381     }
382 
383     @Test
384     public void testCheck() throws Exception {
385         final String[] expected = {
386             "6:1: Violation.",
387         };
388         verifyWithInlineConfigParser(getPath("InputAbstractCheckTestFileContents.java"), expected);
389     }
390 
391     /**
392      * S2384 - Mutable members should not be stored or returned directly.
393      * Sonarqube rule is valid, a pure unit test is required as this condition can't be recreated in
394      * a test with checks and input file as none of the checks try to modify the tokens.
395      */
396     @Test
397     public void testTokensAreUnmodifiable() {
398         final DummyAbstractCheck check = new DummyAbstractCheck();
399         final Set<String> tokenNameSet = check.getTokenNames();
400         final Exception ex = getExpectedThrowable(UnsupportedOperationException.class,
401                 () -> tokenNameSet.add(""));
402         assertWithMessage("Exception class is not expected")
403                 .that(ex.getClass())
404                 .isEqualTo(UnsupportedOperationException.class);
405     }
406 
407     public static final class DummyAbstractCheck extends AbstractCheck {
408 
409         private static final int[] DUMMY_ARRAY = {6};
410 
411         @Override
412         public int[] getDefaultTokens() {
413             return Arrays.copyOf(DUMMY_ARRAY, 1);
414         }
415 
416         @Override
417         public int[] getAcceptableTokens() {
418             return Arrays.copyOf(DUMMY_ARRAY, 1);
419         }
420 
421         @Override
422         public int[] getRequiredTokens() {
423             return Arrays.copyOf(DUMMY_ARRAY, 1);
424         }
425 
426         @Override
427         protected Map<String, String> getCustomMessages() {
428             final Map<String, String> messages = new HashMap<>();
429             messages.put("key", "value");
430             return messages;
431         }
432 
433     }
434 
435     public static final class VisitCounterCheck extends AbstractCheck {
436 
437         private int count;
438 
439         @Override
440         public int[] getDefaultTokens() {
441             return CommonUtil.EMPTY_INT_ARRAY;
442         }
443 
444         @Override
445         public int[] getAcceptableTokens() {
446             return CommonUtil.EMPTY_INT_ARRAY;
447         }
448 
449         @Override
450         public int[] getRequiredTokens() {
451             return CommonUtil.EMPTY_INT_ARRAY;
452         }
453 
454         @Override
455         public void visitToken(DetailAST ast) {
456             super.visitToken(ast);
457             count++;
458         }
459     }
460 
461     public static class ViolationCheck extends AbstractCheck {
462 
463         private static final String MSG_KEY = "Violation.";
464 
465         @Override
466         public int[] getDefaultTokens() {
467             return CommonUtil.EMPTY_INT_ARRAY;
468         }
469 
470         @Override
471         public int[] getAcceptableTokens() {
472             return CommonUtil.EMPTY_INT_ARRAY;
473         }
474 
475         @Override
476         public int[] getRequiredTokens() {
477             return CommonUtil.EMPTY_INT_ARRAY;
478         }
479 
480         @Override
481         public void visitToken(DetailAST ast) {
482             log(1, 5, MSG_KEY);
483             log(1, MSG_KEY);
484         }
485 
486     }
487 
488     public static class ViolationAstCheck extends AbstractCheck {
489 
490         private static final String MSG_KEY = "Violation.";
491 
492         @Override
493         public int[] getDefaultTokens() {
494             return getRequiredTokens();
495         }
496 
497         @Override
498         public int[] getAcceptableTokens() {
499             return getRequiredTokens();
500         }
501 
502         @Override
503         public int[] getRequiredTokens() {
504             return new int[] {
505                 TokenTypes.PACKAGE_DEF,
506             };
507         }
508 
509         @Override
510         public void visitToken(DetailAST ast) {
511             log(ast, MSG_KEY);
512         }
513 
514     }
515 
516 }