View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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      public 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         assertWithMessage("expected severity level")
344                 .that(firstViolation.getSeverityLevel())
345                 .isEqualTo(SeverityLevel.ERROR);
346 
347         final Violation secondViolation = iterator.next();
348         assertWithMessage("expected line")
349                 .that(secondViolation.getLineNo())
350                 .isEqualTo(1);
351         assertWithMessage("expected column")
352                 .that(secondViolation.getColumnNo())
353                 .isEqualTo(6);
354         assertWithMessage("expected severity level")
355                 .that(secondViolation.getSeverityLevel())
356                 .isEqualTo(SeverityLevel.ERROR);
357     }
358 
359     @Test
360     public void testAstLog() throws Exception {
361         final ViolationAstCheck check = new ViolationAstCheck();
362         check.configure(new DefaultConfiguration("check"));
363         final File file = new File("fileName");
364         final FileText theText = new FileText(file, Collections.singletonList("test123"));
365 
366         check.setFileContents(new FileContents(theText));
367         check.clearViolations();
368 
369         final DetailAstImpl ast = new DetailAstImpl();
370         ast.setLineNo(1);
371         ast.setColumnNo(4);
372         check.visitToken(ast);
373 
374         final SortedSet<Violation> internalViolations = check.getViolations();
375 
376         assertWithMessage("Internal violation should only have 1")
377                 .that(internalViolations)
378                 .hasSize(1);
379 
380         final Violation firstViolation = internalViolations.getFirst();
381         assertWithMessage("expected line")
382                 .that(firstViolation.getLineNo())
383                 .isEqualTo(1);
384         assertWithMessage("expected column")
385                 .that(firstViolation.getColumnNo())
386                 .isEqualTo(5);
387         assertWithMessage("expected severity level")
388                 .that(firstViolation.getSeverityLevel())
389                 .isEqualTo(SeverityLevel.ERROR);
390     }
391 
392     @Test
393     public void testCheck() throws Exception {
394         final String[] expected = {
395             "6:1: Violation.",
396         };
397         verifyWithInlineConfigParser(getPath("InputAbstractCheckTestFileContents.java"), expected);
398     }
399 
400     /**
401      * S2384 - Mutable members should not be stored or returned directly.
402      * Sonarqube rule is valid, a pure unit test is required as this condition can't be recreated in
403      * a test with checks and input file as none of the checks try to modify the tokens.
404      */
405     @Test
406     public void testTokensAreUnmodifiable() {
407         final DummyAbstractCheck check = new DummyAbstractCheck();
408         final Set<String> tokenNameSet = check.getTokenNames();
409         final Exception ex = getExpectedThrowable(UnsupportedOperationException.class,
410                 () -> tokenNameSet.add(""));
411         assertWithMessage("Exception class is not expected")
412                 .that(ex.getClass())
413                 .isEqualTo(UnsupportedOperationException.class);
414     }
415 
416     public static final class DummyAbstractCheck extends AbstractCheck {
417 
418         private static final int[] DUMMY_ARRAY = {6};
419 
420         @Override
421         public int[] getDefaultTokens() {
422             return Arrays.copyOf(DUMMY_ARRAY, 1);
423         }
424 
425         @Override
426         public int[] getAcceptableTokens() {
427             return Arrays.copyOf(DUMMY_ARRAY, 1);
428         }
429 
430         @Override
431         public int[] getRequiredTokens() {
432             return Arrays.copyOf(DUMMY_ARRAY, 1);
433         }
434 
435         @Override
436         protected Map<String, String> getCustomMessages() {
437             final Map<String, String> messages = new HashMap<>();
438             messages.put("key", "value");
439             return messages;
440         }
441 
442     }
443 
444     public static final class VisitCounterCheck extends AbstractCheck {
445 
446         private int count;
447 
448         @Override
449         public int[] getDefaultTokens() {
450             return CommonUtil.EMPTY_INT_ARRAY;
451         }
452 
453         @Override
454         public int[] getAcceptableTokens() {
455             return CommonUtil.EMPTY_INT_ARRAY;
456         }
457 
458         @Override
459         public int[] getRequiredTokens() {
460             return CommonUtil.EMPTY_INT_ARRAY;
461         }
462 
463         @Override
464         public void visitToken(DetailAST ast) {
465             super.visitToken(ast);
466             count++;
467         }
468     }
469 
470     public static class ViolationCheck extends AbstractCheck {
471 
472         private static final String MSG_KEY = "Violation.";
473 
474         @Override
475         public int[] getDefaultTokens() {
476             return CommonUtil.EMPTY_INT_ARRAY;
477         }
478 
479         @Override
480         public int[] getAcceptableTokens() {
481             return CommonUtil.EMPTY_INT_ARRAY;
482         }
483 
484         @Override
485         public int[] getRequiredTokens() {
486             return CommonUtil.EMPTY_INT_ARRAY;
487         }
488 
489         @Override
490         public void visitToken(DetailAST ast) {
491             log(1, 5, MSG_KEY);
492             log(1, MSG_KEY);
493         }
494 
495     }
496 
497     public static class ViolationAstCheck extends AbstractCheck {
498 
499         private static final String MSG_KEY = "Violation.";
500 
501         @Override
502         public int[] getDefaultTokens() {
503             return getRequiredTokens();
504         }
505 
506         @Override
507         public int[] getAcceptableTokens() {
508             return getRequiredTokens();
509         }
510 
511         @Override
512         public int[] getRequiredTokens() {
513             return new int[] {
514                 TokenTypes.PACKAGE_DEF,
515             };
516         }
517 
518         @Override
519         public void visitToken(DetailAST ast) {
520             log(ast, MSG_KEY);
521         }
522 
523     }
524 
525 }