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 testProcessJavadocWithLinkAndGenericMethodParameters() throws Exception {
150         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
151         verifyWithInlineConfigParser(
152                 getPath("InputUnusedImportsWithLinkAndGenericMethodParameters.java"), expected);
153     }
154 
155     @Test
156     public void testProcessJavadocWithLinkAndGenericMethodParametersWithInnerTypes()
157             throws Exception {
158         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
159         verifyWithInlineConfigParser(
160                 getPath("InputUnusedImportsWithLinkAndGenericMethodParameters2.java"), expected);
161     }
162 
163     @Test
164     public void testProcessJavadocWithLinkAndGenericMethodParametersWithInnerTypesOnly()
165             throws Exception {
166         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
167         verifyWithInlineConfigParser(
168                 getPath("InputUnusedImportsWithLinkAndGenericMethodParameters3.java"), expected);
169     }
170 
171     @Test
172     public void testProcessJavadocWithLinkAndGenericMethodParametersMultipleArgs()
173             throws Exception {
174         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
175         verifyWithInlineConfigParser(
176                 getPath("InputUnusedImportsWithLinkAndGenericMethodParameters4.java"), expected);
177     }
178 
179     @Test
180     public void testSeeTagWithParameterNames() throws Exception {
181         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
182         verifyWithInlineConfigParser(
183                 getPath("InputUnusedImportsWithSeeTagAndParameterNames.java"), expected);
184     }
185 
186     @Test
187     public void testAnnotations() throws Exception {
188         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
189         verifyWithInlineConfigParser(
190                 getNonCompilablePath("InputUnusedImportsAnnotations.java"), expected);
191     }
192 
193     @Test
194     public void testArrayRef() throws Exception {
195         final String[] expected = {
196             "13:8: " + getCheckMessage(MSG_KEY, "java.util.ArrayList"),
197         };
198         verifyWithInlineConfigParser(
199                 getPath("InputUnusedImportsArrayRef.java"), expected);
200     }
201 
202     @Test
203     public void testBug() throws Exception {
204         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
205         verifyWithInlineConfigParser(
206                 getPath("InputUnusedImportsBug.java"), expected);
207     }
208 
209     @Test
210     public void testNewlinesInsideTags() throws Exception {
211         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
212         verifyWithInlineConfigParser(
213                 getPath("InputUnusedImportsWithNewlinesInsideTags.java"), expected);
214     }
215 
216     @Test
217     public void testGetRequiredTokens() {
218         final UnusedImportsCheck testCheckObject =
219                 new UnusedImportsCheck();
220         final int[] actual = testCheckObject.getRequiredTokens();
221         final int[] expected = {
222             TokenTypes.IDENT,
223             TokenTypes.IMPORT,
224             TokenTypes.STATIC_IMPORT,
225             TokenTypes.OBJBLOCK,
226             TokenTypes.SLIST,
227             TokenTypes.BLOCK_COMMENT_BEGIN,
228         };
229 
230         assertWithMessage("Default required tokens are invalid")
231             .that(actual)
232             .isEqualTo(expected);
233     }
234 
235     @Test
236     public void testGetAcceptableTokens() {
237         final UnusedImportsCheck testCheckObject =
238                 new UnusedImportsCheck();
239         final int[] actual = testCheckObject.getAcceptableTokens();
240         final int[] expected = {
241             TokenTypes.IDENT,
242             TokenTypes.IMPORT,
243             TokenTypes.STATIC_IMPORT,
244             TokenTypes.OBJBLOCK,
245             TokenTypes.SLIST,
246             TokenTypes.BLOCK_COMMENT_BEGIN,
247         };
248 
249         assertWithMessage("Default acceptable tokens are invalid")
250             .that(actual)
251             .isEqualTo(expected);
252     }
253 
254     @Test
255     public void testFileInUnnamedPackage() throws Exception {
256         final String[] expected = {
257             "12:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
258             "13:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
259         };
260         verifyWithInlineConfigParser(
261                 getNonCompilablePath("InputUnusedImportsFileInUnnamedPackage.java"),
262             expected);
263     }
264 
265     @Test
266     public void testImportsFromJavaLang() throws Exception {
267         final String[] expected = {
268             "10:8: " + getCheckMessage(MSG_KEY, "java.lang.String"),
269             "11:8: " + getCheckMessage(MSG_KEY, "java.lang.Math"),
270             "12:8: " + getCheckMessage(MSG_KEY, "java.lang.Class"),
271             "13:8: " + getCheckMessage(MSG_KEY, "java.lang.Exception"),
272             "14:8: " + getCheckMessage(MSG_KEY, "java.lang.Runnable"),
273             "15:8: " + getCheckMessage(MSG_KEY, "java.lang.RuntimeException"),
274             "16:8: " + getCheckMessage(MSG_KEY, "java.lang.ProcessBuilder"),
275             "17:8: " + getCheckMessage(MSG_KEY, "java.lang.Double"),
276             "18:8: " + getCheckMessage(MSG_KEY, "java.lang.Integer"),
277             "19:8: " + getCheckMessage(MSG_KEY, "java.lang.Float"),
278             "20:8: " + getCheckMessage(MSG_KEY, "java.lang.Short"),
279         };
280         verifyWithInlineConfigParser(
281                 getPath("InputUnusedImportsFromJavaLang.java"), expected);
282     }
283 
284     @Test
285     public void testImportsJavadocQualifiedName() throws Exception {
286         final String[] expected = {
287             "11:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
288         };
289         verifyWithInlineConfigParser(
290                 getPath("InputUnusedImportsJavadocQualifiedName.java"), expected);
291     }
292 
293     @Test
294     public void testSingleWordPackage() throws Exception {
295         final String[] expected = {
296             "10:8: " + getCheckMessage(MSG_KEY, "module"),
297             "11:15: " + getCheckMessage(MSG_KEY, "module2"),
298         };
299         verifyWithInlineConfigParser(
300                 getNonCompilablePath("InputUnusedImportsSingleWordPackage.java"),
301                 expected);
302     }
303 
304     @Test
305     public void testRecordsAndCompactCtors() throws Exception {
306         final String[] expected = {
307             "19:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToolBar"),
308             "20:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton"),
309         };
310         verifyWithInlineConfigParser(
311                 getPath("InputUnusedImportsRecordsAndCompactCtors.java"),
312                 expected);
313     }
314 
315     @Test
316     public void testShadowedImports() throws Exception {
317         final String[] expected = {
318             "12:8: " + getCheckMessage(MSG_KEY, "java.util.Map"),
319             "13:8: " + getCheckMessage(MSG_KEY, "java.util.Set"),
320             "16:8: " + getCheckMessage(MSG_KEY, "com.puppycrawl.tools.checkstyle.checks.imports."
321                     + "unusedimports.InputUnusedImportsShadowed"),
322         };
323         verifyWithInlineConfigParser(
324                 getPath("InputUnusedImportsShadowed.java"), expected);
325     }
326 
327     @Test
328     public void testUnusedImports3() throws Exception {
329         final String[] expected = {
330             "11:8: " + getCheckMessage(MSG_KEY, "java.awt.Rectangle"),
331             "13:8: " + getCheckMessage(MSG_KEY, "java.awt.event.KeyEvent"),
332         };
333         verifyWithInlineConfigParser(
334                 getPath("InputUnusedImports3.java"), expected);
335     }
336 
337     @Test
338     public void testStateIsClearedOnBeginTreeCollect() throws Exception {
339         final String file1 = getPath(
340                 "InputUnusedImportsRecordsAndCompactCtors.java");
341         final String file2 = getNonCompilablePath(
342                 "InputUnusedImportsSingleWordPackage.java");
343         final List<String> expectedFirstInput = List.of(
344             "19:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToolBar"),
345             "20:8: " + getCheckMessage(MSG_KEY, "javax.swing.JToggleButton")
346         );
347         final List<String> expectedSecondInput = List.of(
348             "10:8: " + getCheckMessage(MSG_KEY, "module"),
349             "11:15: " + getCheckMessage(MSG_KEY, "module2")
350         );
351         verifyWithInlineConfigParser(file1, file2, expectedFirstInput, expectedSecondInput);
352     }
353 
354     @Test
355     public void testStaticMethodRefImports() throws Exception {
356         final String[] expected = {
357             "26:15: " + getCheckMessage(MSG_KEY, "java.lang.String.format"),
358             "27:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.sort"),
359             "28:15: " + getCheckMessage(MSG_KEY, "java.util.List.of"),
360             "29:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyMap"),
361         };
362         verifyWithInlineConfigParser(
363                 getPath("InputUnusedImportsFromStaticMethodRef.java"), expected);
364     }
365 
366     @Test
367     public void testStaticMethodRefImportsExtended() throws Exception {
368         final String[] expected = {
369             "17:8: " + getCheckMessage(MSG_KEY, "java.util.Objects"),
370             "18:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.toString"),
371             "19:15: " + getCheckMessage(MSG_KEY, "java.util.Arrays.asList"),
372             "20:15: " + getCheckMessage(MSG_KEY, "java.lang.Integer.parseInt"),
373             "21:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyList"),
374         };
375         verifyWithInlineConfigParser(
376                 getPath("InputUnusedImportsFromStaticMethodRefExtended.java"), expected);
377     }
378 
379     @Test
380     public void testStaticMethodRefImportsWithJavadocDisabled() throws Exception {
381         final String[] expected = {
382             "24:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
383             "25:15: " + getCheckMessage(MSG_KEY, "java.lang.Integer.parseInt"),
384             "26:15: " + getCheckMessage(MSG_KEY, "java.lang.String.format"),
385             "27:15: " + getCheckMessage(MSG_KEY, "java.util.List.of"),
386             "28:15: " + getCheckMessage(MSG_KEY, "java.util.Collections.emptyMap"),
387         };
388         verifyWithInlineConfigParser(
389                 getPath("InputUnusedImportsFromStaticMethodRefJavadocDisabled.java"), expected);
390     }
391 
392     @Test
393     public void testStaticMethodRefImportsWithJavadocErrorAndJavadocDisabled() throws Exception {
394         final String[] expected = {
395             "10:8: " + getCheckMessage(MSG_KEY, "java.util.Arrays"),
396             "11:15: " + getCheckMessage(MSG_KEY, "java.lang.Integer.parseInt"),
397         };
398         verifyWithInlineConfigParser(
399                 getJavadocWithErrorPath(
400                     "InputUnusedImportsFromStaticMethodRefJavadocDisabled.java"),
401                 expected);
402     }
403 
404     @Test
405     public void testUnusedImportsJavadocAboveComments() throws Exception {
406         final String[] expected = {
407             "11:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
408         };
409         verifyWithInlineConfigParser(
410                 getPath("InputUnusedImportsJavadocAboveComments.java"), expected);
411     }
412 
413     @Test
414     public void testImportJavaLinkTag() throws Exception {
415         final String[] expected = {
416             "10:8: " + getCheckMessage(MSG_KEY, "java.util.List"),
417             "12:8: " + getCheckMessage(MSG_KEY, "java.util.TreeSet"),
418 
419         };
420         verifyWithInlineConfigParser(
421                 getPath("InputUnusedImportsWithLinkTag.java"), expected);
422 
423     }
424 
425     @Test
426     public void testImportJavaLinkTagWithMethod() throws Exception {
427         final String[] expected = {
428             "15:8: " + getCheckMessage(MSG_KEY, "java.util.Set"),
429             "23:8: " + getCheckMessage(MSG_KEY, "java.util.concurrent.TimeUnit"),
430         };
431         verifyWithInlineConfigParser(
432                 getPath("InputUnusedImportsWithLinkAndMethodTag.java"), expected);
433 
434     }
435 
436     @Test
437     public void testJavadocSetters() throws Exception {
438         final String[] expected = {
439             "14: " + getCheckMessage(MSG_KEY_UNCLOSED_HTML_TAG, "p"),
440         };
441         verifyWithInlineConfigParser(
442                 getPath("InputUnusedImportsJavadocSetters.java"), expected);
443 
444     }
445 
446     @Test
447     public void testCache() throws Exception {
448         final String[] expected = {
449             "10:8: " + getCheckMessage(MSG_KEY, "java.util.TreeSet"),
450         };
451         verifyWithInlineConfigParser(getPath("InputUnusedImportsCache1.java"),
452             getPath("InputUnusedImportsCache2.java"), expected);
453     }
454 
455     /**
456      * Verifies that the check fails on unsupported AST tokens.
457      *
458      * <p>This test does not use {@code verifyWithInlineConfigParser} because such
459      * tokens are never dispatched to this check during normal Checkstyle execution
460      * and must be tested via manual AST construction.</p>
461      */
462     @Test
463     public void testImproperToken() {
464         final UnusedImportsCheck check = new UnusedImportsCheck();
465 
466         final DetailAstImpl lambdaAst = new DetailAstImpl();
467         lambdaAst.setType(TokenTypes.LAMBDA);
468         lambdaAst.setText("LAMBDA");
469 
470         try {
471             check.visitToken(lambdaAst);
472             assertWithMessage("IllegalArgumentException is expected").fail();
473         }
474         catch (IllegalArgumentException exc) {
475             assertWithMessage("Message must include token name")
476                 .that(exc.getMessage())
477                 .contains("LAMBDA");
478         }
479     }
480 
481     /**
482      * Verifies that the check fails on unsupported Javadoc tokens.
483      *
484      * <p>This case cannot be reproduced through real Javadoc parsing, so the AST
485      * node is created manually instead of using {@code verifyWithInlineConfigParser}.</p>
486      */
487     @Test
488     public void testImproperJavadocToken() {
489         final UnusedImportsCheck check = new UnusedImportsCheck();
490 
491         final JavadocNodeImpl ast = new JavadocNodeImpl();
492         ast.setType(JavadocCommentsTokenTypes.EQUALS);
493         ast.setText("EQUALS");
494 
495         try {
496             check.visitJavadocToken(ast);
497             assertWithMessage("IllegalArgumentException is expected").fail();
498         }
499         catch (IllegalArgumentException exc) {
500             assertWithMessage("Message must include token name")
501                 .that(exc.getMessage())
502                 .contains("EQUALS");
503         }
504     }
505 
506 }