1 | //===- unittest/Tooling/ASTSelectionTest.cpp ------------------------------===// |
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 "TestVisitor.h" |
10 | #include "clang/Basic/SourceManager.h" |
11 | #include "clang/Tooling/Refactoring/ASTSelection.h" |
12 | #include <optional> |
13 | |
14 | using namespace clang; |
15 | using namespace tooling; |
16 | |
17 | namespace { |
18 | |
19 | struct FileLocation { |
20 | unsigned Line, Column; |
21 | |
22 | SourceLocation translate(const SourceManager &SM) { |
23 | return SM.translateLineCol(FID: SM.getMainFileID(), Line, Col: Column); |
24 | } |
25 | }; |
26 | |
27 | using FileRange = std::pair<FileLocation, FileLocation>; |
28 | |
29 | class SelectionFinderVisitor : public TestVisitor<SelectionFinderVisitor> { |
30 | FileLocation Location; |
31 | std::optional<FileRange> SelectionRange; |
32 | llvm::function_ref<void(SourceRange SelectionRange, |
33 | std::optional<SelectedASTNode>)> |
34 | Consumer; |
35 | |
36 | public: |
37 | SelectionFinderVisitor( |
38 | FileLocation Location, std::optional<FileRange> SelectionRange, |
39 | llvm::function_ref<void(SourceRange SelectionRange, |
40 | std::optional<SelectedASTNode>)> |
41 | Consumer) |
42 | : Location(Location), SelectionRange(SelectionRange), Consumer(Consumer) { |
43 | } |
44 | |
45 | bool VisitTranslationUnitDecl(const TranslationUnitDecl *TU) { |
46 | const ASTContext &Context = TU->getASTContext(); |
47 | const SourceManager &SM = Context.getSourceManager(); |
48 | |
49 | SourceRange SelRange; |
50 | if (SelectionRange) { |
51 | SelRange = SourceRange(SelectionRange->first.translate(SM), |
52 | SelectionRange->second.translate(SM)); |
53 | } else { |
54 | SourceLocation Loc = Location.translate(SM); |
55 | SelRange = SourceRange(Loc, Loc); |
56 | } |
57 | Consumer(SelRange, findSelectedASTNodes(Context, SelRange)); |
58 | return false; |
59 | } |
60 | }; |
61 | |
62 | /// This is a test utility function that computes the AST selection at the |
63 | /// given location with an optional selection range. |
64 | /// |
65 | /// A location roughly corresponds to a cursor location in an editor, while |
66 | /// the optional range corresponds to the selection range in an editor. |
67 | void findSelectedASTNodesWithRange( |
68 | StringRef Source, FileLocation Location, |
69 | std::optional<FileRange> SelectionRange, |
70 | llvm::function_ref<void(SourceRange SelectionRange, |
71 | std::optional<SelectedASTNode>)> |
72 | Consumer, |
73 | SelectionFinderVisitor::Language Language = |
74 | SelectionFinderVisitor::Lang_CXX11) { |
75 | SelectionFinderVisitor Visitor(Location, SelectionRange, Consumer); |
76 | EXPECT_TRUE(Visitor.runOver(Source, Language)); |
77 | } |
78 | |
79 | void findSelectedASTNodes( |
80 | StringRef Source, FileLocation Location, |
81 | std::optional<FileRange> SelectionRange, |
82 | llvm::function_ref<void(std::optional<SelectedASTNode>)> Consumer, |
83 | SelectionFinderVisitor::Language Language = |
84 | SelectionFinderVisitor::Lang_CXX11) { |
85 | findSelectedASTNodesWithRange( |
86 | Source, Location, SelectionRange, |
87 | Consumer: [&](SourceRange, std::optional<SelectedASTNode> Selection) { |
88 | Consumer(std::move(Selection)); |
89 | }, |
90 | Language); |
91 | } |
92 | |
93 | void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node, |
94 | SourceSelectionKind SelectionKind, unsigned NumChildren) { |
95 | ASSERT_TRUE(IsTypeMatched); |
96 | EXPECT_EQ(Node.Children.size(), NumChildren); |
97 | ASSERT_EQ(Node.SelectionKind, SelectionKind); |
98 | } |
99 | |
100 | void checkDeclName(const SelectedASTNode &Node, StringRef Name) { |
101 | const auto *ND = Node.Node.get<NamedDecl>(); |
102 | EXPECT_TRUE(!!ND); |
103 | ASSERT_EQ(ND->getName(), Name); |
104 | } |
105 | |
106 | template <typename T> |
107 | const SelectedASTNode & |
108 | checkNode(const SelectedASTNode &StmtNode, SourceSelectionKind SelectionKind, |
109 | unsigned NumChildren = 0, |
110 | std::enable_if_t<std::is_base_of_v<Stmt, T>, T> *StmtOverloadChecker = |
111 | nullptr) { |
112 | checkNodeImpl(isa<T>(StmtNode.Node.get<Stmt>()), StmtNode, SelectionKind, |
113 | NumChildren); |
114 | return StmtNode; |
115 | } |
116 | |
117 | template <typename T> |
118 | const SelectedASTNode & |
119 | checkNode(const SelectedASTNode &DeclNode, SourceSelectionKind SelectionKind, |
120 | unsigned NumChildren = 0, StringRef Name = "" , |
121 | std::enable_if_t<std::is_base_of_v<Decl, T>, T> *DeclOverloadChecker = |
122 | nullptr) { |
123 | checkNodeImpl(isa<T>(DeclNode.Node.get<Decl>()), DeclNode, SelectionKind, |
124 | NumChildren); |
125 | if (!Name.empty()) |
126 | checkDeclName(Node: DeclNode, Name); |
127 | return DeclNode; |
128 | } |
129 | |
130 | struct ForAllChildrenOf { |
131 | const SelectedASTNode &Node; |
132 | |
133 | static void childKindVerifier(const SelectedASTNode &Node, |
134 | SourceSelectionKind SelectionKind) { |
135 | for (const SelectedASTNode &Child : Node.Children) { |
136 | ASSERT_EQ(Node.SelectionKind, SelectionKind); |
137 | childKindVerifier(Node: Child, SelectionKind); |
138 | } |
139 | } |
140 | |
141 | public: |
142 | ForAllChildrenOf(const SelectedASTNode &Node) : Node(Node) {} |
143 | |
144 | void shouldHaveSelectionKind(SourceSelectionKind Kind) { |
145 | childKindVerifier(Node, SelectionKind: Kind); |
146 | } |
147 | }; |
148 | |
149 | ForAllChildrenOf allChildrenOf(const SelectedASTNode &Node) { |
150 | return ForAllChildrenOf(Node); |
151 | } |
152 | |
153 | TEST(ASTSelectionFinder, CursorNoSelection) { |
154 | findSelectedASTNodes( |
155 | Source: " void f() { }" , Location: {.Line: 1, .Column: 1}, SelectionRange: std::nullopt, |
156 | Consumer: [](std::optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
157 | } |
158 | |
159 | TEST(ASTSelectionFinder, CursorAtStartOfFunction) { |
160 | findSelectedASTNodes( |
161 | Source: "void f() { }" , Location: {.Line: 1, .Column: 1}, SelectionRange: std::nullopt, |
162 | Consumer: [](std::optional<SelectedASTNode> Node) { |
163 | EXPECT_TRUE(Node); |
164 | checkNode<TranslationUnitDecl>(DeclNode: *Node, SelectionKind: SourceSelectionKind::None, |
165 | /*NumChildren=*/1); |
166 | checkNode<FunctionDecl>(DeclNode: Node->Children[0], |
167 | SelectionKind: SourceSelectionKind::ContainsSelection, |
168 | /*NumChildren=*/0, /*Name=*/"f" ); |
169 | |
170 | // Check that the dumping works. |
171 | std::string DumpValue; |
172 | llvm::raw_string_ostream OS(DumpValue); |
173 | Node->Children[0].dump(OS); |
174 | ASSERT_EQ(OS.str(), "FunctionDecl \"f\" contains-selection\n" ); |
175 | }); |
176 | } |
177 | |
178 | TEST(ASTSelectionFinder, RangeNoSelection) { |
179 | findSelectedASTNodes( |
180 | Source: " void f() { }" , Location: {.Line: 1, .Column: 1}, SelectionRange: FileRange{{.Line: 1, .Column: 1}, {.Line: 1, .Column: 1}}, |
181 | Consumer: [](std::optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
182 | findSelectedASTNodes( |
183 | Source: " void f() { }" , Location: {.Line: 1, .Column: 1}, SelectionRange: FileRange{{.Line: 1, .Column: 1}, {.Line: 1, .Column: 2}}, |
184 | Consumer: [](std::optional<SelectedASTNode> Node) { EXPECT_FALSE(Node); }); |
185 | } |
186 | |
187 | TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) { |
188 | findSelectedASTNodes(Source: "void f() { }" , Location: {.Line: 1, .Column: 1}, SelectionRange: FileRange{{.Line: 1, .Column: 1}, {.Line: 1, .Column: 1}}, |
189 | Consumer: [](std::optional<SelectedASTNode> Node) { |
190 | EXPECT_TRUE(Node); |
191 | checkNode<FunctionDecl>( |
192 | DeclNode: Node->Children[0], |
193 | SelectionKind: SourceSelectionKind::ContainsSelection, |
194 | /*NumChildren=*/0, /*Name=*/"f" ); |
195 | }); |
196 | } |
197 | |
198 | TEST(ASTSelectionFinder, WholeFunctionSelection) { |
199 | StringRef Source = "int f(int x) { return x;\n}\nvoid f2() { }" ; |
200 | // From 'int' until just after '}': |
201 | |
202 | findSelectedASTNodes( |
203 | Source, Location: {.Line: 1, .Column: 1}, SelectionRange: FileRange{{.Line: 1, .Column: 1}, {.Line: 2, .Column: 2}}, |
204 | Consumer: [](std::optional<SelectedASTNode> Node) { |
205 | EXPECT_TRUE(Node); |
206 | EXPECT_EQ(Node->Children.size(), 1u); |
207 | const auto &Fn = checkNode<FunctionDecl>( |
208 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
209 | /*NumChildren=*/2, /*Name=*/"f" ); |
210 | checkNode<ParmVarDecl>(DeclNode: Fn.Children[0], |
211 | SelectionKind: SourceSelectionKind::InsideSelection); |
212 | const auto &Body = checkNode<CompoundStmt>( |
213 | StmtNode: Fn.Children[1], SelectionKind: SourceSelectionKind::InsideSelection, |
214 | /*NumChildren=*/1); |
215 | const auto &Return = checkNode<ReturnStmt>( |
216 | StmtNode: Body.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
217 | /*NumChildren=*/1); |
218 | checkNode<ImplicitCastExpr>(StmtNode: Return.Children[0], |
219 | SelectionKind: SourceSelectionKind::InsideSelection, |
220 | /*NumChildren=*/1); |
221 | checkNode<DeclRefExpr>(StmtNode: Return.Children[0].Children[0], |
222 | SelectionKind: SourceSelectionKind::InsideSelection); |
223 | }); |
224 | |
225 | // From 'int' until just before '}': |
226 | findSelectedASTNodes( |
227 | Source, Location: {.Line: 2, .Column: 1}, SelectionRange: FileRange{{.Line: 1, .Column: 1}, {.Line: 2, .Column: 1}}, |
228 | Consumer: [](std::optional<SelectedASTNode> Node) { |
229 | EXPECT_TRUE(Node); |
230 | EXPECT_EQ(Node->Children.size(), 1u); |
231 | const auto &Fn = checkNode<FunctionDecl>( |
232 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
233 | /*NumChildren=*/2, /*Name=*/"f" ); |
234 | const auto &Body = checkNode<CompoundStmt>( |
235 | StmtNode: Fn.Children[1], SelectionKind: SourceSelectionKind::ContainsSelectionEnd, |
236 | /*NumChildren=*/1); |
237 | checkNode<ReturnStmt>(StmtNode: Body.Children[0], |
238 | SelectionKind: SourceSelectionKind::InsideSelection, |
239 | /*NumChildren=*/1); |
240 | }); |
241 | // From '{' until just after '}': |
242 | findSelectedASTNodes( |
243 | Source, Location: {.Line: 1, .Column: 14}, SelectionRange: FileRange{{.Line: 1, .Column: 14}, {.Line: 2, .Column: 2}}, |
244 | Consumer: [](std::optional<SelectedASTNode> Node) { |
245 | EXPECT_TRUE(Node); |
246 | EXPECT_EQ(Node->Children.size(), 1u); |
247 | const auto &Fn = checkNode<FunctionDecl>( |
248 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
249 | /*NumChildren=*/1, /*Name=*/"f" ); |
250 | const auto &Body = checkNode<CompoundStmt>( |
251 | StmtNode: Fn.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
252 | /*NumChildren=*/1); |
253 | checkNode<ReturnStmt>(StmtNode: Body.Children[0], |
254 | SelectionKind: SourceSelectionKind::InsideSelection, |
255 | /*NumChildren=*/1); |
256 | }); |
257 | // From 'x' until just after '}': |
258 | findSelectedASTNodes( |
259 | Source, Location: {.Line: 2, .Column: 2}, SelectionRange: FileRange{{.Line: 1, .Column: 11}, {.Line: 2, .Column: 2}}, |
260 | Consumer: [](std::optional<SelectedASTNode> Node) { |
261 | EXPECT_TRUE(Node); |
262 | EXPECT_EQ(Node->Children.size(), 1u); |
263 | const auto &Fn = checkNode<FunctionDecl>( |
264 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
265 | /*NumChildren=*/2, /*Name=*/"f" ); |
266 | checkNode<ParmVarDecl>(DeclNode: Fn.Children[0], |
267 | SelectionKind: SourceSelectionKind::ContainsSelectionStart); |
268 | const auto &Body = checkNode<CompoundStmt>( |
269 | StmtNode: Fn.Children[1], SelectionKind: SourceSelectionKind::InsideSelection, |
270 | /*NumChildren=*/1); |
271 | checkNode<ReturnStmt>(StmtNode: Body.Children[0], |
272 | SelectionKind: SourceSelectionKind::InsideSelection, |
273 | /*NumChildren=*/1); |
274 | }); |
275 | } |
276 | |
277 | TEST(ASTSelectionFinder, MultipleFunctionSelection) { |
278 | StringRef Source = R"(void f0() { |
279 | } |
280 | void f1() { } |
281 | void f2() { } |
282 | void f3() { } |
283 | )" ; |
284 | auto SelectedF1F2 = [](std::optional<SelectedASTNode> Node) { |
285 | EXPECT_TRUE(Node); |
286 | EXPECT_EQ(Node->Children.size(), 2u); |
287 | checkNode<FunctionDecl>(DeclNode: Node->Children[0], |
288 | SelectionKind: SourceSelectionKind::InsideSelection, |
289 | /*NumChildren=*/1, /*Name=*/"f1" ); |
290 | checkNode<FunctionDecl>(DeclNode: Node->Children[1], |
291 | SelectionKind: SourceSelectionKind::InsideSelection, |
292 | /*NumChildren=*/1, /*Name=*/"f2" ); |
293 | }; |
294 | // Just after '}' of f0 and just before 'void' of f3: |
295 | findSelectedASTNodes(Source, Location: {.Line: 2, .Column: 2}, SelectionRange: FileRange{{.Line: 2, .Column: 2}, {.Line: 5, .Column: 1}}, Consumer: SelectedF1F2); |
296 | // Just before 'void' of f1 and just after '}' of f2: |
297 | findSelectedASTNodes(Source, Location: {.Line: 3, .Column: 1}, SelectionRange: FileRange{{.Line: 3, .Column: 1}, {.Line: 4, .Column: 14}}, |
298 | Consumer: SelectedF1F2); |
299 | } |
300 | |
301 | TEST(ASTSelectionFinder, MultipleStatementSelection) { |
302 | StringRef Source = R"(void f(int x, int y) { |
303 | int z = x; |
304 | f(2, 3); |
305 | if (x == 0) { |
306 | return; |
307 | } |
308 | x = 1; |
309 | return; |
310 | })" ; |
311 | // From 'f(2,3)' until just before 'x = 1;': |
312 | findSelectedASTNodes( |
313 | Source, Location: {.Line: 3, .Column: 2}, SelectionRange: FileRange{{.Line: 3, .Column: 2}, {.Line: 7, .Column: 1}}, |
314 | Consumer: [](std::optional<SelectedASTNode> Node) { |
315 | EXPECT_TRUE(Node); |
316 | EXPECT_EQ(Node->Children.size(), 1u); |
317 | const auto &Fn = checkNode<FunctionDecl>( |
318 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
319 | /*NumChildren=*/1, /*Name=*/"f" ); |
320 | const auto &Body = checkNode<CompoundStmt>( |
321 | StmtNode: Fn.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
322 | /*NumChildren=*/2); |
323 | allChildrenOf(Node: checkNode<CallExpr>(StmtNode: Body.Children[0], |
324 | SelectionKind: SourceSelectionKind::InsideSelection, |
325 | /*NumChildren=*/3)) |
326 | .shouldHaveSelectionKind(Kind: SourceSelectionKind::InsideSelection); |
327 | allChildrenOf(Node: checkNode<IfStmt>(StmtNode: Body.Children[1], |
328 | SelectionKind: SourceSelectionKind::InsideSelection, |
329 | /*NumChildren=*/2)) |
330 | .shouldHaveSelectionKind(Kind: SourceSelectionKind::InsideSelection); |
331 | }); |
332 | // From 'f(2,3)' until just before ';' in 'x = 1;': |
333 | findSelectedASTNodes( |
334 | Source, Location: {.Line: 3, .Column: 2}, SelectionRange: FileRange{{.Line: 3, .Column: 2}, {.Line: 7, .Column: 8}}, |
335 | Consumer: [](std::optional<SelectedASTNode> Node) { |
336 | EXPECT_TRUE(Node); |
337 | EXPECT_EQ(Node->Children.size(), 1u); |
338 | const auto &Fn = checkNode<FunctionDecl>( |
339 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
340 | /*NumChildren=*/1, /*Name=*/"f" ); |
341 | const auto &Body = checkNode<CompoundStmt>( |
342 | StmtNode: Fn.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
343 | /*NumChildren=*/3); |
344 | checkNode<CallExpr>(StmtNode: Body.Children[0], |
345 | SelectionKind: SourceSelectionKind::InsideSelection, |
346 | /*NumChildren=*/3); |
347 | checkNode<IfStmt>(StmtNode: Body.Children[1], |
348 | SelectionKind: SourceSelectionKind::InsideSelection, |
349 | /*NumChildren=*/2); |
350 | checkNode<BinaryOperator>(StmtNode: Body.Children[2], |
351 | SelectionKind: SourceSelectionKind::InsideSelection, |
352 | /*NumChildren=*/2); |
353 | }); |
354 | // From the middle of 'int z = 3' until the middle of 'x = 1;': |
355 | findSelectedASTNodes( |
356 | Source, Location: {.Line: 2, .Column: 10}, SelectionRange: FileRange{{.Line: 2, .Column: 10}, {.Line: 7, .Column: 5}}, |
357 | Consumer: [](std::optional<SelectedASTNode> Node) { |
358 | EXPECT_TRUE(Node); |
359 | EXPECT_EQ(Node->Children.size(), 1u); |
360 | const auto &Fn = checkNode<FunctionDecl>( |
361 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
362 | /*NumChildren=*/1, /*Name=*/"f" ); |
363 | const auto &Body = checkNode<CompoundStmt>( |
364 | StmtNode: Fn.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
365 | /*NumChildren=*/4); |
366 | checkNode<DeclStmt>(StmtNode: Body.Children[0], |
367 | SelectionKind: SourceSelectionKind::ContainsSelectionStart, |
368 | /*NumChildren=*/1); |
369 | checkNode<CallExpr>(StmtNode: Body.Children[1], |
370 | SelectionKind: SourceSelectionKind::InsideSelection, |
371 | /*NumChildren=*/3); |
372 | checkNode<IfStmt>(StmtNode: Body.Children[2], |
373 | SelectionKind: SourceSelectionKind::InsideSelection, |
374 | /*NumChildren=*/2); |
375 | checkNode<BinaryOperator>(StmtNode: Body.Children[3], |
376 | SelectionKind: SourceSelectionKind::ContainsSelectionEnd, |
377 | /*NumChildren=*/1); |
378 | }); |
379 | } |
380 | |
381 | TEST(ASTSelectionFinder, SelectionInFunctionInObjCImplementation) { |
382 | StringRef Source = R"( |
383 | @interface I |
384 | @end |
385 | @implementation I |
386 | |
387 | int notSelected() { } |
388 | |
389 | int selected(int x) { |
390 | return x; |
391 | } |
392 | |
393 | @end |
394 | @implementation I(Cat) |
395 | |
396 | void catF() { } |
397 | |
398 | @end |
399 | |
400 | void outerFunction() { } |
401 | )" ; |
402 | // Just the 'x' expression in 'selected': |
403 | findSelectedASTNodes( |
404 | Source, Location: {.Line: 9, .Column: 10}, SelectionRange: FileRange{{.Line: 9, .Column: 10}, {.Line: 9, .Column: 11}}, |
405 | Consumer: [](std::optional<SelectedASTNode> Node) { |
406 | EXPECT_TRUE(Node); |
407 | EXPECT_EQ(Node->Children.size(), 1u); |
408 | const auto &Impl = checkNode<ObjCImplementationDecl>( |
409 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
410 | /*NumChildren=*/1, /*Name=*/"I" ); |
411 | const auto &Fn = checkNode<FunctionDecl>( |
412 | DeclNode: Impl.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
413 | /*NumChildren=*/1, /*Name=*/"selected" ); |
414 | allChildrenOf(Node: Fn).shouldHaveSelectionKind( |
415 | Kind: SourceSelectionKind::ContainsSelection); |
416 | }, |
417 | Language: SelectionFinderVisitor::Lang_OBJC); |
418 | // The entire 'catF': |
419 | findSelectedASTNodes( |
420 | Source, Location: {.Line: 15, .Column: 1}, SelectionRange: FileRange{{.Line: 15, .Column: 1}, {.Line: 15, .Column: 16}}, |
421 | Consumer: [](std::optional<SelectedASTNode> Node) { |
422 | EXPECT_TRUE(Node); |
423 | EXPECT_EQ(Node->Children.size(), 1u); |
424 | const auto &Impl = checkNode<ObjCCategoryImplDecl>( |
425 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
426 | /*NumChildren=*/1, /*Name=*/"Cat" ); |
427 | const auto &Fn = checkNode<FunctionDecl>( |
428 | DeclNode: Impl.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
429 | /*NumChildren=*/1, /*Name=*/"catF" ); |
430 | allChildrenOf(Node: Fn).shouldHaveSelectionKind( |
431 | Kind: SourceSelectionKind::ContainsSelection); |
432 | }, |
433 | Language: SelectionFinderVisitor::Lang_OBJC); |
434 | // From the line before 'selected' to the line after 'catF': |
435 | findSelectedASTNodes( |
436 | Source, Location: {.Line: 16, .Column: 1}, SelectionRange: FileRange{{.Line: 7, .Column: 1}, {.Line: 16, .Column: 1}}, |
437 | Consumer: [](std::optional<SelectedASTNode> Node) { |
438 | EXPECT_TRUE(Node); |
439 | EXPECT_EQ(Node->Children.size(), 2u); |
440 | const auto &Impl = checkNode<ObjCImplementationDecl>( |
441 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelectionStart, |
442 | /*NumChildren=*/1, /*Name=*/"I" ); |
443 | const auto &Selected = checkNode<FunctionDecl>( |
444 | DeclNode: Impl.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
445 | /*NumChildren=*/2, /*Name=*/"selected" ); |
446 | allChildrenOf(Node: Selected).shouldHaveSelectionKind( |
447 | Kind: SourceSelectionKind::InsideSelection); |
448 | const auto &Cat = checkNode<ObjCCategoryImplDecl>( |
449 | DeclNode: Node->Children[1], SelectionKind: SourceSelectionKind::ContainsSelectionEnd, |
450 | /*NumChildren=*/1, /*Name=*/"Cat" ); |
451 | const auto &CatF = checkNode<FunctionDecl>( |
452 | DeclNode: Cat.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
453 | /*NumChildren=*/1, /*Name=*/"catF" ); |
454 | allChildrenOf(Node: CatF).shouldHaveSelectionKind( |
455 | Kind: SourceSelectionKind::InsideSelection); |
456 | }, |
457 | Language: SelectionFinderVisitor::Lang_OBJC); |
458 | // Just the 'outer' function: |
459 | findSelectedASTNodes( |
460 | Source, Location: {.Line: 19, .Column: 1}, SelectionRange: FileRange{{.Line: 19, .Column: 1}, {.Line: 19, .Column: 25}}, |
461 | Consumer: [](std::optional<SelectedASTNode> Node) { |
462 | EXPECT_TRUE(Node); |
463 | EXPECT_EQ(Node->Children.size(), 1u); |
464 | checkNode<FunctionDecl>(DeclNode: Node->Children[0], |
465 | SelectionKind: SourceSelectionKind::ContainsSelection, |
466 | /*NumChildren=*/1, /*Name=*/"outerFunction" ); |
467 | }, |
468 | Language: SelectionFinderVisitor::Lang_OBJC); |
469 | } |
470 | |
471 | TEST(ASTSelectionFinder, FunctionInObjCImplementationCarefulWithEarlyExit) { |
472 | StringRef Source = R"( |
473 | @interface I |
474 | @end |
475 | @implementation I |
476 | |
477 | void selected() { |
478 | } |
479 | |
480 | - (void) method { } |
481 | |
482 | @end |
483 | )" ; |
484 | // Just 'selected' |
485 | findSelectedASTNodes( |
486 | Source, Location: {.Line: 6, .Column: 1}, SelectionRange: FileRange{{.Line: 6, .Column: 1}, {.Line: 7, .Column: 2}}, |
487 | Consumer: [](std::optional<SelectedASTNode> Node) { |
488 | EXPECT_TRUE(Node); |
489 | EXPECT_EQ(Node->Children.size(), 1u); |
490 | const auto &Impl = checkNode<ObjCImplementationDecl>( |
491 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
492 | /*NumChildren=*/1, /*Name=*/"I" ); |
493 | checkNode<FunctionDecl>(DeclNode: Impl.Children[0], |
494 | SelectionKind: SourceSelectionKind::ContainsSelection, |
495 | /*NumChildren=*/1, /*Name=*/"selected" ); |
496 | }, |
497 | Language: SelectionFinderVisitor::Lang_OBJC); |
498 | } |
499 | |
500 | TEST(ASTSelectionFinder, AvoidImplicitDeclarations) { |
501 | StringRef Source = R"( |
502 | struct Copy { |
503 | int x; |
504 | }; |
505 | void foo() { |
506 | Copy x; |
507 | Copy y = x; |
508 | } |
509 | )" ; |
510 | // The entire struct 'Copy': |
511 | findSelectedASTNodes( |
512 | Source, Location: {.Line: 2, .Column: 1}, SelectionRange: FileRange{{.Line: 2, .Column: 1}, {.Line: 4, .Column: 3}}, |
513 | Consumer: [](std::optional<SelectedASTNode> Node) { |
514 | EXPECT_TRUE(Node); |
515 | EXPECT_EQ(Node->Children.size(), 1u); |
516 | const auto &Record = checkNode<CXXRecordDecl>( |
517 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
518 | /*NumChildren=*/1, /*Name=*/"Copy" ); |
519 | checkNode<FieldDecl>(DeclNode: Record.Children[0], |
520 | SelectionKind: SourceSelectionKind::InsideSelection); |
521 | }); |
522 | } |
523 | |
524 | TEST(ASTSelectionFinder, CorrectEndForObjectiveCImplementation) { |
525 | StringRef Source = R"( |
526 | @interface I |
527 | @end |
528 | @implementation I |
529 | @ end |
530 | )" ; |
531 | // Just after '@ end' |
532 | findSelectedASTNodes( |
533 | Source, Location: {.Line: 5, .Column: 6}, SelectionRange: std::nullopt, |
534 | Consumer: [](std::optional<SelectedASTNode> Node) { |
535 | EXPECT_TRUE(Node); |
536 | EXPECT_EQ(Node->Children.size(), 1u); |
537 | checkNode<ObjCImplementationDecl>( |
538 | DeclNode: Node->Children[0], SelectionKind: SourceSelectionKind::ContainsSelection); |
539 | }, |
540 | Language: SelectionFinderVisitor::Lang_OBJC); |
541 | } |
542 | |
543 | const SelectedASTNode &checkFnBody(const std::optional<SelectedASTNode> &Node, |
544 | StringRef Name) { |
545 | EXPECT_TRUE(Node); |
546 | EXPECT_EQ(Node->Children.size(), 1u); |
547 | const auto &Fn = checkNode<FunctionDecl>( |
548 | Node->Children[0], SourceSelectionKind::ContainsSelection, |
549 | /*NumChildren=*/1, Name); |
550 | return checkNode<CompoundStmt>(Fn.Children[0], |
551 | SourceSelectionKind::ContainsSelection, |
552 | /*NumChildren=*/1); |
553 | } |
554 | |
555 | TEST(ASTSelectionFinder, SelectObjectiveCPseudoObjectExprs) { |
556 | StringRef Source = R"( |
557 | @interface I |
558 | @property(readwrite) int prop; |
559 | @end |
560 | void selectProp(I *i) { |
561 | (void)i.prop; |
562 | i.prop = 21; |
563 | } |
564 | |
565 | |
566 | @interface NSMutableArray |
567 | - (id)objectAtIndexedSubscript:(unsigned int)index; |
568 | - (void)setObject:(id)object atIndexedSubscript:(unsigned int)index; |
569 | @end |
570 | |
571 | void selectSubscript(NSMutableArray *array, I *i) { |
572 | (void)array[10]; |
573 | array[i.prop] = i; |
574 | } |
575 | )" ; |
576 | // Just 'i.prop'. |
577 | findSelectedASTNodes( |
578 | Source, Location: {.Line: 6, .Column: 7}, SelectionRange: FileRange{{.Line: 6, .Column: 7}, {.Line: 6, .Column: 13}}, |
579 | Consumer: [](std::optional<SelectedASTNode> Node) { |
580 | const auto &CS = checkFnBody(Node, /*Name=*/"selectProp" ); |
581 | const auto &CCast = checkNode<CStyleCastExpr>( |
582 | StmtNode: CS.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
583 | /*NumChildren=*/1); |
584 | const auto &POE = checkNode<PseudoObjectExpr>( |
585 | StmtNode: CCast.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
586 | /*NumChildren=*/1); |
587 | const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
588 | StmtNode: POE.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
589 | /*NumChildren=*/1); |
590 | const auto &Cast = checkNode<ImplicitCastExpr>( |
591 | StmtNode: PRE.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
592 | /*NumChildren=*/1); |
593 | checkNode<DeclRefExpr>(StmtNode: Cast.Children[0], |
594 | SelectionKind: SourceSelectionKind::InsideSelection); |
595 | }, |
596 | Language: SelectionFinderVisitor::Lang_OBJC); |
597 | // Just 'i.prop = 21' |
598 | findSelectedASTNodes( |
599 | Source, Location: {.Line: 7, .Column: 1}, SelectionRange: FileRange{{.Line: 7, .Column: 1}, {.Line: 7, .Column: 12}}, |
600 | Consumer: [](std::optional<SelectedASTNode> Node) { |
601 | const auto &CS = checkFnBody(Node, /*Name=*/"selectProp" ); |
602 | const auto &POE = checkNode<PseudoObjectExpr>( |
603 | StmtNode: CS.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
604 | /*NumChildren=*/1); |
605 | const auto &BinOp = checkNode<BinaryOperator>( |
606 | StmtNode: POE.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
607 | /*NumChildren=*/2); |
608 | const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
609 | StmtNode: BinOp.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
610 | /*NumChildren=*/1); |
611 | const auto &Cast = checkNode<ImplicitCastExpr>( |
612 | StmtNode: PRE.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
613 | /*NumChildren=*/1); |
614 | checkNode<DeclRefExpr>(StmtNode: Cast.Children[0], |
615 | SelectionKind: SourceSelectionKind::InsideSelection); |
616 | checkNode<IntegerLiteral>(StmtNode: BinOp.Children[1], |
617 | SelectionKind: SourceSelectionKind::InsideSelection); |
618 | }, |
619 | Language: SelectionFinderVisitor::Lang_OBJC); |
620 | // Just 'array[10]' |
621 | findSelectedASTNodes( |
622 | Source, Location: {.Line: 17, .Column: 9}, SelectionRange: FileRange{{.Line: 17, .Column: 9}, {.Line: 17, .Column: 18}}, |
623 | Consumer: [](std::optional<SelectedASTNode> Node) { |
624 | const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript" ); |
625 | const auto &CCast = checkNode<CStyleCastExpr>( |
626 | StmtNode: CS.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
627 | /*NumChildren=*/1); |
628 | const auto &POE = checkNode<PseudoObjectExpr>( |
629 | StmtNode: CCast.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
630 | /*NumChildren=*/1); |
631 | const auto &SRE = checkNode<ObjCSubscriptRefExpr>( |
632 | StmtNode: POE.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
633 | /*NumChildren=*/2); |
634 | const auto &Cast = checkNode<ImplicitCastExpr>( |
635 | StmtNode: SRE.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
636 | /*NumChildren=*/1); |
637 | checkNode<DeclRefExpr>(StmtNode: Cast.Children[0], |
638 | SelectionKind: SourceSelectionKind::InsideSelection); |
639 | checkNode<IntegerLiteral>(StmtNode: SRE.Children[1], |
640 | SelectionKind: SourceSelectionKind::InsideSelection); |
641 | }, |
642 | Language: SelectionFinderVisitor::Lang_OBJC); |
643 | // Just 'array[i.prop] = array' |
644 | findSelectedASTNodes( |
645 | Source, Location: {.Line: 18, .Column: 3}, SelectionRange: FileRange{{.Line: 18, .Column: 3}, {.Line: 18, .Column: 20}}, |
646 | Consumer: [](std::optional<SelectedASTNode> Node) { |
647 | const auto &CS = checkFnBody(Node, /*Name=*/"selectSubscript" ); |
648 | const auto &POE = checkNode<PseudoObjectExpr>( |
649 | StmtNode: CS.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
650 | /*NumChildren=*/1); |
651 | const auto &BinOp = checkNode<BinaryOperator>( |
652 | StmtNode: POE.Children[0], SelectionKind: SourceSelectionKind::ContainsSelection, |
653 | /*NumChildren=*/2); |
654 | const auto &SRE = checkNode<ObjCSubscriptRefExpr>( |
655 | StmtNode: BinOp.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
656 | /*NumChildren=*/2); |
657 | const auto &Cast = checkNode<ImplicitCastExpr>( |
658 | StmtNode: SRE.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
659 | /*NumChildren=*/1); |
660 | checkNode<DeclRefExpr>(StmtNode: Cast.Children[0], |
661 | SelectionKind: SourceSelectionKind::InsideSelection); |
662 | const auto &POE2 = checkNode<PseudoObjectExpr>( |
663 | StmtNode: SRE.Children[1], SelectionKind: SourceSelectionKind::InsideSelection, |
664 | /*NumChildren=*/1); |
665 | const auto &PRE = checkNode<ObjCPropertyRefExpr>( |
666 | StmtNode: POE2.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
667 | /*NumChildren=*/1); |
668 | const auto &Cast2 = checkNode<ImplicitCastExpr>( |
669 | StmtNode: PRE.Children[0], SelectionKind: SourceSelectionKind::InsideSelection, |
670 | /*NumChildren=*/1); |
671 | checkNode<DeclRefExpr>(StmtNode: Cast2.Children[0], |
672 | SelectionKind: SourceSelectionKind::InsideSelection); |
673 | checkNode<DeclRefExpr>(StmtNode: BinOp.Children[1], |
674 | SelectionKind: SourceSelectionKind::InsideSelection); |
675 | }, |
676 | Language: SelectionFinderVisitor::Lang_OBJC); |
677 | } |
678 | |
679 | TEST(ASTSelectionFinder, SimpleCodeRangeASTSelection) { |
680 | StringRef Source = R"(void f(int x, int y) { |
681 | int z = x; |
682 | f(2, 3); |
683 | if (x == 0) { |
684 | return; |
685 | } |
686 | x = 1; |
687 | return; |
688 | } |
689 | void f2() { |
690 | int m = 0; |
691 | } |
692 | )" ; |
693 | // No selection range. |
694 | findSelectedASTNodesWithRange( |
695 | Source, Location: {.Line: 2, .Column: 2}, SelectionRange: std::nullopt, |
696 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
697 | EXPECT_TRUE(Node); |
698 | std::optional<CodeRangeASTSelection> SelectedCode = |
699 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
700 | EXPECT_FALSE(SelectedCode); |
701 | }); |
702 | findSelectedASTNodesWithRange( |
703 | Source, Location: {.Line: 2, .Column: 2}, SelectionRange: FileRange{{.Line: 2, .Column: 2}, {.Line: 2, .Column: 2}}, |
704 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
705 | EXPECT_TRUE(Node); |
706 | std::optional<CodeRangeASTSelection> SelectedCode = |
707 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
708 | EXPECT_FALSE(SelectedCode); |
709 | }); |
710 | // Range that spans multiple functions is an invalid code range. |
711 | findSelectedASTNodesWithRange( |
712 | Source, Location: {.Line: 2, .Column: 2}, SelectionRange: FileRange{{.Line: 7, .Column: 2}, {.Line: 12, .Column: 1}}, |
713 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
714 | EXPECT_TRUE(Node); |
715 | std::optional<CodeRangeASTSelection> SelectedCode = |
716 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
717 | EXPECT_FALSE(SelectedCode); |
718 | }); |
719 | // Just 'z = x;': |
720 | findSelectedASTNodesWithRange( |
721 | Source, Location: {.Line: 2, .Column: 2}, SelectionRange: FileRange{{.Line: 2, .Column: 2}, {.Line: 2, .Column: 13}}, |
722 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
723 | EXPECT_TRUE(Node); |
724 | std::optional<CodeRangeASTSelection> SelectedCode = |
725 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
726 | EXPECT_TRUE(SelectedCode); |
727 | EXPECT_EQ(SelectedCode->size(), 1u); |
728 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
729 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
730 | SelectedCode->getParents(); |
731 | EXPECT_EQ(Parents.size(), 3u); |
732 | EXPECT_TRUE( |
733 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
734 | // Function 'f' definition. |
735 | EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
736 | // Function body of function 'F'. |
737 | EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
738 | }); |
739 | // From 'f(2,3)' until just before 'x = 1;': |
740 | findSelectedASTNodesWithRange( |
741 | Source, Location: {.Line: 3, .Column: 2}, SelectionRange: FileRange{{.Line: 3, .Column: 2}, {.Line: 7, .Column: 1}}, |
742 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
743 | EXPECT_TRUE(Node); |
744 | std::optional<CodeRangeASTSelection> SelectedCode = |
745 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
746 | EXPECT_TRUE(SelectedCode); |
747 | EXPECT_EQ(SelectedCode->size(), 2u); |
748 | EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
749 | EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
750 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
751 | SelectedCode->getParents(); |
752 | EXPECT_EQ(Parents.size(), 3u); |
753 | EXPECT_TRUE( |
754 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
755 | // Function 'f' definition. |
756 | EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
757 | // Function body of function 'F'. |
758 | EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
759 | }); |
760 | // From 'f(2,3)' until just before ';' in 'x = 1;': |
761 | findSelectedASTNodesWithRange( |
762 | Source, Location: {.Line: 3, .Column: 2}, SelectionRange: FileRange{{.Line: 3, .Column: 2}, {.Line: 7, .Column: 8}}, |
763 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
764 | EXPECT_TRUE(Node); |
765 | std::optional<CodeRangeASTSelection> SelectedCode = |
766 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
767 | EXPECT_TRUE(SelectedCode); |
768 | EXPECT_EQ(SelectedCode->size(), 3u); |
769 | EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
770 | EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
771 | EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[2])); |
772 | }); |
773 | // From the middle of 'int z = 3' until the middle of 'x = 1;': |
774 | findSelectedASTNodesWithRange( |
775 | Source, Location: {.Line: 2, .Column: 10}, SelectionRange: FileRange{{.Line: 2, .Column: 10}, {.Line: 7, .Column: 5}}, |
776 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
777 | EXPECT_TRUE(Node); |
778 | EXPECT_TRUE(Node); |
779 | std::optional<CodeRangeASTSelection> SelectedCode = |
780 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
781 | EXPECT_TRUE(SelectedCode); |
782 | EXPECT_EQ(SelectedCode->size(), 4u); |
783 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
784 | EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[1])); |
785 | EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[2])); |
786 | EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[3])); |
787 | }); |
788 | } |
789 | |
790 | TEST(ASTSelectionFinder, OutOfBodyCodeRange) { |
791 | StringRef Source = R"( |
792 | int codeRange = 2 + 3; |
793 | )" ; |
794 | // '2+3' expression. |
795 | findSelectedASTNodesWithRange( |
796 | Source, Location: {.Line: 2, .Column: 17}, SelectionRange: FileRange{{.Line: 2, .Column: 17}, {.Line: 2, .Column: 22}}, |
797 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
798 | EXPECT_TRUE(Node); |
799 | std::optional<CodeRangeASTSelection> SelectedCode = |
800 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
801 | EXPECT_TRUE(SelectedCode); |
802 | EXPECT_EQ(SelectedCode->size(), 1u); |
803 | EXPECT_TRUE(isa<BinaryOperator>((*SelectedCode)[0])); |
804 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
805 | SelectedCode->getParents(); |
806 | EXPECT_EQ(Parents.size(), 2u); |
807 | EXPECT_TRUE( |
808 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
809 | // Variable 'codeRange'. |
810 | EXPECT_TRUE(isa<VarDecl>(Parents[1].get().Node.get<Decl>())); |
811 | }); |
812 | } |
813 | |
814 | TEST(ASTSelectionFinder, SelectVarDeclStmt) { |
815 | StringRef Source = R"( |
816 | void f() { |
817 | { |
818 | int a; |
819 | } |
820 | } |
821 | )" ; |
822 | // 'int a' |
823 | findSelectedASTNodesWithRange( |
824 | Source, Location: {.Line: 4, .Column: 8}, SelectionRange: FileRange{{.Line: 4, .Column: 8}, {.Line: 4, .Column: 14}}, |
825 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
826 | EXPECT_TRUE(Node); |
827 | std::optional<CodeRangeASTSelection> SelectedCode = |
828 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
829 | EXPECT_TRUE(SelectedCode); |
830 | EXPECT_EQ(SelectedCode->size(), 1u); |
831 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
832 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
833 | SelectedCode->getParents(); |
834 | EXPECT_EQ(Parents.size(), 4u); |
835 | EXPECT_TRUE( |
836 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
837 | // Function 'f' definition. |
838 | EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
839 | // Function body of function 'F'. |
840 | EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
841 | // Compound statement in body of 'F'. |
842 | EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
843 | }); |
844 | } |
845 | |
846 | TEST(ASTSelectionFinder, SelectEntireDeclStmtRange) { |
847 | StringRef Source = R"( |
848 | void f(int x, int y) { |
849 | int a = x * y; |
850 | } |
851 | )" ; |
852 | // 'int a = x * y' |
853 | findSelectedASTNodesWithRange( |
854 | Source, Location: {.Line: 3, .Column: 4}, SelectionRange: FileRange{{.Line: 3, .Column: 4}, {.Line: 3, .Column: 17}}, |
855 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
856 | EXPECT_TRUE(Node); |
857 | std::optional<CodeRangeASTSelection> SelectedCode = |
858 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
859 | EXPECT_TRUE(SelectedCode); |
860 | EXPECT_EQ(SelectedCode->size(), 1u); |
861 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
862 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
863 | SelectedCode->getParents(); |
864 | EXPECT_EQ(Parents.size(), 3u); |
865 | EXPECT_TRUE( |
866 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
867 | // Function 'f' definition. |
868 | EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
869 | // Function body of function 'F'. |
870 | EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
871 | }); |
872 | } |
873 | |
874 | TEST(ASTSelectionFinder, SelectEntireDeclStmtRangeWithMultipleDecls) { |
875 | StringRef Source = R"( |
876 | void f(int x, int y) { |
877 | int a = x * y, b = x - y; |
878 | } |
879 | )" ; |
880 | // 'b = x - y' |
881 | findSelectedASTNodesWithRange( |
882 | Source, Location: {.Line: 3, .Column: 19}, SelectionRange: FileRange{{.Line: 3, .Column: 19}, {.Line: 3, .Column: 28}}, |
883 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
884 | EXPECT_TRUE(Node); |
885 | std::optional<CodeRangeASTSelection> SelectedCode = |
886 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
887 | EXPECT_TRUE(SelectedCode); |
888 | EXPECT_EQ(SelectedCode->size(), 1u); |
889 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
890 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
891 | SelectedCode->getParents(); |
892 | EXPECT_EQ(Parents.size(), 3u); |
893 | EXPECT_TRUE( |
894 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
895 | // Function 'f' definition. |
896 | EXPECT_TRUE(isa<FunctionDecl>(Parents[1].get().Node.get<Decl>())); |
897 | // Function body of function 'F'. |
898 | EXPECT_TRUE(isa<CompoundStmt>(Parents[2].get().Node.get<Stmt>())); |
899 | }); |
900 | } |
901 | |
902 | TEST(ASTSelectionFinder, SimpleCodeRangeASTSelectionInObjCMethod) { |
903 | StringRef Source = R"(@interface I @end |
904 | @implementation I |
905 | - (void) f:(int)x with:(int) y { |
906 | int z = x; |
907 | [self f: 2 with: 3]; |
908 | if (x == 0) { |
909 | return; |
910 | } |
911 | x = 1; |
912 | return; |
913 | } |
914 | - (void)f2 { |
915 | int m = 0; |
916 | } |
917 | @end |
918 | )" ; |
919 | // Range that spans multiple methods is an invalid code range. |
920 | findSelectedASTNodesWithRange( |
921 | Source, Location: {.Line: 9, .Column: 2}, SelectionRange: FileRange{{.Line: 9, .Column: 2}, {.Line: 13, .Column: 1}}, |
922 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
923 | EXPECT_TRUE(Node); |
924 | std::optional<CodeRangeASTSelection> SelectedCode = |
925 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
926 | EXPECT_FALSE(SelectedCode); |
927 | }, |
928 | Language: SelectionFinderVisitor::Lang_OBJC); |
929 | // Just 'z = x;': |
930 | findSelectedASTNodesWithRange( |
931 | Source, Location: {.Line: 4, .Column: 2}, SelectionRange: FileRange{{.Line: 4, .Column: 2}, {.Line: 4, .Column: 13}}, |
932 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
933 | EXPECT_TRUE(Node); |
934 | std::optional<CodeRangeASTSelection> SelectedCode = |
935 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
936 | EXPECT_TRUE(SelectedCode); |
937 | EXPECT_EQ(SelectedCode->size(), 1u); |
938 | EXPECT_TRUE(isa<DeclStmt>((*SelectedCode)[0])); |
939 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
940 | SelectedCode->getParents(); |
941 | EXPECT_EQ(Parents.size(), 4u); |
942 | EXPECT_TRUE( |
943 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
944 | // 'I' @implementation. |
945 | EXPECT_TRUE(isa<ObjCImplDecl>(Parents[1].get().Node.get<Decl>())); |
946 | // Function 'f' definition. |
947 | EXPECT_TRUE(isa<ObjCMethodDecl>(Parents[2].get().Node.get<Decl>())); |
948 | // Function body of function 'F'. |
949 | EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
950 | }, |
951 | Language: SelectionFinderVisitor::Lang_OBJC); |
952 | // From '[self f: 2 with: 3]' until just before 'x = 1;': |
953 | findSelectedASTNodesWithRange( |
954 | Source, Location: {.Line: 5, .Column: 2}, SelectionRange: FileRange{{.Line: 5, .Column: 2}, {.Line: 9, .Column: 1}}, |
955 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
956 | EXPECT_TRUE(Node); |
957 | std::optional<CodeRangeASTSelection> SelectedCode = |
958 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
959 | EXPECT_TRUE(SelectedCode); |
960 | EXPECT_EQ(SelectedCode->size(), 2u); |
961 | EXPECT_TRUE(isa<ObjCMessageExpr>((*SelectedCode)[0])); |
962 | EXPECT_TRUE(isa<IfStmt>((*SelectedCode)[1])); |
963 | ArrayRef<SelectedASTNode::ReferenceType> Parents = |
964 | SelectedCode->getParents(); |
965 | EXPECT_EQ(Parents.size(), 4u); |
966 | EXPECT_TRUE( |
967 | isa<TranslationUnitDecl>(Parents[0].get().Node.get<Decl>())); |
968 | // 'I' @implementation. |
969 | EXPECT_TRUE(isa<ObjCImplDecl>(Parents[1].get().Node.get<Decl>())); |
970 | // Function 'f' definition. |
971 | EXPECT_TRUE(isa<ObjCMethodDecl>(Parents[2].get().Node.get<Decl>())); |
972 | // Function body of function 'F'. |
973 | EXPECT_TRUE(isa<CompoundStmt>(Parents[3].get().Node.get<Stmt>())); |
974 | }, |
975 | Language: SelectionFinderVisitor::Lang_OBJC); |
976 | } |
977 | |
978 | TEST(ASTSelectionFinder, CanonicalizeObjCStringLiteral) { |
979 | StringRef Source = R"( |
980 | void foo() { |
981 | (void)@"test"; |
982 | } |
983 | )" ; |
984 | // Just '"test"': |
985 | findSelectedASTNodesWithRange( |
986 | Source, Location: {.Line: 3, .Column: 10}, SelectionRange: FileRange{{.Line: 3, .Column: 10}, {.Line: 3, .Column: 16}}, |
987 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
988 | EXPECT_TRUE(Node); |
989 | std::optional<CodeRangeASTSelection> SelectedCode = |
990 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
991 | EXPECT_TRUE(SelectedCode); |
992 | EXPECT_EQ(SelectedCode->size(), 1u); |
993 | EXPECT_TRUE(isa<ObjCStringLiteral>((*SelectedCode)[0])); |
994 | }, |
995 | Language: SelectionFinderVisitor::Lang_OBJC); |
996 | // Just 'test': |
997 | findSelectedASTNodesWithRange( |
998 | Source, Location: {.Line: 3, .Column: 11}, SelectionRange: FileRange{{.Line: 3, .Column: 11}, {.Line: 3, .Column: 15}}, |
999 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
1000 | EXPECT_TRUE(Node); |
1001 | std::optional<CodeRangeASTSelection> SelectedCode = |
1002 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
1003 | EXPECT_TRUE(SelectedCode); |
1004 | EXPECT_EQ(SelectedCode->size(), 1u); |
1005 | EXPECT_TRUE(isa<ObjCStringLiteral>((*SelectedCode)[0])); |
1006 | }, |
1007 | Language: SelectionFinderVisitor::Lang_OBJC); |
1008 | } |
1009 | |
1010 | TEST(ASTSelectionFinder, CanonicalizeMemberCalleeToCall) { |
1011 | StringRef Source = R"( |
1012 | class AClass { public: |
1013 | void method(); |
1014 | int afield; |
1015 | void selectWholeCallWhenJustMethodSelected(int &i) { |
1016 | method(); |
1017 | } |
1018 | }; |
1019 | void selectWholeCallWhenJustMethodSelected() { |
1020 | AClass a; |
1021 | a.method(); |
1022 | } |
1023 | void dontSelectArgument(AClass &a) { |
1024 | a.selectWholeCallWhenJustMethodSelected(a.afield); |
1025 | } |
1026 | )" ; |
1027 | // Just 'method' with implicit 'this': |
1028 | findSelectedASTNodesWithRange( |
1029 | Source, Location: {.Line: 6, .Column: 5}, SelectionRange: FileRange{{.Line: 6, .Column: 5}, {.Line: 6, .Column: 11}}, |
1030 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
1031 | EXPECT_TRUE(Node); |
1032 | std::optional<CodeRangeASTSelection> SelectedCode = |
1033 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
1034 | EXPECT_TRUE(SelectedCode); |
1035 | EXPECT_EQ(SelectedCode->size(), 1u); |
1036 | EXPECT_TRUE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
1037 | }); |
1038 | // Just 'method': |
1039 | findSelectedASTNodesWithRange( |
1040 | Source, Location: {.Line: 11, .Column: 5}, SelectionRange: FileRange{{.Line: 11, .Column: 5}, {.Line: 11, .Column: 11}}, |
1041 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
1042 | EXPECT_TRUE(Node); |
1043 | std::optional<CodeRangeASTSelection> SelectedCode = |
1044 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
1045 | EXPECT_TRUE(SelectedCode); |
1046 | EXPECT_EQ(SelectedCode->size(), 1u); |
1047 | EXPECT_TRUE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
1048 | }); |
1049 | // Just 'afield', which should not select the call. |
1050 | findSelectedASTNodesWithRange( |
1051 | Source, Location: {.Line: 14, .Column: 5}, SelectionRange: FileRange{{.Line: 14, .Column: 45}, {.Line: 14, .Column: 51}}, |
1052 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
1053 | EXPECT_TRUE(Node); |
1054 | std::optional<CodeRangeASTSelection> SelectedCode = |
1055 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
1056 | EXPECT_TRUE(SelectedCode); |
1057 | EXPECT_EQ(SelectedCode->size(), 1u); |
1058 | EXPECT_FALSE(isa<CXXMemberCallExpr>((*SelectedCode)[0])); |
1059 | }); |
1060 | } |
1061 | |
1062 | TEST(ASTSelectionFinder, CanonicalizeFuncCalleeToCall) { |
1063 | StringRef Source = R"( |
1064 | void function(); |
1065 | |
1066 | void test() { |
1067 | function(); |
1068 | } |
1069 | )" ; |
1070 | // Just 'function': |
1071 | findSelectedASTNodesWithRange( |
1072 | Source, Location: {.Line: 5, .Column: 3}, SelectionRange: FileRange{{.Line: 5, .Column: 3}, {.Line: 5, .Column: 11}}, |
1073 | Consumer: [](SourceRange SelectionRange, std::optional<SelectedASTNode> Node) { |
1074 | EXPECT_TRUE(Node); |
1075 | Node->dump(); |
1076 | std::optional<CodeRangeASTSelection> SelectedCode = |
1077 | CodeRangeASTSelection::create(SelectionRange, ASTSelection: std::move(*Node)); |
1078 | EXPECT_TRUE(SelectedCode); |
1079 | EXPECT_EQ(SelectedCode->size(), 1u); |
1080 | EXPECT_TRUE(isa<CallExpr>((*SelectedCode)[0])); |
1081 | EXPECT_TRUE(isa<CompoundStmt>( |
1082 | SelectedCode->getParents()[SelectedCode->getParents().size() - 1] |
1083 | .get() |
1084 | .Node.get<Stmt>())); |
1085 | }); |
1086 | } |
1087 | |
1088 | } // end anonymous namespace |
1089 | |