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.checks.imports;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck.MSG_KEY;
24  import static com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck.MSG_KEY_UNCLOSED_HTML_TAG;
25  
26  import java.io.File;
27  import java.util.Arrays;
28  import java.util.List;
29  
30  import org.junit.jupiter.api.Test;
31  
32  import com.google.common.collect.ImmutableMap;
33  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
34  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
35  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
36  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
39  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
40  
41  public class UnusedImportsCheckTest extends AbstractModuleTestSupport {
42  
43      @Override
44      public String getPackageLocation() {
45          return "com/puppycrawl/tools/checkstyle/checks/imports/unusedimports";
46      }
47  
48      @Test
49      public void testReferencedStateIsCleared() throws Exception {
50          final DefaultConfiguration checkConfig = createModuleConfig(UnusedImportsCheck.class);
51          final String inputWithoutWarnings = getPath("InputUnusedImportsWithoutWarnings.java");
52          final String inputWithWarnings = getPath("InputUnusedImportsCheckClearState.java");
53          final List<String> expectedFirstInput = Arrays.asList(CommonUtil.EMPTY_STRING_ARRAY);
54          final List<String> expectedSecondInput = Arrays.asList(
55                  "10:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
56                  "11:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
57                  "12:8: " + getCheckMessage(MSG_KEY, "java.util.Set")
58          );
59          final File[] inputsWithWarningsFirst =
60              {new File(inputWithWarnings), new File(inputWithoutWarnings)};
61          final File[] inputsWithoutWarningFirst =
62              {new File(inputWithoutWarnings), new File(inputWithWarnings)};
63  
64          verify(createChecker(checkConfig), inputsWithWarningsFirst, ImmutableMap.of(
65                  inputWithoutWarnings, expectedFirstInput,
66                  inputWithWarnings, expectedSecondInput));
67          verify(createChecker(checkConfig), inputsWithoutWarningFirst, ImmutableMap.of(
68                  inputWithoutWarnings, expectedFirstInput,
69                  inputWithWarnings, expectedSecondInput));
70      }
71  
72      @Test
73      public void testWithoutProcessJavadoc() throws Exception {
74          final String[] expected = {
75              "11:8: " + getCheckMessage(MSG_KEY,
76                  "com.google.errorprone.annotations."
77                  + "concurrent.GuardedBy"),
78              "15:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
79              "17:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
80              "18:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
81              "21:8: " + getCheckMessage(MSG_KEY, "java.util.Enumeration"),
82              "24:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton"),
83              "26:8: " + getCheckMessage(MSG_KEY, "javax.swing.BorderFactory"),
84              "31:15: " + getCheckMessage(MSG_KEY, "java.io.File.createTempFile"),
85              // "33:8: Unused import - java.awt.Component.", // Should be detected
86              "34:8: " + getCheckMessage(MSG_KEY, "java.awt.Graphics2D"),
87              "35:8: " + getCheckMessage(MSG_KEY, "java.awt.HeadlessException"),
88              "36:8: " + getCheckMessage(MSG_KEY, "java.awt.Label"),
89              "37:8: " + getCheckMessage(MSG_KEY, "java.util.Date"),
90              "38:8: " + getCheckMessage(MSG_KEY, "java.util.Calendar"),
91              "39:8: " + getCheckMessage(MSG_KEY, "java.util.BitSet"),
92              "41:8: " + getCheckMessage(MSG_KEY, "com.google.errorprone."
93                      + "annotations.CheckReturnValue"),
94              "42:8: " + getCheckMessage(MSG_KEY, "com.google.errorprone."
95                      + "annotations.CanIgnoreReturnValue"),
96              "43:8: " + getCheckMessage(MSG_KEY, "com.google.errorprone."
97                      + "annotations.CompatibleWith"),
98              "44:8: " + getCheckMessage(MSG_KEY,
99                  "com.google.errorprone.annotations.concurrent."
100                         + "LazyInit"),
101             "45:8: " + getCheckMessage(MSG_KEY,
102                 "com.google.errorprone.annotations.DoNotCall"),
103             "46:8: " + getCheckMessage(MSG_KEY,
104                 "com.google.errorprone.annotations.CompileTimeConstant"),
105             "47:8: " + getCheckMessage(MSG_KEY,
106                 "com.google.errorprone.annotations.FormatMethod"),
107             "48:8: " + getCheckMessage(MSG_KEY, "com.google.errorprone.annotations.FormatString"),
108         };
109         verifyWithInlineConfigParser(
110                 getPath("InputUnusedImports2.java"), expected);
111     }
112 
113     @Test
114     public void testProcessJavadoc() throws Exception {
115         final String[] expected = {
116             "11:8: " + getCheckMessage(MSG_KEY,
117                     "com.google.errorprone.annotations."
118                     + "concurrent.GuardedBy"),
119             "15:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
120             "17:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
121             "18:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
122             "21:8: " + getCheckMessage(MSG_KEY, "java.util.Enumeration"),
123             "24:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton"),
124             "26:8: " + getCheckMessage(MSG_KEY, "javax.swing.BorderFactory"),
125             "31:15: " + getCheckMessage(MSG_KEY, "java.io.File.createTempFile"),
126             // "30:8: Unused import - java.awt.Component.", // Should be detected
127             "36:8: " + getCheckMessage(MSG_KEY, "java.awt.Label"),
128             "48:8: " + getCheckMessage(MSG_KEY, "com.google.errorprone.annotations.ForOverride"),
129         };
130         verifyWithInlineConfigParser(
131                 getPath("InputUnusedImports.java"), expected);
132     }
133 
134     @Test
135     public void testProcessJavadocWithLinkTag() throws Exception {
136         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
137         verifyWithInlineConfigParser(
138                 getPath("InputUnusedImportsWithValueTag.java"), expected);
139     }
140 
141     @Test
142     public void testProcessJavadocWithBlockTagContainingMethodParameters() throws Exception {
143         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
144         verifyWithInlineConfigParser(
145                 getPath("InputUnusedImportsWithBlockMethodParameters.java"), expected);
146     }
147 
148     @Test
149     public void testAnnotations() throws Exception {
150         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
151         verifyWithInlineConfigParser(
152                 getNonCompilablePath("InputUnusedImportsAnnotations.java"), expected);
153     }
154 
155     @Test
156     public void testArrayRef() throws Exception {
157         final String[] expected = {
158             "13:8: " + getCheckMessage(MSG_KEY, "java.util.ArrayList"),
159         };
160         verifyWithInlineConfigParser(
161                 getPath("InputUnusedImportsArrayRef.java"), expected);
162     }
163 
164     @Test
165     public void testBug() throws Exception {
166         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
167         verifyWithInlineConfigParser(
168                 getPath("InputUnusedImportsBug.java"), expected);
169     }
170 
171     @Test
172     public void testNewlinesInsideTags() throws Exception {
173         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
174         verifyWithInlineConfigParser(
175                 getPath("InputUnusedImportsWithNewlinesInsideTags.java"), expected);
176     }
177 
178     @Test
179     public void testGetRequiredTokens() {
180         final UnusedImportsCheck testCheckObject =
181                 new UnusedImportsCheck();
182         final int[] actual = testCheckObject.getRequiredTokens();
183         final int[] expected = {
184             TokenTypes.IDENT,
185             TokenTypes.IMPORT,
186             TokenTypes.STATIC_IMPORT,
187             TokenTypes.OBJBLOCK,
188             TokenTypes.SLIST,
189             TokenTypes.BLOCK_COMMENT_BEGIN,
190         };
191 
192         assertWithMessage("Default required tokens are invalid")
193             .that(actual)
194             .isEqualTo(expected);
195     }
196 
197     @Test
198     public void testGetAcceptableTokens() {
199         final UnusedImportsCheck testCheckObject =
200                 new UnusedImportsCheck();
201         final int[] actual = testCheckObject.getAcceptableTokens();
202         final int[] expected = {
203             TokenTypes.IDENT,
204             TokenTypes.IMPORT,
205             TokenTypes.STATIC_IMPORT,
206             TokenTypes.OBJBLOCK,
207             TokenTypes.SLIST,
208             TokenTypes.BLOCK_COMMENT_BEGIN,
209         };
210 
211         assertWithMessage("Default acceptable tokens are invalid")
212             .that(actual)
213             .isEqualTo(expected);
214     }
215 
216     @Test
217     public void testFileInUnnamedPackage() throws Exception {
218         final String[] expected = {
219             "12:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
220             "13:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
221         };
222         verifyWithInlineConfigParser(
223                 getNonCompilablePath("InputUnusedImportsFileInUnnamedPackage.java"),
224             expected);
225     }
226 
227     @Test
228     public void testImportsFromJavaLang() throws Exception {
229         final String[] expected = {
230             "10:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
231             "11:8: " + getCheckMessage(MSG_KEY, "java.lang.Math"),
232             "12:8: " + getCheckMessage(MSG_KEY, "java.lang.Class"),
233             "13:8: " + getCheckMessage(MSG_KEY, "java.lang.Exception"),
234             "14:8: " + getCheckMessage(MSG_KEY, "java.lang.Runnable"),
235             "15:8: " + getCheckMessage(MSG_KEY, "java.lang.RuntimeException"),
236             "16:8: " + getCheckMessage(MSG_KEY, "java.lang.ProcessBuilder"),
237             "17:8: " + getCheckMessage(MSG_KEY, "java.lang.Double"),
238             "18:8: " + getCheckMessage(MSG_KEY, "java.lang.Integer"),
239             "19:8: " + getCheckMessage(MSG_KEY, "java.lang.Float"),
240             "20:8: " + getCheckMessage(MSG_KEY, "java.lang.Short"),
241         };
242         verifyWithInlineConfigParser(
243                 getPath("InputUnusedImportsFromJavaLang.java"), expected);
244     }
245 
246     @Test
247     public void testImportsJavadocQualifiedName() throws Exception {
248         final String[] expected = {
249             "11:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
250         };
251         verifyWithInlineConfigParser(
252                 getPath("InputUnusedImportsJavadocQualifiedName.java"), expected);
253     }
254 
255     @Test
256     public void testSingleWordPackage() throws Exception {
257         final String[] expected = {
258             "10:8: " + getCheckMessage(MSG_KEY, "module"),
259             "11:15: " + getCheckMessage(MSG_KEY, "module2"),
260         };
261         verifyWithInlineConfigParser(
262                 getNonCompilablePath("InputUnusedImportsSingleWordPackage.java"),
263                 expected);
264     }
265 
266     @Test
267     public void testRecordsAndCompactCtors() throws Exception {
268         final String[] expected = {
269             "19:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToolBar"),
270             "20:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton"),
271         };
272         verifyWithInlineConfigParser(
273                 getPath("InputUnusedImportsRecordsAndCompactCtors.java"),
274                 expected);
275     }
276 
277     @Test
278     public void testShadowedImports() throws Exception {
279         final String[] expected = {
280             "12:8: " + getCheckMessage(MSG_KEY, "java.util.Map"),
281             "13:8: " + getCheckMessage(MSG_KEY, "java.util.Set"),
282             "16:8: " + getCheckMessage(MSG_KEY, "com.puppycrawl.tools.checkstyle.checks.imports."
283                     + "unusedimports.InputUnusedImportsShadowed"),
284         };
285         verifyWithInlineConfigParser(
286                 getPath("InputUnusedImportsShadowed.java"), expected);
287     }
288 
289     @Test
290     public void testUnusedImports3() throws Exception {
291         final String[] expected = {
292             "11:8: " + getCheckMessage(MSG_KEY, "java.awt.Rectangle"),
293             "13:8: " + getCheckMessage(MSG_KEY, "java.awt.event.KeyEvent"),
294         };
295         verifyWithInlineConfigParser(
296                 getPath("InputUnusedImports3.java"), expected);
297     }
298 
299     @Test
300     public void testStateIsClearedOnBeginTreeCollect() throws Exception {
301         final String file1 = getPath(
302                 "InputUnusedImportsRecordsAndCompactCtors.java");
303         final String file2 = getNonCompilablePath(
304                 "InputUnusedImportsSingleWordPackage.java");
305         final List<String> expectedFirstInput = List.of(
306             "19:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToolBar"),
307             "20:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton")
308         );
309         final List<String> expectedSecondInput = List.of(
310             "10:8: " + getCheckMessage(MSG_KEY, "module"),
311             "11:15: " + getCheckMessage(MSG_KEY, "module2")
312         );
313         verifyWithInlineConfigParser(file1, file2, expectedFirstInput, expectedSecondInput);
314     }
315 
316     @Test
317     public void testStaticMethodRefImports() throws Exception {
318         final String[] expected = {
319             "26:15: " + getCheckMessage(MSG_KEY, "java.lang.String.format"),
320             "27:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.sort"),
321             "28:15: " + getCheckMessage(MSG_KEY, "java.util.List.of"),
322             "29:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyMap"),
323         };
324         verifyWithInlineConfigParser(
325                 getPath("InputUnusedImportsFromStaticMethodRef.java"), expected);
326     }
327 
328     @Test
329     public void testStaticMethodRefImportsExtended() throws Exception {
330         final String[] expected = {
331             "17:8: " + getCheckMessage(MSG_KEY, "java.util.Objects"),
332             "18:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.toString"),
333             "19:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.asList"),
334             "20:15: " + getCheckMessage(MSG_KEY, "java.lang.Integer.parseInt"),
335             "21:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyList"),
336         };
337         verifyWithInlineConfigParser(
338                 getPath("InputUnusedImportsFromStaticMethodRefExtended.java"), expected);
339     }
340 
341     @Test
342     public void testStaticMethodRefImportsWithJavadocDisabled() throws Exception {
343         final String[] expected = {
344             "24:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
345             "25:15: " + getCheckMessage(MSG_KEY, "java.lang.Integer.parseInt"),
346             "26:15: " + getCheckMessage(MSG_KEY, "java.lang.String.format"),
347             "27:15: " + getCheckMessage(MSG_KEY, "java.util.List.of"),
348             "28:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyMap"),
349         };
350         verifyWithInlineConfigParser(
351                 getPath("InputUnusedImportsFromStaticMethodRefJavadocDisabled.java"), expected);
352     }
353 
354     @Test
355     public void testUnusedImportsJavadocAboveComments() throws Exception {
356         final String[] expected = {
357             "11:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
358         };
359         verifyWithInlineConfigParser(
360                 getPath("InputUnusedImportsJavadocAboveComments.java"), expected);
361     }
362 
363     @Test
364     public void testImportJavaLinkTag() throws Exception {
365         final String[] expected = {
366             "10:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
367             "12:8: " + getCheckMessage(MSG_KEY, "java.util.TreeSet"),
368 
369         };
370         verifyWithInlineConfigParser(
371                 getPath("InputUnusedImportsWithLinkTag.java"), expected);
372 
373     }
374 
375     @Test
376     public void testImportJavaLinkTagWithMethod() throws Exception {
377         final String[] expected = {
378             "15:8: " + getCheckMessage(MSG_KEY, "java.util.Set"),
379             "23:8: " + getCheckMessage(MSG_KEY, "java.util.concurrent.TimeUnit"),
380         };
381         verifyWithInlineConfigParser(
382                 getPath("InputUnusedImportsWithLinkAndMethodTag.java"), expected);
383 
384     }
385 
386     @Test
387     public void testJavadocSetters() throws Exception {
388         final String[] expected = {
389             "14: " + getCheckMessage(MSG_KEY_UNCLOSED_HTML_TAG, "p"),
390         };
391         verifyWithInlineConfigParser(
392                 getPath("InputUnusedImportsJavadocSetters.java"), expected);
393 
394     }
395 
396     @Test
397     public void testCache() throws Exception {
398         final String[] expected = {
399             "10:8: " + getCheckMessage(MSG_KEY, "java.util.TreeSet"),
400         };
401         verifyWithInlineConfigParser(getPath("InputUnusedImportsCache1.java"),
402             getPath("InputUnusedImportsCache2.java"), expected);
403     }
404 
405     /**
406      * Verifies that the check fails on unsupported AST tokens.
407      *
408      * <p>This test does not use {@code verifyWithInlineConfigParser} because such
409      * tokens are never dispatched to this check during normal Checkstyle execution
410      * and must be tested via manual AST construction.</p>
411      */
412     @Test
413     public void testImproperToken() {
414         final UnusedImportsCheck check = new UnusedImportsCheck();
415 
416         final DetailAstImpl lambdaAst = new DetailAstImpl();
417         lambdaAst.setType(TokenTypes.LAMBDA);
418         lambdaAst.setText("LAMBDA");
419 
420         try {
421             check.visitToken(lambdaAst);
422             assertWithMessage("IllegalArgumentException is expected").fail();
423         }
424         catch (IllegalArgumentException exc) {
425             assertWithMessage("Message must include token name")
426                 .that(exc.getMessage())
427                 .contains("LAMBDA");
428         }
429     }
430 
431     /**
432      * Verifies that the check fails on unsupported Javadoc tokens.
433      *
434      * <p>This case cannot be reproduced through real Javadoc parsing, so the AST
435      * node is created manually instead of using {@code verifyWithInlineConfigParser}.</p>
436      */
437     @Test
438     public void testImproperJavadocToken() {
439         final UnusedImportsCheck check = new UnusedImportsCheck();
440 
441         final JavadocNodeImpl ast = new JavadocNodeImpl();
442         ast.setType(JavadocCommentsTokenTypes.EQUALS);
443         ast.setText("EQUALS");
444 
445         try {
446             check.visitJavadocToken(ast);
447             assertWithMessage("IllegalArgumentException is expected").fail();
448         }
449         catch (IllegalArgumentException exc) {
450             assertWithMessage("Message must include token name")
451                 .that(exc.getMessage())
452                 .contains("EQUALS");
453         }
454     }
455 
456 }