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