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