1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Modifier;
29 import java.nio.charset.StandardCharsets;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35
36 import org.antlr.v4.runtime.CharStream;
37 import org.antlr.v4.runtime.CharStreams;
38 import org.antlr.v4.runtime.CommonTokenStream;
39 import org.junit.jupiter.api.Test;
40
41 import com.puppycrawl.tools.checkstyle.api.DetailAST;
42 import com.puppycrawl.tools.checkstyle.api.FileContents;
43 import com.puppycrawl.tools.checkstyle.api.FileText;
44 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
45 import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
46 import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
47 import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParserBaseVisitor;
48 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
49
50
51
52
53
54
55
56
57
58
59
60 public class JavaAstVisitorTest extends AbstractModuleTestSupport {
61
62
63
64
65
66
67
68
69
70 private static final Set<String> VISIT_METHODS_NOT_OVERRIDDEN = Set.of(
71
72 "visitClassOrInterfaceOrPrimitiveType",
73 "visitNonWildcardTypeArgs",
74 "visitStat",
75 "visitAnnotationConstantRest",
76 "visitSwitchLabeledRule",
77 "visitLocalVariableDeclaration",
78 "visitTypes",
79 "visitSwitchStat",
80 "visitSwitchPrimary",
81 "visitClassDef",
82 "visitInterfaceMemberDeclaration",
83 "visitMemberDeclaration",
84 "visitLiteralPrimary",
85 "visitPatternDefinition",
86 "visitLocalType",
87 "visitLocalTypeDeclaration",
88 "visitRecordBodyDeclaration",
89 "visitResource",
90 "visitVariableInitializer",
91 "visitLambdaBody",
92 "visitPatternVariableDef",
93
94
95 "visitCreatedNameExtended",
96 "visitSuperSuffixSimple",
97 "visitFieldAccessNoIdent",
98 "visitClassType",
99 "visitClassOrInterfaceTypeExtended",
100 "visitQualifiedNameExtended",
101 "visitGuard"
102 );
103
104 @Override
105 public String getPackageLocation() {
106 return "com/puppycrawl/tools/checkstyle/javaastvisitor";
107 }
108
109 @Test
110 public void testAllVisitMethodsAreOverridden() {
111 final Method[] baseVisitMethods = JavaLanguageParserBaseVisitor
112 .class.getDeclaredMethods();
113 final Method[] visitMethods = JavaAstVisitor.class.getDeclaredMethods();
114
115 final Set<String> filteredBaseVisitMethodNames = Arrays.stream(baseVisitMethods)
116 .filter(method -> !VISIT_METHODS_NOT_OVERRIDDEN.contains(method.getName()))
117 .filter(method -> method.getName().contains("visit"))
118 .filter(method -> method.getModifiers() == Modifier.PUBLIC)
119 .map(Method::getName)
120 .collect(Collectors.toUnmodifiableSet());
121
122 final Set<String> filteredVisitMethodNames = Arrays.stream(visitMethods)
123 .filter(method -> method.getName().contains("visit"))
124
125
126 .filter(method -> !"visit".equals(method.getName()))
127 .filter(method -> method.getModifiers() == Modifier.PUBLIC)
128 .map(Method::getName)
129 .collect(Collectors.toUnmodifiableSet());
130
131 assertWithMessage(
132 "Visit methods in 'JavaLanguageParserBaseVisitor' generated from production "
133 + "rules and labeled alternatives in 'JavaLanguageParser.g4' should be "
134 + "overridden in 'JavaAstVisitor' or be added to 'VISIT_METHODS_NOT_OVERRIDDEN' "
135 + "with comment explaining why.")
136 .that(filteredVisitMethodNames)
137 .containsExactlyElementsIn(filteredBaseVisitMethodNames);
138 }
139
140 @Test
141 public void testOrderOfVisitMethodsAndProductionRules() throws Exception {
142
143
144 final String baseVisitorFilename = "target/generated-sources/antlr/com/puppycrawl"
145 + "/tools/checkstyle/grammar/java/JavaLanguageParserBaseVisitor.java";
146 final DetailAST baseVisitorAst = JavaParser.parseFile(new File(baseVisitorFilename),
147 JavaParser.Options.WITHOUT_COMMENTS);
148
149 final String visitorFilename = "src/main/java/com/puppycrawl/tools/checkstyle"
150 + "/JavaAstVisitor.java";
151 final DetailAST visitorAst = JavaParser.parseFile(new File(visitorFilename),
152 JavaParser.Options.WITHOUT_COMMENTS);
153
154 final List<String> orderedBaseVisitorMethodNames =
155 getOrderedVisitMethodNames(baseVisitorAst);
156 final List<String> orderedVisitorMethodNames =
157 getOrderedVisitMethodNames(visitorAst);
158
159 orderedBaseVisitorMethodNames.removeAll(VISIT_METHODS_NOT_OVERRIDDEN);
160
161
162 orderedVisitorMethodNames.remove("visit");
163
164 assertWithMessage("Visit methods in 'JavaAstVisitor' should appear in same order as "
165 + "production rules and labeled alternatives in 'JavaLanguageParser.g4'.")
166 .that(orderedVisitorMethodNames)
167 .containsExactlyElementsIn(orderedBaseVisitorMethodNames)
168 .inOrder();
169 }
170
171
172
173
174
175
176
177
178
179
180 @Test
181 public void countExprUsagesInParserGrammar() throws IOException {
182 final String parserGrammarFilename = "src/main/resources/com/puppycrawl"
183 + "/tools/checkstyle/grammar/java/JavaLanguageParser.g4";
184
185 final int actualExprCount = Arrays.stream(new FileText(new File(parserGrammarFilename),
186 StandardCharsets.UTF_8.name()).toLinesArray())
187 .mapToInt(JavaAstVisitorTest::countExprInLine)
188 .sum();
189
190
191
192 final int expectedExprCount = 44;
193
194 assertWithMessage("The 'expr' parser rule does not build an imaginary"
195 + " 'EXPR' node. Any usage of this rule should be questioned.")
196 .that(actualExprCount)
197 .isEqualTo(expectedExprCount);
198
199 }
200
201 private static int countExprInLine(String line) {
202 return (int) Arrays.stream(line.split(" "))
203 .filter("expr"::equals)
204 .count();
205 }
206
207
208
209
210
211
212
213
214
215 private static List<String> getOrderedVisitMethodNames(DetailAST root) {
216 final List<String> orderedVisitMethodNames = new ArrayList<>();
217
218 DetailAST classDef = root.getFirstChild();
219 while (classDef.getType() != TokenTypes.CLASS_DEF) {
220 classDef = classDef.getNextSibling();
221 }
222
223 final DetailAST objBlock = classDef.findFirstToken(TokenTypes.OBJBLOCK);
224 DetailAST objBlockChild = objBlock.findFirstToken(TokenTypes.METHOD_DEF);
225 while (objBlockChild != null) {
226 if (isVisitMethod(objBlockChild)) {
227 orderedVisitMethodNames.add(objBlockChild
228 .findFirstToken(TokenTypes.IDENT)
229 .getText());
230 }
231 objBlockChild = objBlockChild.getNextSibling();
232 }
233 return orderedVisitMethodNames;
234 }
235
236
237
238
239
240
241
242 private static boolean isVisitMethod(DetailAST objBlockChild) {
243 return objBlockChild.getType() == TokenTypes.METHOD_DEF
244 && objBlockChild.findFirstToken(TokenTypes.IDENT).getText().contains("visit");
245 }
246
247 @Test
248 public void testNullSelfInAddLastSibling() {
249 assertDoesNotThrow(() -> {
250 TestUtil.invokeVoidStaticMethod(JavaAstVisitor.class, "addLastSibling",
251 null, null);
252 }, "Method should not throw exception.");
253 }
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273 @Test
274 public void testNoStackOverflowOnDeepStringConcat() throws Exception {
275 final File file =
276 new File(getPath("InputJavaAstVisitorNoStackOverflowOnDeepStringConcat.java"));
277 final FileText fileText = new FileText(file, StandardCharsets.UTF_8.name());
278 final FileContents contents = new FileContents(fileText);
279
280 final String fullText = contents.getText().getFullText().toString();
281 final CharStream codePointCharStream = CharStreams.fromString(fullText);
282 final JavaLanguageLexer lexer = new JavaLanguageLexer(codePointCharStream, true);
283 lexer.setCommentListener(contents);
284
285 final CommonTokenStream tokenStream = new CommonTokenStream(lexer);
286 final JavaLanguageParser parser = new JavaLanguageParser(tokenStream);
287
288 final JavaLanguageParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
289
290
291
292
293
294 final DetailAST root = TestUtil.getResultWithLimitedResources(
295 () -> new JavaAstVisitor(tokenStream).visit(compilationUnit)
296 );
297
298 assertWithMessage("File parsing and AST building should complete successfully.")
299 .that(root)
300 .isNotNull();
301 }
302 }