1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.grammar;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.File;
28  import java.io.InputStream;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.nio.charset.StandardCharsets;
33  import java.util.Arrays;
34  
35  import org.junit.Test;
36  
37  import antlr.NoViableAltForCharException;
38  import antlr.ParserSharedInputState;
39  import antlr.SemanticException;
40  import antlr.TokenBuffer;
41  import com.puppycrawl.tools.checkstyle.AbstractTreeTestSupport;
42  import com.puppycrawl.tools.checkstyle.AstTreeStringPrinter;
43  import com.puppycrawl.tools.checkstyle.JavaParser;
44  import com.puppycrawl.tools.checkstyle.api.FileText;
45  
46  public class AstRegressionTest extends AbstractTreeTestSupport {
47  
48      @Override
49      protected String getPackageLocation() {
50          return "com/puppycrawl/tools/checkstyle/grammar";
51      }
52  
53      @Test
54      public void testClassAstTree1() throws Exception {
55          verifyAst(getPath("InputRegressionJavaClass1Ast.txt"),
56                  getPath("InputRegressionJavaClass1.java"));
57      }
58  
59      @Test
60      public void testClassAstTree2() throws Exception {
61          verifyAst(getPath("InputRegressionJavaClass2Ast.txt"),
62                  getPath("InputRegressionJavaClass2.java"));
63      }
64  
65      @Test
66      public void testJava8ClassAstTree1() throws Exception {
67          verifyAst(getPath("InputRegressionJava8Class1Ast.txt"),
68                  getPath("InputRegressionJava8Class1.java"));
69      }
70  
71      @Test
72      public void testInputSemicolonBetweenImports() throws Exception {
73          verifyAst(getPath("InputSemicolonBetweenImportsAst.txt"),
74                  getNonCompilablePath("InputSemicolonBetweenImports.java"));
75      }
76  
77      @Test
78      public void testInterfaceAstTree1() throws Exception {
79          verifyAst(getPath("InputRegressionJavaInterface1Ast.txt"),
80                  getPath("InputRegressionJavaInterface1.java"));
81      }
82  
83      @Test
84      public void testInterfaceAstTree2() throws Exception {
85          verifyAst(getPath("InputRegressionJavaInterface2Ast.txt"),
86                  getPath("InputRegressionJavaInterface2.java"));
87      }
88  
89      @Test
90      public void testJava8InterfaceAstTree1() throws Exception {
91          verifyAst(getPath("InputRegressionJava8Interface1Ast.txt"),
92                  getPath("InputRegressionJava8Interface1.java"));
93      }
94  
95      @Test
96      public void testEnumAstTree1() throws Exception {
97          verifyAst(getPath("InputRegressionJavaEnum1Ast.txt"),
98                  getPath("InputRegressionJavaEnum1.java"));
99      }
100 
101     @Test
102     public void testEnumAstTree2() throws Exception {
103         verifyAst(getPath("InputRegressionJavaEnum2Ast.txt"),
104                 getPath("InputRegressionJavaEnum2.java"));
105     }
106 
107     @Test
108     public void testAnnotationAstTree1() throws Exception {
109         verifyAst(getPath("InputRegressionJavaAnnotation1Ast.txt"),
110                 getPath("InputRegressionJavaAnnotation1.java"));
111     }
112 
113     @Test
114     public void testTypecast() throws Exception {
115         verifyAst(getPath("InputRegressionJavaTypecastAst.txt"),
116                 getPath("InputRegressionJavaTypecast.java"));
117     }
118 
119     @Test
120     public void testUnusedConstructors1() throws Exception {
121         final Class<?> clss = GeneratedJavaLexer.class;
122         final Constructor<?> constructor = clss.getDeclaredConstructor(InputStream.class);
123 
124         assertNotNull("InputStream should not be null",
125                 constructor.newInstance(new Object[] {null}));
126     }
127 
128     @Test
129     public void testUnusedConstructors2() throws Exception {
130         final Class<?> clss = GeneratedJavaRecognizer.class;
131         final Constructor<?> constructor = clss
132                 .getDeclaredConstructor(ParserSharedInputState.class);
133 
134         assertNotNull("ParserSharedInputState should not be null",
135                 constructor.newInstance(new Object[] {null}));
136     }
137 
138     @Test
139     public void testUnusedConstructors3() throws Exception {
140         final Class<?> clss = GeneratedJavaRecognizer.class;
141         final Constructor<?> constructor = clss.getDeclaredConstructor(TokenBuffer.class);
142 
143         assertNotNull("TokenBuffer should not be null",
144                 constructor.newInstance(new Object[] {null}));
145     }
146 
147     @Test
148     public void testCustomAstTree() throws Exception {
149         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\t");
150         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\r\n");
151         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\n");
152         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\r\r");
153         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\r");
154         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "\u000c\f");
155         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "// \n",
156                 JavaParser.Options.WITH_COMMENTS);
157         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "// \r",
158                 JavaParser.Options.WITH_COMMENTS);
159         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "// \r\n",
160                 JavaParser.Options.WITH_COMMENTS);
161         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "/* \n */",
162                 JavaParser.Options.WITH_COMMENTS);
163         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "/* \r\n */",
164                 JavaParser.Options.WITH_COMMENTS);
165         verifyAstRaw(getPath("InputRegressionEmptyAst.txt"), "/* \r" + "\u0000\u0000" + " */",
166                 JavaParser.Options.WITH_COMMENTS);
167     }
168 
169     @Test
170     public void testNewlineCr() throws Exception {
171         verifyAst(getPath("InputNewlineCrAtEndOfFileAst.txt"),
172                 getPath("InputAstRegressionNewlineCrAtEndOfFile.java"),
173                 JavaParser.Options.WITH_COMMENTS);
174     }
175 
176     @Test
177     public void testImpossibleExceptions() throws Exception {
178         AssertGeneratedJavaLexer.verifyFail("mSTD_ESC", 'a');
179         AssertGeneratedJavaLexer.verifyFail("mSTD_ESC", '0', (char) 0xFFFF);
180         AssertGeneratedJavaLexer.verifyFail("mSTD_ESC", '4', (char) 0xFFFF);
181         AssertGeneratedJavaLexer.verifyFail("mCHAR_LITERAL", '\'', '\'');
182         AssertGeneratedJavaLexer.verifyFail("mHEX_DIGIT", ';');
183         AssertGeneratedJavaLexer.verifyFail("mEXPONENT", ';');
184         AssertGeneratedJavaLexer.verifyFail("mBINARY_DIGIT", '2');
185         AssertGeneratedJavaLexer.verifyFail("mSIGNED_INTEGER", 'a');
186         AssertGeneratedJavaLexer.verifyFail("mID_START", '%');
187         AssertGeneratedJavaLexer.verifyFail("mID_START", (char) 0xBF);
188         AssertGeneratedJavaLexer.verifyFailNoGuessing("mID_START", (char) 0xBF);
189         AssertGeneratedJavaLexer.verifyFail("mID_PART", '%');
190         AssertGeneratedJavaLexer.verifyFail("mID_PART", (char) 0xBF);
191         AssertGeneratedJavaLexer.verifyFailNoGuessing("mID_PART", (char) 0xBF);
192         AssertGeneratedJavaLexer.verifyFail("mESC", '\\', 'a');
193         AssertGeneratedJavaLexer.verifyFail("mLONG_LITERAL", '0', ';');
194         AssertGeneratedJavaLexer.verifyFail("mLONG_LITERAL", '1', ';');
195         AssertGeneratedJavaLexer.verifyFail("mLONG_LITERAL", ';');
196         AssertGeneratedJavaLexer.verifyFail("mINT_LITERAL", ';');
197         AssertGeneratedJavaLexer.verifyFail("mHEX_DOUBLE_LITERAL", '0', 'a');
198         AssertGeneratedJavaLexer.verifyFail("mHEX_FLOAT_LITERAL", '0', 'a');
199     }
200 
201     @Test
202     public void testImpossibleValid() throws Exception {
203         AssertGeneratedJavaLexer.verifyPass("mSTD_ESC", 'n');
204         AssertGeneratedJavaLexer.verifyPass("mELLIPSIS", '.', '.', '.');
205         AssertGeneratedJavaLexer.verifyPass("mDOT", '.');
206         AssertGeneratedJavaLexer.verifyPass("mBINARY_EXPONENT", 'p', '0', ';');
207         AssertGeneratedJavaLexer.verifyPass("mHEX_DIGIT", '0');
208         AssertGeneratedJavaLexer.verifyPass("mEXPONENT", 'e', '0', ';');
209         AssertGeneratedJavaLexer.verifyPass("mBINARY_DIGIT", '0');
210         AssertGeneratedJavaLexer.verifyPass("mSIGNED_INTEGER", '0', ';');
211         AssertGeneratedJavaLexer.verifyPass("mWS", ' ', ';');
212         AssertGeneratedJavaLexer.verifyPass("mID_START", '$');
213         AssertGeneratedJavaLexer.verifyPass("mID_PART", '$');
214         AssertGeneratedJavaLexer.verifyPass("mESC", '\\', '\\');
215         AssertGeneratedJavaLexer.verifyPass("mLONG_LITERAL", '1', 'L');
216         AssertGeneratedJavaLexer.verifyPass("mINT_LITERAL", '0', ';');
217         AssertGeneratedJavaLexer.verifyPass("mFLOAT_LITERAL", '0', 'f');
218         AssertGeneratedJavaLexer.verifyPass("mDOUBLE_LITERAL", '0', 'd');
219         AssertGeneratedJavaLexer.verifyPass("mHEX_FLOAT_LITERAL", '0', 'x', '2', '_', '4', '.',
220                 '4', '4', '.', '4', 'P', '4', ';');
221         AssertGeneratedJavaLexer.verifyPass("mHEX_DOUBLE_LITERAL", '0', 'x', '2', '_', '4', '.',
222                 '4', '4', '.', '4', 'P', '4', 'D', ';');
223     }
224 
225     private static void verifyAstRaw(String expectedTextPrintFileName, String actualJava)
226             throws Exception {
227         verifyAstRaw(expectedTextPrintFileName, actualJava, JavaParser.Options.WITHOUT_COMMENTS);
228     }
229 
230     private static void verifyAstRaw(String expectedTextPrintFileName, String actualJava,
231             JavaParser.Options withComments) throws Exception {
232         final File expectedFile = new File(expectedTextPrintFileName);
233         final String expectedContents = new FileText(expectedFile, System.getProperty(
234                 "file.encoding", StandardCharsets.UTF_8.name()))
235                 .getFullText().toString().replace("\r", "");
236 
237         final FileText actualFileContents = new FileText(new File(""),
238                 Arrays.asList(actualJava.split("\\n|\\r\\n?")));
239         final String actualContents = AstTreeStringPrinter.printAst(actualFileContents,
240                 withComments);
241 
242         assertEquals("Generated AST from Java code should match pre-defined AST", expectedContents,
243                 actualContents);
244     }
245 
246     private static final class AssertGeneratedJavaLexer extends GeneratedJavaLexer {
247 
248         private int laPosition;
249         private char[] laResults;
250 
251         private AssertGeneratedJavaLexer() {
252             super((InputStream) null);
253         }
254 
255         public static void verifyFailNoGuessing(String methodName, char... laResults)
256                 throws Exception {
257             verify(methodName, false, 0, laResults);
258         }
259 
260         public static void verifyPass(String methodName, char... laResults) throws Exception {
261             verify(methodName, true, 1, laResults);
262         }
263 
264         public static void verifyFail(String methodName, char... laResults) throws Exception {
265             verify(methodName, false, 1, laResults);
266         }
267 
268         private static void verify(String methodName, boolean expectPass, int guessing,
269                 char... laResults) throws Exception {
270             final AssertGeneratedJavaLexer instance = new AssertGeneratedJavaLexer();
271             instance.laPosition = 0;
272             instance.laResults = laResults.clone();
273             instance.inputState.guessing = guessing;
274 
275             final Method method = GeneratedJavaLexer.class.getDeclaredMethod(methodName,
276                     boolean.class);
277             boolean exception;
278 
279             try {
280                 method.invoke(instance, true);
281                 exception = false;
282             }
283             catch (InvocationTargetException ex) {
284                 if (expectPass) {
285                     throw ex;
286                 }
287 
288                 final Class<?> clss = ex.getTargetException().getClass();
289                 if (clss != NoViableAltForCharException.class
290                         && clss != SemanticException.class) {
291                     throw ex;
292                 }
293                 exception = true;
294             }
295 
296             if (expectPass) {
297                 assertFalse("Call to GeneratedJavaLexer." + methodName
298                         + " resulted in an exception", exception);
299             }
300             else {
301                 assertTrue("Call to GeneratedJavaLexer." + methodName
302                         + " did not result in an exception", exception);
303             }
304         }
305 
306         @Override
307         public char LA(int i) {
308             return laResults[laPosition + i - 1];
309         }
310 
311         @Override
312         public void consume() {
313             laPosition++;
314         }
315 
316         @Override
317         public int mark() {
318             return 1;
319         }
320 
321     }
322 
323 }