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