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