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.design;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck.MSG_KEY;
24  
25  import java.io.File;
26  import java.util.Optional;
27  
28  import org.junit.jupiter.api.Test;
29  
30  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
31  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
32  import com.puppycrawl.tools.checkstyle.JavaParser;
33  import com.puppycrawl.tools.checkstyle.api.DetailAST;
34  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
36  
37  public class FinalClassCheckTest
38      extends AbstractModuleTestSupport {
39  
40      @Override
41      public String getPackageLocation() {
42          return "com/puppycrawl/tools/checkstyle/checks/design/finalclass";
43      }
44  
45      @Test
46      public void testGetRequiredTokens() {
47          final FinalClassCheck checkObj = new FinalClassCheck();
48          final int[] expected = {
49              TokenTypes.ANNOTATION_DEF,
50              TokenTypes.CLASS_DEF,
51              TokenTypes.ENUM_DEF,
52              TokenTypes.INTERFACE_DEF,
53              TokenTypes.RECORD_DEF,
54              TokenTypes.CTOR_DEF,
55              TokenTypes.PACKAGE_DEF,
56              TokenTypes.LITERAL_NEW,
57          };
58          assertWithMessage("Default required tokens are invalid")
59              .that(checkObj.getRequiredTokens())
60              .isEqualTo(expected);
61      }
62  
63      @Test
64      public void testFinalClass() throws Exception {
65          final String[] expected = {
66              "11:1: " + getCheckMessage(MSG_KEY, "InputFinalClass"),
67              "19:4: " + getCheckMessage(MSG_KEY, "test4"),
68          };
69          verifyWithInlineConfigParser(
70                  getPath("InputFinalClass.java"), expected);
71      }
72  
73      @Test
74      public void testFinalClass2() throws Exception {
75          final String[] expected = {
76              "26:5: " + getCheckMessage(MSG_KEY, "InnerFinalClass"),
77              "33:5: " + getCheckMessage(MSG_KEY, "SomeClass"),
78              "39:5: " + getCheckMessage(MSG_KEY, "SomeClass"),
79              "60:1: " + getCheckMessage(MSG_KEY, "TestNewKeyword"),
80              "93:5: " + getCheckMessage(MSG_KEY, "NestedClass"),
81          };
82          verifyWithInlineConfigParser(
83                  getPath("InputFinalClass2.java"), expected);
84      }
85  
86      @Test
87      public void testClassWithPrivateCtorAndNestedExtendingSubclass() throws Exception {
88          final String[] expected = {
89              "13:9: " + getCheckMessage(MSG_KEY, "ExtendA"),
90              "18:9: " + getCheckMessage(MSG_KEY, "ExtendB"),
91              "22:5: " + getCheckMessage(MSG_KEY, "C"),
92              "24:9: " + getCheckMessage(MSG_KEY, "ExtendC"),
93          };
94          verifyWithInlineConfigParser(
95                  getNonCompilablePath(
96                          "InputFinalClassClassWithPrivateCtorWithNestedExtendingClass.java"),
97                  expected);
98      }
99  
100     @Test
101     public void testClassWithPrivateCtorAndNestedExtendingSubclassWithoutPackage()
102             throws Exception {
103         final String[] expected = {
104             "11:9: " + getCheckMessage(MSG_KEY, "ExtendA"),
105             "14:5: " + getCheckMessage(MSG_KEY, "C"),
106             "16:9: " + getCheckMessage(MSG_KEY, "ExtendC"),
107         };
108         verifyWithInlineConfigParser(
109                 getNonCompilablePath(
110                 "InputFinalClassClassWithPrivateCtorWithNestedExtendingClassWithoutPackage.java"),
111                 expected);
112     }
113 
114     @Test
115     public void testCompactSourceFile() throws Exception {
116         final String[] expected = {
117             "19:1: " + getCheckMessage(MSG_KEY, "Bar"),
118         };
119         verifyWithInlineConfigParser(
120                 getNonCompilablePath("InputFinalClassCompactSourceFile.java"),
121                 expected);
122     }
123 
124     @Test
125     public void testCompactSourceFileAnonymousNestedResolution() throws Exception {
126         final String[] expected = {
127             "12:5: " + getCheckMessage(MSG_KEY, "Inner"),
128         };
129         verifyWithInlineConfigParser(
130                 getNonCompilablePath(
131                         "InputFinalClassCompactSourceFileAnonymousNestedResolution.java"),
132                 expected);
133     }
134 
135     @Test
136     public void testFinalClassConstructorInRecord() throws Exception {
137 
138         final String[] expected = {
139             "27:9: " + getCheckMessage(MSG_KEY, "F"),
140         };
141 
142         verifyWithInlineConfigParser(
143                 getPath("InputFinalClassConstructorInRecord.java"),
144             expected);
145     }
146 
147     @Test
148     public void testImproperToken() {
149         final FinalClassCheck finalClassCheck = new FinalClassCheck();
150         final DetailAstImpl badAst = new DetailAstImpl();
151         final int unsupportedTokenByCheck = TokenTypes.COMPILATION_UNIT;
152         badAst.setType(unsupportedTokenByCheck);
153         final IllegalStateException exc =
154             TestUtil.getExpectedThrowable(
155                 IllegalStateException.class, () -> {
156                     finalClassCheck.visitToken(badAst);
157                 });
158         assertWithMessage("Invalid exception message")
159             .that(exc.getMessage())
160             .isEqualTo(badAst.toString());
161     }
162 
163     @Test
164     public void testGetAcceptableTokens() {
165         final FinalClassCheck obj = new FinalClassCheck();
166         final int[] expected = {
167             TokenTypes.ANNOTATION_DEF,
168             TokenTypes.CLASS_DEF,
169             TokenTypes.ENUM_DEF,
170             TokenTypes.INTERFACE_DEF,
171             TokenTypes.RECORD_DEF,
172             TokenTypes.CTOR_DEF,
173             TokenTypes.PACKAGE_DEF,
174             TokenTypes.LITERAL_NEW,
175         };
176         assertWithMessage("Default acceptable tokens are invalid")
177             .that(obj.getAcceptableTokens())
178             .isEqualTo(expected);
179     }
180 
181     @Test
182     public void testFinalClassInnerAndNestedClasses() throws Exception {
183         final String[] expected = {
184             "16:5: " + getCheckMessage(MSG_KEY, "SubClass"),
185             "19:5: " + getCheckMessage(MSG_KEY, "SameName"),
186             "45:9: " + getCheckMessage(MSG_KEY, "SameName"),
187             "69:13: " + getCheckMessage(MSG_KEY, "B"),
188             "84:9: " + getCheckMessage(MSG_KEY, "c"),
189         };
190         verifyWithInlineConfigParser(getPath("InputFinalClassInnerAndNestedClass.java"), expected);
191     }
192 
193     @Test
194     public void testFinalClassStaticNestedClasses() throws Exception {
195 
196         final String[] expected = {
197             "14:17: " + getCheckMessage(MSG_KEY, "C"),
198             "32:9: " + getCheckMessage(MSG_KEY, "B"),
199             "43:9: " + getCheckMessage(MSG_KEY, "C"),
200             "60:13: " + getCheckMessage(MSG_KEY, "Q"),
201             "76:9: " + getCheckMessage(MSG_KEY, "F"),
202             "83:9: " + getCheckMessage(MSG_KEY, "c"),
203         };
204 
205         verifyWithInlineConfigParser(
206                 getPath("InputFinalClassNestedStaticClassInsideInnerClass.java"),
207                 expected);
208     }
209 
210     @Test
211     public void testFinalClassEnum() throws Exception {
212         final String[] expected = {
213             "35:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
214         };
215         verifyWithInlineConfigParser(getPath("InputFinalClassEnum.java"), expected);
216     }
217 
218     @Test
219     public void testFinalClassAnnotation() throws Exception {
220         final String[] expected = {
221             "15:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
222         };
223         verifyWithInlineConfigParser(getPath("InputFinalClassAnnotation.java"), expected);
224     }
225 
226     @Test
227     public void testFinalClassInterface() throws Exception {
228         final String[] expected = {
229             "15:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
230         };
231         verifyWithInlineConfigParser(getPath("InputFinalClassInterface.java"), expected);
232     }
233 
234     @Test
235     public void testFinalClassAnonymousInnerClass() throws Exception {
236         final String[] expected = {
237             "11:9: " + getCheckMessage(MSG_KEY, "b"),
238             "27:9: " + getCheckMessage(MSG_KEY, "m"),
239             "40:9: " + getCheckMessage(MSG_KEY, "q"),
240             "52:13: " + getCheckMessage(MSG_KEY, "b"),
241             "67:9: " + getCheckMessage(MSG_KEY, "g"),
242             "71:9: " + getCheckMessage(MSG_KEY, "y"),
243             "84:9: " + getCheckMessage(MSG_KEY, "n"),
244             "91:9: " + getCheckMessage(MSG_KEY, "n"),
245         };
246         verifyWithInlineConfigParser(getPath("InputFinalClassAnonymousInnerClass.java"), expected);
247     }
248 
249     @Test
250     public void testFinalClassNestedInInterface() throws Exception {
251         final String[] expected = {
252             "24:5: " + getCheckMessage(MSG_KEY, "b"),
253             "28:13: " + getCheckMessage(MSG_KEY, "m"),
254             "50:5: " + getCheckMessage(MSG_KEY, "c"),
255         };
256         verifyWithInlineConfigParser(
257             getPath("InputFinalClassNestedInInterfaceWithAnonInnerClass.java"), expected);
258     }
259 
260     @Test
261     public void testFinalClassNestedInEnum() throws Exception {
262         final String[] expected = {
263             "13:9: " + getCheckMessage(MSG_KEY, "j"),
264             "27:9: " + getCheckMessage(MSG_KEY, "n"),
265         };
266         verifyWithInlineConfigParser(getPath("InputFinalClassNestedInEnumWithAnonInnerClass.java"),
267                                      expected);
268     }
269 
270     @Test
271     public void testFinalClassNestedInRecord() throws Exception {
272         final String[] expected = {
273             "13:9: " + getCheckMessage(MSG_KEY, "c"),
274             "31:13: " + getCheckMessage(MSG_KEY, "j"),
275             "49:5: " + getCheckMessage(MSG_KEY, "Nothing"),
276         };
277         verifyWithInlineConfigParser(getPath("InputFinalClassNestedInRecord.java"),
278                                      expected);
279     }
280 
281     /**
282      * We cannot reproduce situation when visitToken is called and leaveToken is not.
283      * So, we have to use reflection to be sure that even in such situation
284      * state of the field will be cleared.
285      *
286      * @throws Exception when code tested throws exception
287      */
288     @Test
289     public void testClearState() throws Exception {
290         final FinalClassCheck check = new FinalClassCheck();
291         final DetailAST root = JavaParser.parseFile(new File(getPath("InputFinalClass.java")),
292                 JavaParser.Options.WITHOUT_COMMENTS);
293         final Optional<DetailAST> packageDef = TestUtil.findTokenInAstByPredicate(root,
294             ast -> ast.getType() == TokenTypes.PACKAGE_DEF);
295 
296         assertWithMessage("Ast should contain PACKAGE_DEF")
297                 .that(packageDef.isPresent())
298                 .isTrue();
299         assertWithMessage("State is not cleared on beginTree")
300                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check,
301                         packageDef.orElseThrow(), "packageName",
302                         packageName -> ((CharSequence) packageName).isEmpty()))
303                 .isTrue();
304     }
305 
306     @Test
307     public void testPrivateClassWithDefaultCtor() throws Exception {
308         final String[] expected = {
309             "14:5: " + getCheckMessage(MSG_KEY, "Some2"),
310             "19:1: " + getCheckMessage(MSG_KEY, "Some"),
311             "24:5: " + getCheckMessage(MSG_KEY, "Some3"),
312             "26:5: " + getCheckMessage(MSG_KEY, "Some4"),
313             "31:5: " + getCheckMessage(MSG_KEY, "PaperSetter"),
314             "36:5: " + getCheckMessage(MSG_KEY, "Paper"),
315             "44:5: " + getCheckMessage(MSG_KEY, "Node"),
316             "51:5: " + getCheckMessage(MSG_KEY, "Some1"),
317             "55:1: " + getCheckMessage(MSG_KEY, "Some2"),
318             "106:5: " + getCheckMessage(MSG_KEY, "NewCheck"),
319             "110:5: " + getCheckMessage(MSG_KEY, "NewCheck2"),
320             "115:5: " + getCheckMessage(MSG_KEY, "OldCheck"),
321         };
322         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor.java"),
323                                      expected);
324     }
325 
326     @Test
327     public void testPrivateClassWithDefaultCtor2() throws Exception {
328         final String[] expected = {
329             "22:5: " + getCheckMessage(MSG_KEY, "PrivateClass"),
330             "34:5: " + getCheckMessage(MSG_KEY, "Check"),
331             "44:5: " + getCheckMessage(MSG_KEY, "K"),
332             "54:5: " + getCheckMessage(MSG_KEY, "Modifiers"),
333         };
334         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor2.java"),
335                                      expected);
336     }
337 
338     @Test
339     public void testPrivateClassWithDefaultCtor3() throws Exception {
340         final String[] expected = {
341             "26:5: " + getCheckMessage(MSG_KEY, "MyClass"),
342             "30:5: " + getCheckMessage(MSG_KEY, "Check2"),
343             "31:9: " + getCheckMessage(MSG_KEY, "Check3"),
344             "35:5: " + getCheckMessage(MSG_KEY, "Check4"),
345             "40:5: " + getCheckMessage(MSG_KEY, "Check"),
346         };
347         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor3.java"),
348                                      expected);
349     }
350 
351 }