1 | //===---- QueryParserTest.cpp - clang-query test --------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "QueryParser.h" |
10 | #include "Query.h" |
11 | #include "QuerySession.h" |
12 | #include "llvm/LineEditor/LineEditor.h" |
13 | #include "gtest/gtest.h" |
14 | |
15 | using namespace clang; |
16 | using namespace clang::query; |
17 | |
18 | class QueryParserTest : public ::testing::Test { |
19 | protected: |
20 | QueryParserTest() : QS(llvm::ArrayRef<std::unique_ptr<ASTUnit>>()) {} |
21 | QueryRef parse(StringRef Code) { return QueryParser::parse(Line: Code, QS); } |
22 | |
23 | QuerySession QS; |
24 | }; |
25 | |
26 | TEST_F(QueryParserTest, NoOp) { |
27 | QueryRef Q = parse(Code: "" ); |
28 | EXPECT_TRUE(isa<NoOpQuery>(Q)); |
29 | |
30 | Q = parse(Code: "\n" ); |
31 | EXPECT_TRUE(isa<NoOpQuery>(Q)); |
32 | } |
33 | |
34 | TEST_F(QueryParserTest, Invalid) { |
35 | QueryRef Q = parse(Code: "foo" ); |
36 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
37 | EXPECT_EQ("unknown command: foo" , cast<InvalidQuery>(Q)->ErrStr); |
38 | } |
39 | |
40 | TEST_F(QueryParserTest, Help) { |
41 | QueryRef Q = parse(Code: "help" ); |
42 | ASSERT_TRUE(isa<HelpQuery>(Q)); |
43 | |
44 | Q = parse(Code: "help me" ); |
45 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
46 | EXPECT_EQ("unexpected extra input: ' me'" , cast<InvalidQuery>(Q)->ErrStr); |
47 | } |
48 | |
49 | TEST_F(QueryParserTest, Quit) { |
50 | QueryRef Q = parse(Code: "quit" ); |
51 | ASSERT_TRUE(isa<QuitQuery>(Q)); |
52 | |
53 | Q = parse(Code: "q" ); |
54 | ASSERT_TRUE(isa<QuitQuery>(Q)); |
55 | |
56 | Q = parse(Code: "quit me" ); |
57 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
58 | EXPECT_EQ("unexpected extra input: ' me'" , cast<InvalidQuery>(Q)->ErrStr); |
59 | } |
60 | |
61 | TEST_F(QueryParserTest, Set) { |
62 | |
63 | QueryRef Q = parse(Code: "set" ); |
64 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
65 | EXPECT_EQ("expected variable name" , cast<InvalidQuery>(Q)->ErrStr); |
66 | |
67 | Q = parse(Code: "set foo bar" ); |
68 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
69 | EXPECT_EQ("unknown variable: 'foo'" , cast<InvalidQuery>(Q)->ErrStr); |
70 | |
71 | Q = parse(Code: "set output" ); |
72 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
73 | EXPECT_EQ("expected 'diag', 'print', 'detailed-ast' or 'dump', got ''" , |
74 | cast<InvalidQuery>(Q)->ErrStr); |
75 | |
76 | Q = parse(Code: "set bind-root true foo" ); |
77 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
78 | EXPECT_EQ("unexpected extra input: ' foo'" , cast<InvalidQuery>(Q)->ErrStr); |
79 | |
80 | Q = parse(Code: "set output foo" ); |
81 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
82 | EXPECT_EQ("expected 'diag', 'print', 'detailed-ast' or 'dump', got 'foo'" , |
83 | cast<InvalidQuery>(Q)->ErrStr); |
84 | |
85 | Q = parse(Code: "set output dump" ); |
86 | ASSERT_TRUE(isa<SetExclusiveOutputQuery >(Q)); |
87 | EXPECT_EQ(&QuerySession::DetailedASTOutput, cast<SetExclusiveOutputQuery>(Q)->Var); |
88 | |
89 | Q = parse(Code: "set output detailed-ast" ); |
90 | ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q)); |
91 | EXPECT_EQ(&QuerySession::DetailedASTOutput, cast<SetExclusiveOutputQuery>(Q)->Var); |
92 | |
93 | Q = parse(Code: "enable output detailed-ast" ); |
94 | ASSERT_TRUE(isa<EnableOutputQuery>(Q)); |
95 | EXPECT_EQ(&QuerySession::DetailedASTOutput, cast<EnableOutputQuery>(Q)->Var); |
96 | |
97 | Q = parse(Code: "enable" ); |
98 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
99 | EXPECT_EQ("expected variable name" , cast<InvalidQuery>(Q)->ErrStr); |
100 | |
101 | Q = parse(Code: "disable output detailed-ast" ); |
102 | ASSERT_TRUE(isa<DisableOutputQuery>(Q)); |
103 | EXPECT_EQ(&QuerySession::DetailedASTOutput, cast<DisableOutputQuery>(Q)->Var); |
104 | |
105 | Q = parse(Code: "set bind-root foo" ); |
106 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
107 | EXPECT_EQ("expected 'true' or 'false', got 'foo'" , |
108 | cast<InvalidQuery>(Q)->ErrStr); |
109 | |
110 | Q = parse(Code: "set bind-root true" ); |
111 | ASSERT_TRUE(isa<SetQuery<bool> >(Q)); |
112 | EXPECT_EQ(&QuerySession::BindRoot, cast<SetQuery<bool> >(Q)->Var); |
113 | EXPECT_EQ(true, cast<SetQuery<bool> >(Q)->Value); |
114 | |
115 | Q = parse(Code: "set traversal AsIs" ); |
116 | ASSERT_TRUE(isa<SetQuery<TraversalKind>>(Q)); |
117 | EXPECT_EQ(&QuerySession::TK, cast<SetQuery<TraversalKind>>(Q)->Var); |
118 | EXPECT_EQ(TK_AsIs, cast<SetQuery<TraversalKind>>(Q)->Value); |
119 | |
120 | Q = parse(Code: "set traversal NotATraversal" ); |
121 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
122 | EXPECT_EQ("expected traversal kind, got 'NotATraversal'" , |
123 | cast<InvalidQuery>(Q)->ErrStr); |
124 | } |
125 | |
126 | TEST_F(QueryParserTest, Match) { |
127 | QueryRef Q = parse(Code: "match decl()" ); |
128 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
129 | EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Decl>()); |
130 | |
131 | Q = parse(Code: "m stmt()" ); |
132 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
133 | EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Stmt>()); |
134 | } |
135 | |
136 | TEST_F(QueryParserTest, LetUnlet) { |
137 | QueryRef Q = parse(Code: "let foo decl()" ); |
138 | ASSERT_TRUE(isa<LetQuery>(Q)); |
139 | EXPECT_EQ("foo" , cast<LetQuery>(Q)->Name); |
140 | EXPECT_TRUE(cast<LetQuery>(Q)->Value.isMatcher()); |
141 | EXPECT_TRUE(cast<LetQuery>(Q)->Value.getMatcher().hasTypedMatcher<Decl>()); |
142 | |
143 | Q = parse(Code: "l foo decl()" ); |
144 | ASSERT_TRUE(isa<LetQuery>(Q)); |
145 | EXPECT_EQ("foo" , cast<LetQuery>(Q)->Name); |
146 | EXPECT_TRUE(cast<LetQuery>(Q)->Value.isMatcher()); |
147 | EXPECT_TRUE(cast<LetQuery>(Q)->Value.getMatcher().hasTypedMatcher<Decl>()); |
148 | |
149 | Q = parse(Code: "let bar \"str\"" ); |
150 | ASSERT_TRUE(isa<LetQuery>(Q)); |
151 | EXPECT_EQ("bar" , cast<LetQuery>(Q)->Name); |
152 | EXPECT_TRUE(cast<LetQuery>(Q)->Value.isString()); |
153 | EXPECT_EQ("str" , cast<LetQuery>(Q)->Value.getString()); |
154 | |
155 | Q = parse(Code: "let" ); |
156 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
157 | EXPECT_EQ("expected variable name" , cast<InvalidQuery>(Q)->ErrStr); |
158 | |
159 | Q = parse(Code: "unlet x" ); |
160 | ASSERT_TRUE(isa<LetQuery>(Q)); |
161 | EXPECT_EQ("x" , cast<LetQuery>(Q)->Name); |
162 | EXPECT_FALSE(cast<LetQuery>(Q)->Value.hasValue()); |
163 | |
164 | Q = parse(Code: "unlet" ); |
165 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
166 | EXPECT_EQ("expected variable name" , cast<InvalidQuery>(Q)->ErrStr); |
167 | |
168 | Q = parse(Code: "unlet x bad_data" ); |
169 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
170 | EXPECT_EQ("unexpected extra input: ' bad_data'" , |
171 | cast<InvalidQuery>(Q)->ErrStr); |
172 | } |
173 | |
174 | TEST_F(QueryParserTest, Comment) { |
175 | QueryRef Q = parse(Code: "# let foo decl()" ); |
176 | ASSERT_TRUE(isa<NoOpQuery>(Q)); |
177 | |
178 | Q = parse(Code: "let foo decl() # creates a decl() matcher called foo" ); |
179 | ASSERT_TRUE(isa<LetQuery>(Q)); |
180 | |
181 | Q = parse(Code: "set bind-root false # reduce noise" ); |
182 | ASSERT_TRUE(isa<SetQuery<bool>>(Q)); |
183 | } |
184 | |
185 | TEST_F(QueryParserTest, Complete) { |
186 | std::vector<llvm::LineEditor::Completion> Comps = |
187 | QueryParser::complete(Line: "" , Pos: 0, QS); |
188 | ASSERT_EQ(9u, Comps.size()); |
189 | EXPECT_EQ("help " , Comps[0].TypedText); |
190 | EXPECT_EQ("help" , Comps[0].DisplayText); |
191 | EXPECT_EQ("let " , Comps[1].TypedText); |
192 | EXPECT_EQ("let" , Comps[1].DisplayText); |
193 | EXPECT_EQ("match " , Comps[2].TypedText); |
194 | EXPECT_EQ("match" , Comps[2].DisplayText); |
195 | EXPECT_EQ("quit " , Comps[3].TypedText); |
196 | EXPECT_EQ("quit" , Comps[3].DisplayText); |
197 | EXPECT_EQ("set " , Comps[4].TypedText); |
198 | EXPECT_EQ("set" , Comps[4].DisplayText); |
199 | EXPECT_EQ("enable " , Comps[5].TypedText); |
200 | EXPECT_EQ("enable" , Comps[5].DisplayText); |
201 | EXPECT_EQ("disable " , Comps[6].TypedText); |
202 | EXPECT_EQ("disable" , Comps[6].DisplayText); |
203 | EXPECT_EQ("unlet " , Comps[7].TypedText); |
204 | EXPECT_EQ("unlet" , Comps[7].DisplayText); |
205 | EXPECT_EQ("file " , Comps[8].TypedText); |
206 | EXPECT_EQ("file" , Comps[8].DisplayText); |
207 | |
208 | Comps = QueryParser::complete(Line: "set o" , Pos: 5, QS); |
209 | ASSERT_EQ(1u, Comps.size()); |
210 | EXPECT_EQ("utput " , Comps[0].TypedText); |
211 | EXPECT_EQ("output" , Comps[0].DisplayText); |
212 | |
213 | Comps = QueryParser::complete(Line: "set t" , Pos: 5, QS); |
214 | ASSERT_EQ(1u, Comps.size()); |
215 | EXPECT_EQ("raversal " , Comps[0].TypedText); |
216 | EXPECT_EQ("traversal" , Comps[0].DisplayText); |
217 | |
218 | Comps = QueryParser::complete(Line: "enable " , Pos: 7, QS); |
219 | ASSERT_EQ(1u, Comps.size()); |
220 | EXPECT_EQ("output " , Comps[0].TypedText); |
221 | EXPECT_EQ("output" , Comps[0].DisplayText); |
222 | |
223 | Comps = QueryParser::complete(Line: "enable output " , Pos: 14, QS); |
224 | ASSERT_EQ(4u, Comps.size()); |
225 | |
226 | EXPECT_EQ("diag " , Comps[0].TypedText); |
227 | EXPECT_EQ("diag" , Comps[0].DisplayText); |
228 | EXPECT_EQ("print " , Comps[1].TypedText); |
229 | EXPECT_EQ("print" , Comps[1].DisplayText); |
230 | EXPECT_EQ("detailed-ast " , Comps[2].TypedText); |
231 | EXPECT_EQ("detailed-ast" , Comps[2].DisplayText); |
232 | EXPECT_EQ("dump " , Comps[3].TypedText); |
233 | EXPECT_EQ("dump" , Comps[3].DisplayText); |
234 | |
235 | Comps = QueryParser::complete(Line: "set traversal " , Pos: 14, QS); |
236 | ASSERT_EQ(2u, Comps.size()); |
237 | |
238 | EXPECT_EQ("AsIs " , Comps[0].TypedText); |
239 | EXPECT_EQ("AsIs" , Comps[0].DisplayText); |
240 | EXPECT_EQ("IgnoreUnlessSpelledInSource " , Comps[1].TypedText); |
241 | EXPECT_EQ("IgnoreUnlessSpelledInSource" , Comps[1].DisplayText); |
242 | |
243 | Comps = QueryParser::complete(Line: "match while" , Pos: 11, QS); |
244 | ASSERT_EQ(1u, Comps.size()); |
245 | EXPECT_EQ("Stmt(" , Comps[0].TypedText); |
246 | EXPECT_EQ("Matcher<Stmt> whileStmt(Matcher<WhileStmt>...)" , |
247 | Comps[0].DisplayText); |
248 | |
249 | Comps = QueryParser::complete(Line: "m" , Pos: 1, QS); |
250 | ASSERT_EQ(1u, Comps.size()); |
251 | EXPECT_EQ("atch " , Comps[0].TypedText); |
252 | EXPECT_EQ("match" , Comps[0].DisplayText); |
253 | |
254 | Comps = QueryParser::complete(Line: "l" , Pos: 1, QS); |
255 | ASSERT_EQ(1u, Comps.size()); |
256 | EXPECT_EQ("et " , Comps[0].TypedText); |
257 | EXPECT_EQ("let" , Comps[0].DisplayText); |
258 | } |
259 | |
260 | TEST_F(QueryParserTest, Multiline) { |
261 | |
262 | // Single string with multiple commands |
263 | QueryRef Q = parse(Code: R"matcher( |
264 | set bind-root false |
265 | set output dump |
266 | )matcher" ); |
267 | |
268 | ASSERT_TRUE(isa<SetQuery<bool>>(Q)); |
269 | |
270 | Q = parse(Code: Q->RemainingContent); |
271 | ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q)); |
272 | |
273 | // Missing newline |
274 | Q = parse(Code: R"matcher( |
275 | set bind-root false set output dump |
276 | )matcher" ); |
277 | |
278 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
279 | EXPECT_EQ("unexpected extra input: ' set output dump\n '" , |
280 | cast<InvalidQuery>(Q)->ErrStr); |
281 | |
282 | // Commands which do their own parsing |
283 | Q = parse(Code: R"matcher( |
284 | let fn functionDecl(hasName("foo")) |
285 | match callExpr(callee(functionDecl())) |
286 | )matcher" ); |
287 | |
288 | ASSERT_TRUE(isa<LetQuery>(Q)); |
289 | |
290 | Q = parse(Code: Q->RemainingContent); |
291 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
292 | |
293 | // Multi-line matcher |
294 | Q = parse(Code: R"matcher( |
295 | match callExpr(callee( |
296 | functionDecl().bind("fn") |
297 | )) |
298 | |
299 | )matcher" ); |
300 | |
301 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
302 | |
303 | // Comment locations |
304 | Q = parse(Code: R"matcher( |
305 | #nospacecomment |
306 | # Leading comment |
307 | match callExpr ( # Trailing comment |
308 | # Comment alone on line |
309 | |
310 | callee( |
311 | functionDecl( |
312 | ).bind( |
313 | "fn" |
314 | ) |
315 | )) # Comment trailing close |
316 | # Comment after match |
317 | )matcher" ); |
318 | |
319 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
320 | |
321 | // \r\n |
322 | Q = parse(Code: "set bind-root false\r\nset output dump" ); |
323 | |
324 | ASSERT_TRUE(isa<SetQuery<bool>>(Q)); |
325 | |
326 | Q = parse(Code: Q->RemainingContent); |
327 | ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q)); |
328 | |
329 | // Leading and trailing space in lines |
330 | Q = parse(Code: " set bind-root false \r\n set output dump " ); |
331 | |
332 | ASSERT_TRUE(isa<SetQuery<bool>>(Q)); |
333 | |
334 | Q = parse(Code: Q->RemainingContent); |
335 | ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q)); |
336 | |
337 | // Incomplete commands |
338 | Q = parse(Code: "set\nbind-root false" ); |
339 | |
340 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
341 | EXPECT_EQ("expected variable name" , cast<InvalidQuery>(Q)->ErrStr); |
342 | |
343 | Q = parse(Code: "set bind-root\nfalse" ); |
344 | |
345 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
346 | EXPECT_EQ("expected 'true' or 'false', got ''" , |
347 | cast<InvalidQuery>(Q)->ErrStr); |
348 | |
349 | Q = parse(Code: R"matcher( |
350 | match callExpr |
351 | ( |
352 | ) |
353 | )matcher" ); |
354 | |
355 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
356 | EXPECT_EQ("1:9: Error parsing matcher. Found token <NewLine> " |
357 | "while looking for '('." , |
358 | cast<InvalidQuery>(Q)->ErrStr); |
359 | |
360 | Q = parse(Code: "let someMatcher\nm parmVarDecl()" ); |
361 | |
362 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
363 | EXPECT_EQ("1:1: Invalid token <NewLine> found when looking for a value." , |
364 | cast<InvalidQuery>(Q)->ErrStr); |
365 | |
366 | Q = parse(Code: "\nm parmVarDecl()\nlet someMatcher\nm parmVarDecl()" ); |
367 | |
368 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
369 | Q = parse(Code: Q->RemainingContent); |
370 | |
371 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
372 | EXPECT_EQ("1:1: Invalid token <NewLine> found when looking for a value." , |
373 | cast<InvalidQuery>(Q)->ErrStr); |
374 | |
375 | Q = parse(Code: "\nlet someMatcher\n" ); |
376 | |
377 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
378 | EXPECT_EQ("1:1: Invalid token <NewLine> found when looking for a value." , |
379 | cast<InvalidQuery>(Q)->ErrStr); |
380 | |
381 | Q = parse(Code: "\nm parmVarDecl()\nlet someMatcher\n" ); |
382 | |
383 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
384 | Q = parse(Code: Q->RemainingContent); |
385 | |
386 | ASSERT_TRUE(isa<InvalidQuery>(Q)); |
387 | EXPECT_EQ("1:1: Invalid token <NewLine> found when looking for a value." , |
388 | cast<InvalidQuery>(Q)->ErrStr); |
389 | |
390 | Q = parse(Code: R"matcher( |
391 | |
392 | let Construct parmVarDecl() |
393 | |
394 | m parmVarDecl( |
395 | Construct |
396 | ) |
397 | )matcher" ); |
398 | |
399 | ASSERT_TRUE(isa<LetQuery>(Q)); |
400 | { |
401 | llvm::raw_null_ostream NullOutStream; |
402 | dyn_cast<LetQuery>(Val&: Q)->run(OS&: NullOutStream, QS); |
403 | } |
404 | |
405 | Q = parse(Code: Q->RemainingContent); |
406 | |
407 | ASSERT_TRUE(isa<MatchQuery>(Q)); |
408 | } |
409 | |