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