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