View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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      protected 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, "someinnerClass"),
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 testFinalClassConstructorInRecord() throws Exception {
116 
117         final String[] expected = {
118             "27:9: " + getCheckMessage(MSG_KEY, "F"),
119         };
120 
121         verifyWithInlineConfigParser(
122                 getNonCompilablePath("InputFinalClassConstructorInRecord.java"),
123             expected);
124     }
125 
126     @Test
127     public void testImproperToken() {
128         final FinalClassCheck finalClassCheck = new FinalClassCheck();
129         final DetailAstImpl badAst = new DetailAstImpl();
130         final int unsupportedTokenByCheck = TokenTypes.COMPILATION_UNIT;
131         badAst.setType(unsupportedTokenByCheck);
132         try {
133             finalClassCheck.visitToken(badAst);
134             assertWithMessage("IllegalStateException is expected").fail();
135         }
136         catch (IllegalStateException ex) {
137             assertWithMessage("Invalid exception message")
138                 .that(ex.getMessage())
139                 .isEqualTo(badAst.toString());
140         }
141     }
142 
143     @Test
144     public void testGetAcceptableTokens() {
145         final FinalClassCheck obj = new FinalClassCheck();
146         final int[] expected = {
147             TokenTypes.ANNOTATION_DEF,
148             TokenTypes.CLASS_DEF,
149             TokenTypes.ENUM_DEF,
150             TokenTypes.INTERFACE_DEF,
151             TokenTypes.RECORD_DEF,
152             TokenTypes.CTOR_DEF,
153             TokenTypes.PACKAGE_DEF,
154             TokenTypes.LITERAL_NEW,
155         };
156         assertWithMessage("Default acceptable tokens are invalid")
157             .that(obj.getAcceptableTokens())
158             .isEqualTo(expected);
159     }
160 
161     @Test
162     public void testFinalClassInnerAndNestedClasses() throws Exception {
163         final String[] expected = {
164             "16:5: " + getCheckMessage(MSG_KEY, "SubClass"),
165             "19:5: " + getCheckMessage(MSG_KEY, "SameName"),
166             "45:9: " + getCheckMessage(MSG_KEY, "SameName"),
167             "69:13: " + getCheckMessage(MSG_KEY, "B"),
168             "84:9: " + getCheckMessage(MSG_KEY, "c"),
169         };
170         verifyWithInlineConfigParser(getPath("InputFinalClassInnerAndNestedClass.java"), expected);
171     }
172 
173     @Test
174     public void testFinalClassStaticNestedClasses() throws Exception {
175 
176         final String[] expected = {
177             "14:17: " + getCheckMessage(MSG_KEY, "C"),
178             "32:9: " + getCheckMessage(MSG_KEY, "B"),
179             "43:9: " + getCheckMessage(MSG_KEY, "C"),
180             "60:13: " + getCheckMessage(MSG_KEY, "Q"),
181             "76:9: " + getCheckMessage(MSG_KEY, "F"),
182             "83:9: " + getCheckMessage(MSG_KEY, "c"),
183         };
184 
185         verifyWithInlineConfigParser(
186                 getNonCompilablePath("InputFinalClassNestedStaticClassInsideInnerClass.java"),
187                 expected);
188     }
189 
190     @Test
191     public void testFinalClassEnum() throws Exception {
192         final String[] expected = {
193             "35:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
194         };
195         verifyWithInlineConfigParser(getPath("InputFinalClassEnum.java"), expected);
196     }
197 
198     @Test
199     public void testFinalClassAnnotation() throws Exception {
200         final String[] expected = {
201             "15:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
202         };
203         verifyWithInlineConfigParser(getPath("InputFinalClassAnnotation.java"), expected);
204     }
205 
206     @Test
207     public void testFinalClassInterface() throws Exception {
208         final String[] expected = {
209             "15:5: " + getCheckMessage(MSG_KEY, "DerivedClass"),
210         };
211         verifyWithInlineConfigParser(getPath("InputFinalClassInterface.java"), expected);
212     }
213 
214     @Test
215     public void testFinalClassAnonymousInnerClass() throws Exception {
216         final String[] expected = {
217             "11:9: " + getCheckMessage(MSG_KEY, "b"),
218             "27:9: " + getCheckMessage(MSG_KEY, "m"),
219             "40:9: " + getCheckMessage(MSG_KEY, "q"),
220             "52:13: " + getCheckMessage(MSG_KEY, "b"),
221             "67:9: " + getCheckMessage(MSG_KEY, "g"),
222             "71:9: " + getCheckMessage(MSG_KEY, "y"),
223             "84:9: " + getCheckMessage(MSG_KEY, "n"),
224             "91:9: " + getCheckMessage(MSG_KEY, "n"),
225         };
226         verifyWithInlineConfigParser(getPath("InputFinalClassAnonymousInnerClass.java"), expected);
227     }
228 
229     @Test
230     public void testFinalClassNestedInInterface() throws Exception {
231         final String[] expected = {
232             "24:5: " + getCheckMessage(MSG_KEY, "b"),
233             "28:13: " + getCheckMessage(MSG_KEY, "m"),
234             "50:5: " + getCheckMessage(MSG_KEY, "c"),
235         };
236         verifyWithInlineConfigParser(
237             getPath("InputFinalClassNestedInInterfaceWithAnonInnerClass.java"), expected);
238     }
239 
240     @Test
241     public void testFinalClassNestedInEnum() throws Exception {
242         final String[] expected = {
243             "13:9: " + getCheckMessage(MSG_KEY, "j"),
244             "27:9: " + getCheckMessage(MSG_KEY, "n"),
245         };
246         verifyWithInlineConfigParser(getPath("InputFinalClassNestedInEnumWithAnonInnerClass.java"),
247                                      expected);
248     }
249 
250     @Test
251     public void testFinalClassNestedInRecord() throws Exception {
252         final String[] expected = {
253             "13:9: " + getCheckMessage(MSG_KEY, "c"),
254             "31:13: " + getCheckMessage(MSG_KEY, "j"),
255             "49:5: " + getCheckMessage(MSG_KEY, "Nothing"),
256         };
257         verifyWithInlineConfigParser(getNonCompilablePath("InputFinalClassNestedInRecord.java"),
258                                      expected);
259     }
260 
261     /**
262      * We cannot reproduce situation when visitToken is called and leaveToken is not.
263      * So, we have to use reflection to be sure that even in such situation
264      * state of the field will be cleared.
265      *
266      * @throws Exception when code tested throws exception
267      */
268     @Test
269     public void testClearState() throws Exception {
270         final FinalClassCheck check = new FinalClassCheck();
271         final DetailAST root = JavaParser.parseFile(new File(getPath("InputFinalClass.java")),
272                 JavaParser.Options.WITHOUT_COMMENTS);
273         final Optional<DetailAST> packageDef = TestUtil.findTokenInAstByPredicate(root,
274             ast -> ast.getType() == TokenTypes.PACKAGE_DEF);
275 
276         assertWithMessage("Ast should contain PACKAGE_DEF")
277                 .that(packageDef.isPresent())
278                 .isTrue();
279         assertWithMessage("State is not cleared on beginTree")
280                 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check,
281                         packageDef.orElseThrow(), "packageName",
282                         packageName -> ((String) packageName).isEmpty()))
283                 .isTrue();
284     }
285 
286     @Test
287     public void testPrivateClassWithDefaultCtor() throws Exception {
288         final String[] expected = {
289             "14:5: " + getCheckMessage(MSG_KEY, "Some2"),
290             "19:1: " + getCheckMessage(MSG_KEY, "Some"),
291             "24:5: " + getCheckMessage(MSG_KEY, "Some3"),
292             "26:5: " + getCheckMessage(MSG_KEY, "Some4"),
293             "31:5: " + getCheckMessage(MSG_KEY, "PaperSetter"),
294             "36:5: " + getCheckMessage(MSG_KEY, "Paper"),
295             "44:5: " + getCheckMessage(MSG_KEY, "Node"),
296             "51:5: " + getCheckMessage(MSG_KEY, "Some1"),
297             "55:1: " + getCheckMessage(MSG_KEY, "Some2"),
298             "105:5: " + getCheckMessage(MSG_KEY, "NewCheck"),
299             "108:5: " + getCheckMessage(MSG_KEY, "NewCheck2"),
300             "112:5: " + getCheckMessage(MSG_KEY, "OldCheck"),
301         };
302         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor.java"),
303                                      expected);
304     }
305 
306     @Test
307     public void testPrivateClassWithDefaultCtor2() throws Exception {
308         final String[] expected = {
309             "22:5: " + getCheckMessage(MSG_KEY, "PrivateClass"),
310             "34:5: " + getCheckMessage(MSG_KEY, "Check"),
311             "44:5: " + getCheckMessage(MSG_KEY, "K"),
312             "54:5: " + getCheckMessage(MSG_KEY, "Modifiers"),
313         };
314         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor2.java"),
315                                      expected);
316     }
317 
318     @Test
319     public void testPrivateClassWithDefaultCtor3() throws Exception {
320         final String[] expected = {
321             "26:5: " + getCheckMessage(MSG_KEY, "MyClass"),
322             "30:5: " + getCheckMessage(MSG_KEY, "Check2"),
323             "31:9: " + getCheckMessage(MSG_KEY, "Check3"),
324             "35:5: " + getCheckMessage(MSG_KEY, "Check4"),
325             "40:5: " + getCheckMessage(MSG_KEY, "Check"),
326         };
327         verifyWithInlineConfigParser(getPath("InputFinalClassPrivateCtor3.java"),
328                                      expected);
329     }
330 }