blob: b4e8bc042b89a31fa8c91122fe69b4c8e8a93c86 [file] [log] [blame]
Alex Lorenza844f392017-08-24 13:51:09 +00001//===- unittest/Tooling/ASTSelectionTest.cpp ------------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "TestVisitor.h"
11#include "clang/Basic/SourceManager.h"
12#include "clang/Tooling/Refactoring/ASTSelection.h"
13
14using namespace clang;
15using namespace tooling;
16
17namespace {
18
19struct FileLocation {
20 unsigned Line, Column;
21
22 SourceLocation translate(const SourceManager &SM) {
23 return SM.translateLineCol(SM.getMainFileID(), Line, Column);
24 }
25};
26
27using FileRange = std::pair<FileLocation, FileLocation>;
28
29class SelectionFinderVisitor : public TestVisitor<SelectionFinderVisitor> {
30 FileLocation Location;
31 Optional<FileRange> SelectionRange;
32
33public:
34 Optional<SelectedASTNode> Selection;
35
36 SelectionFinderVisitor(FileLocation Location,
37 Optional<FileRange> SelectionRange)
38 : Location(Location), SelectionRange(SelectionRange) {}
39
40 bool VisitTranslationUnitDecl(const TranslationUnitDecl *TU) {
41 const ASTContext &Context = TU->getASTContext();
42 const SourceManager &SM = Context.getSourceManager();
43
44 SourceRange SelRange;
45 if (SelectionRange) {
46 SelRange = SourceRange(SelectionRange->first.translate(SM),
47 SelectionRange->second.translate(SM));
48 } else {
49 SourceLocation Loc = Location.translate(SM);
50 SelRange = SourceRange(Loc, Loc);
51 }
52 Selection = findSelectedASTNodes(Context, SelRange);
53 return false;
54 }
55};
56
57Optional<SelectedASTNode>
58findSelectedASTNodes(StringRef Source, FileLocation Location,
59 Optional<FileRange> SelectionRange,
60 SelectionFinderVisitor::Language Language =
61 SelectionFinderVisitor::Lang_CXX11) {
62 SelectionFinderVisitor Visitor(Location, SelectionRange);
63 EXPECT_TRUE(Visitor.runOver(Source, Language));
64 return std::move(Visitor.Selection);
65}
66
67void checkNodeImpl(bool IsTypeMatched, const SelectedASTNode &Node,
68 SourceSelectionKind SelectionKind, unsigned NumChildren) {
69 ASSERT_TRUE(IsTypeMatched);
70 EXPECT_EQ(Node.Children.size(), NumChildren);
71 ASSERT_EQ(Node.SelectionKind, SelectionKind);
72}
73
74void checkDeclName(const SelectedASTNode &Node, StringRef Name) {
75 const auto *ND = Node.Node.get<NamedDecl>();
76 EXPECT_TRUE(!!ND);
77 ASSERT_EQ(ND->getName(), Name);
78}
79
80template <typename T>
81const SelectedASTNode &
82checkNode(const SelectedASTNode &StmtNode, SourceSelectionKind SelectionKind,
83 unsigned NumChildren = 0,
84 typename std::enable_if<std::is_base_of<Stmt, T>::value, T>::type
85 *StmtOverloadChecker = nullptr) {
86 checkNodeImpl(isa<T>(StmtNode.Node.get<Stmt>()), StmtNode, SelectionKind,
87 NumChildren);
88 return StmtNode;
89}
90
91template <typename T>
92const SelectedASTNode &
93checkNode(const SelectedASTNode &DeclNode, SourceSelectionKind SelectionKind,
94 unsigned NumChildren = 0, StringRef Name = "",
95 typename std::enable_if<std::is_base_of<Decl, T>::value, T>::type
96 *DeclOverloadChecker = nullptr) {
97 checkNodeImpl(isa<T>(DeclNode.Node.get<Decl>()), DeclNode, SelectionKind,
98 NumChildren);
99 if (!Name.empty())
100 checkDeclName(DeclNode, Name);
101 return DeclNode;
102}
103
104struct ForAllChildrenOf {
105 const SelectedASTNode &Node;
106
107 static void childKindVerifier(const SelectedASTNode &Node,
108 SourceSelectionKind SelectionKind) {
109 for (const SelectedASTNode &Child : Node.Children) {
110 ASSERT_EQ(Node.SelectionKind, SelectionKind);
111 childKindVerifier(Child, SelectionKind);
112 }
113 }
114
115public:
116 ForAllChildrenOf(const SelectedASTNode &Node) : Node(Node) {}
117
118 void shouldHaveSelectionKind(SourceSelectionKind Kind) {
119 childKindVerifier(Node, Kind);
120 }
121};
122
123ForAllChildrenOf allChildrenOf(const SelectedASTNode &Node) {
124 return ForAllChildrenOf(Node);
125}
126
127TEST(ASTSelectionFinder, CursorNoSelection) {
128 Optional<SelectedASTNode> Node =
129 findSelectedASTNodes(" void f() { }", {1, 1}, None);
130 EXPECT_FALSE(Node);
131}
132
133TEST(ASTSelectionFinder, CursorAtStartOfFunction) {
134 Optional<SelectedASTNode> Node =
135 findSelectedASTNodes("void f() { }", {1, 1}, None);
136 EXPECT_TRUE(Node);
137 checkNode<TranslationUnitDecl>(*Node, SourceSelectionKind::None,
138 /*NumChildren=*/1);
139 checkNode<FunctionDecl>(Node->Children[0],
140 SourceSelectionKind::ContainsSelection,
141 /*NumChildren=*/0, /*Name=*/"f");
142
143 // Check that the dumping works.
144 std::string DumpValue;
145 llvm::raw_string_ostream OS(DumpValue);
146 Node->Children[0].dump(OS);
147 ASSERT_EQ(OS.str(), "FunctionDecl \"f\" contains-selection\n");
148}
149
150TEST(ASTSelectionFinder, RangeNoSelection) {
151 {
152 Optional<SelectedASTNode> Node = findSelectedASTNodes(
153 " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}});
154 EXPECT_FALSE(Node);
155 }
156 {
157 Optional<SelectedASTNode> Node = findSelectedASTNodes(
158 " void f() { }", {1, 1}, FileRange{{1, 1}, {1, 2}});
159 EXPECT_FALSE(Node);
160 }
161}
162
163TEST(ASTSelectionFinder, EmptyRangeFallbackToCursor) {
164 Optional<SelectedASTNode> Node =
165 findSelectedASTNodes("void f() { }", {1, 1}, FileRange{{1, 1}, {1, 1}});
166 EXPECT_TRUE(Node);
167 checkNode<FunctionDecl>(Node->Children[0],
168 SourceSelectionKind::ContainsSelection,
169 /*NumChildren=*/0, /*Name=*/"f");
170}
171
172TEST(ASTSelectionFinder, WholeFunctionSelection) {
173 StringRef Source = "int f(int x) { return x;\n}\nvoid f2() { }";
174 // From 'int' until just after '}':
175 {
176 auto Node = findSelectedASTNodes(Source, {1, 1}, FileRange{{1, 1}, {2, 2}});
177 EXPECT_TRUE(Node);
178 EXPECT_EQ(Node->Children.size(), 1u);
179 const auto &Fn = checkNode<FunctionDecl>(
180 Node->Children[0], SourceSelectionKind::ContainsSelection,
181 /*NumChildren=*/2, /*Name=*/"f");
182 checkNode<ParmVarDecl>(Fn.Children[0],
183 SourceSelectionKind::InsideSelection);
184 const auto &Body = checkNode<CompoundStmt>(
185 Fn.Children[1], SourceSelectionKind::InsideSelection,
186 /*NumChildren=*/1);
187 const auto &Return = checkNode<ReturnStmt>(
188 Body.Children[0], SourceSelectionKind::InsideSelection,
189 /*NumChildren=*/1);
190 checkNode<ImplicitCastExpr>(Return.Children[0],
191 SourceSelectionKind::InsideSelection,
192 /*NumChildren=*/1);
193 checkNode<DeclRefExpr>(Return.Children[0].Children[0],
194 SourceSelectionKind::InsideSelection);
195 }
196 // From 'int' until just before '}':
197 {
198 auto Node = findSelectedASTNodes(Source, {2, 1}, FileRange{{1, 1}, {2, 1}});
199 EXPECT_TRUE(Node);
200 EXPECT_EQ(Node->Children.size(), 1u);
201 const auto &Fn = checkNode<FunctionDecl>(
202 Node->Children[0], SourceSelectionKind::ContainsSelection,
203 /*NumChildren=*/2, /*Name=*/"f");
204 const auto &Body = checkNode<CompoundStmt>(
205 Fn.Children[1], SourceSelectionKind::ContainsSelectionEnd,
206 /*NumChildren=*/1);
207 checkNode<ReturnStmt>(Body.Children[0],
208 SourceSelectionKind::InsideSelection,
209 /*NumChildren=*/1);
210 }
211 // From '{' until just after '}':
212 {
213 auto Node =
214 findSelectedASTNodes(Source, {1, 14}, FileRange{{1, 14}, {2, 2}});
215 EXPECT_TRUE(Node);
216 EXPECT_EQ(Node->Children.size(), 1u);
217 const auto &Fn = checkNode<FunctionDecl>(
218 Node->Children[0], SourceSelectionKind::ContainsSelection,
219 /*NumChildren=*/1, /*Name=*/"f");
220 const auto &Body = checkNode<CompoundStmt>(
221 Fn.Children[0], SourceSelectionKind::ContainsSelection,
222 /*NumChildren=*/1);
223 checkNode<ReturnStmt>(Body.Children[0],
224 SourceSelectionKind::InsideSelection,
225 /*NumChildren=*/1);
226 }
227 // From 'x' until just after '}':
228 {
229 auto Node =
230 findSelectedASTNodes(Source, {2, 2}, FileRange{{1, 11}, {2, 2}});
231 EXPECT_TRUE(Node);
232 EXPECT_EQ(Node->Children.size(), 1u);
233 const auto &Fn = checkNode<FunctionDecl>(
234 Node->Children[0], SourceSelectionKind::ContainsSelection,
235 /*NumChildren=*/2, /*Name=*/"f");
236 checkNode<ParmVarDecl>(Fn.Children[0],
237 SourceSelectionKind::ContainsSelectionStart);
238 const auto &Body = checkNode<CompoundStmt>(
239 Fn.Children[1], SourceSelectionKind::InsideSelection,
240 /*NumChildren=*/1);
241 checkNode<ReturnStmt>(Body.Children[0],
242 SourceSelectionKind::InsideSelection,
243 /*NumChildren=*/1);
244 }
245}
246
247TEST(ASTSelectionFinder, MultipleFunctionSelection) {
248 StringRef Source = R"(void f0() {
249}
250void f1() { }
251void f2() { }
252void f3() { }
253)";
254 auto SelectedF1F2 = [](Optional<SelectedASTNode> Node) {
255 EXPECT_TRUE(Node);
256 EXPECT_EQ(Node->Children.size(), 2u);
257 checkNode<FunctionDecl>(Node->Children[0],
258 SourceSelectionKind::InsideSelection,
259 /*NumChildren=*/1, /*Name=*/"f1");
260 checkNode<FunctionDecl>(Node->Children[1],
261 SourceSelectionKind::InsideSelection,
262 /*NumChildren=*/1, /*Name=*/"f2");
263 };
264 // Just after '}' of f0 and just before 'void' of f3:
265 SelectedF1F2(findSelectedASTNodes(Source, {2, 2}, FileRange{{2, 2}, {5, 1}}));
266 // Just before 'void' of f1 and just after '}' of f2:
267 SelectedF1F2(
268 findSelectedASTNodes(Source, {3, 1}, FileRange{{3, 1}, {4, 14}}));
269}
270
271TEST(ASTSelectionFinder, MultipleStatementSelection) {
272 StringRef Source = R"(void f(int x, int y) {
273 int z = x;
274 f(2, 3);
275 if (x == 0) {
276 return;
277 }
278 x = 1;
279 return;
280})";
281 // From 'f(2,3)' until just before 'x = 1;':
282 {
283 auto Node = findSelectedASTNodes(Source, {3, 2}, FileRange{{3, 2}, {7, 1}});
284 EXPECT_TRUE(Node);
285 EXPECT_EQ(Node->Children.size(), 1u);
286 const auto &Fn = checkNode<FunctionDecl>(
287 Node->Children[0], SourceSelectionKind::ContainsSelection,
288 /*NumChildren=*/1, /*Name=*/"f");
289 const auto &Body = checkNode<CompoundStmt>(
290 Fn.Children[0], SourceSelectionKind::ContainsSelection,
291 /*NumChildren=*/2);
292 allChildrenOf(checkNode<CallExpr>(Body.Children[0],
293 SourceSelectionKind::InsideSelection,
294 /*NumChildren=*/3))
295 .shouldHaveSelectionKind(SourceSelectionKind::InsideSelection);
296 allChildrenOf(checkNode<IfStmt>(Body.Children[1],
297 SourceSelectionKind::InsideSelection,
298 /*NumChildren=*/2))
299 .shouldHaveSelectionKind(SourceSelectionKind::InsideSelection);
300 }
301 // From 'f(2,3)' until just before ';' in 'x = 1;':
302 {
303 auto Node = findSelectedASTNodes(Source, {3, 2}, FileRange{{3, 2}, {7, 8}});
304 EXPECT_TRUE(Node);
305 EXPECT_EQ(Node->Children.size(), 1u);
306 const auto &Fn = checkNode<FunctionDecl>(
307 Node->Children[0], SourceSelectionKind::ContainsSelection,
308 /*NumChildren=*/1, /*Name=*/"f");
309 const auto &Body = checkNode<CompoundStmt>(
310 Fn.Children[0], SourceSelectionKind::ContainsSelection,
311 /*NumChildren=*/3);
312 checkNode<CallExpr>(Body.Children[0], SourceSelectionKind::InsideSelection,
313 /*NumChildren=*/3);
314 checkNode<IfStmt>(Body.Children[1], SourceSelectionKind::InsideSelection,
315 /*NumChildren=*/2);
316 checkNode<BinaryOperator>(Body.Children[2],
317 SourceSelectionKind::InsideSelection,
318 /*NumChildren=*/2);
319 }
320 // From the middle of 'int z = 3' until the middle of 'x = 1;':
321 {
322 auto Node =
323 findSelectedASTNodes(Source, {2, 10}, FileRange{{2, 10}, {7, 5}});
324 EXPECT_TRUE(Node);
325 EXPECT_EQ(Node->Children.size(), 1u);
326 const auto &Fn = checkNode<FunctionDecl>(
327 Node->Children[0], SourceSelectionKind::ContainsSelection,
328 /*NumChildren=*/1, /*Name=*/"f");
329 const auto &Body = checkNode<CompoundStmt>(
330 Fn.Children[0], SourceSelectionKind::ContainsSelection,
331 /*NumChildren=*/4);
332 checkNode<DeclStmt>(Body.Children[0],
333 SourceSelectionKind::ContainsSelectionStart,
334 /*NumChildren=*/1);
335 checkNode<CallExpr>(Body.Children[1], SourceSelectionKind::InsideSelection,
336 /*NumChildren=*/3);
337 checkNode<IfStmt>(Body.Children[2], SourceSelectionKind::InsideSelection,
338 /*NumChildren=*/2);
339 checkNode<BinaryOperator>(Body.Children[3],
340 SourceSelectionKind::ContainsSelectionEnd,
341 /*NumChildren=*/1);
342 }
343}
344
345TEST(ASTSelectionFinder, SelectionInFunctionInObjCImplementation) {
346 StringRef Source = R"(
347@interface I
348@end
349@implementation I
350
351int notSelected() { }
352
353int selected(int x) {
354 return x;
355}
356
357@end
358@implementation I(Cat)
359
360void catF() { }
361
362@end
363
364void outerFunction() { }
365)";
366 // Just the 'x' expression in 'selected':
367 {
368 auto Node =
369 findSelectedASTNodes(Source, {9, 10}, FileRange{{9, 10}, {9, 11}},
370 SelectionFinderVisitor::Lang_OBJC);
371 EXPECT_TRUE(Node);
372 EXPECT_EQ(Node->Children.size(), 1u);
373 const auto &Impl = checkNode<ObjCImplementationDecl>(
374 Node->Children[0], SourceSelectionKind::ContainsSelection,
375 /*NumChildren=*/1, /*Name=*/"I");
376 const auto &Fn = checkNode<FunctionDecl>(
377 Impl.Children[0], SourceSelectionKind::ContainsSelection,
378 /*NumChildren=*/1, /*Name=*/"selected");
379 allChildrenOf(Fn).shouldHaveSelectionKind(
380 SourceSelectionKind::ContainsSelection);
381 }
382 // The entire 'catF':
383 {
384 auto Node =
385 findSelectedASTNodes(Source, {15, 1}, FileRange{{15, 1}, {15, 16}},
386 SelectionFinderVisitor::Lang_OBJC);
387 EXPECT_TRUE(Node);
388 EXPECT_EQ(Node->Children.size(), 1u);
389 const auto &Impl = checkNode<ObjCCategoryImplDecl>(
390 Node->Children[0], SourceSelectionKind::ContainsSelection,
391 /*NumChildren=*/1, /*Name=*/"Cat");
392 const auto &Fn = checkNode<FunctionDecl>(
393 Impl.Children[0], SourceSelectionKind::ContainsSelection,
394 /*NumChildren=*/1, /*Name=*/"catF");
395 allChildrenOf(Fn).shouldHaveSelectionKind(
396 SourceSelectionKind::ContainsSelection);
397 }
398 // From the line before 'selected' to the line after 'catF':
399 {
400 auto Node =
401 findSelectedASTNodes(Source, {16, 1}, FileRange{{7, 1}, {16, 1}},
402 SelectionFinderVisitor::Lang_OBJC);
403 EXPECT_TRUE(Node);
404 EXPECT_EQ(Node->Children.size(), 2u);
405 const auto &Impl = checkNode<ObjCImplementationDecl>(
406 Node->Children[0], SourceSelectionKind::ContainsSelectionStart,
407 /*NumChildren=*/1, /*Name=*/"I");
408 const auto &Selected = checkNode<FunctionDecl>(
409 Impl.Children[0], SourceSelectionKind::InsideSelection,
410 /*NumChildren=*/2, /*Name=*/"selected");
411 allChildrenOf(Selected).shouldHaveSelectionKind(
412 SourceSelectionKind::InsideSelection);
413 const auto &Cat = checkNode<ObjCCategoryImplDecl>(
414 Node->Children[1], SourceSelectionKind::ContainsSelectionEnd,
415 /*NumChildren=*/1, /*Name=*/"Cat");
416 const auto &CatF = checkNode<FunctionDecl>(
417 Cat.Children[0], SourceSelectionKind::InsideSelection,
418 /*NumChildren=*/1, /*Name=*/"catF");
419 allChildrenOf(CatF).shouldHaveSelectionKind(
420 SourceSelectionKind::InsideSelection);
421 }
422 // Just the 'outer' function:
423 {
424 auto Node =
425 findSelectedASTNodes(Source, {19, 1}, FileRange{{19, 1}, {19, 25}},
426 SelectionFinderVisitor::Lang_OBJC);
427 EXPECT_TRUE(Node);
428 EXPECT_EQ(Node->Children.size(), 1u);
429 checkNode<FunctionDecl>(Node->Children[0],
430 SourceSelectionKind::ContainsSelection,
431 /*NumChildren=*/1, /*Name=*/"outerFunction");
432 }
433}
434
435TEST(ASTSelectionFinder, FunctionInObjCImplementationCarefulWithEarlyExit) {
436 StringRef Source = R"(
437@interface I
438@end
439@implementation I
440
441void selected() {
442}
443
444- (void) method { }
445
446@end
447)";
448 // Just 'selected'
449 {
450 auto Node = findSelectedASTNodes(Source, {6, 1}, FileRange{{6, 1}, {7, 2}},
451 SelectionFinderVisitor::Lang_OBJC);
452 EXPECT_TRUE(Node);
453 EXPECT_EQ(Node->Children.size(), 1u);
454 const auto &Impl = checkNode<ObjCImplementationDecl>(
455 Node->Children[0], SourceSelectionKind::ContainsSelection,
456 /*NumChildren=*/1, /*Name=*/"I");
457 checkNode<FunctionDecl>(Impl.Children[0],
458 SourceSelectionKind::ContainsSelection,
459 /*NumChildren=*/1, /*Name=*/"selected");
460 }
461}
462
463TEST(ASTSelectionFinder, AvoidImplicitDeclarations) {
464 StringRef Source = R"(
465struct Copy {
466 int x;
467};
468void foo() {
469 Copy x;
470 Copy y = x;
471}
472)";
473 // The entire struct 'Copy':
474 auto Node = findSelectedASTNodes(Source, {2, 1}, FileRange{{2, 1}, {4, 3}});
475 EXPECT_TRUE(Node);
476 EXPECT_EQ(Node->Children.size(), 1u);
477 const auto &Record = checkNode<CXXRecordDecl>(
478 Node->Children[0], SourceSelectionKind::InsideSelection,
479 /*NumChildren=*/1, /*Name=*/"Copy");
480 checkNode<FieldDecl>(Record.Children[0],
481 SourceSelectionKind::InsideSelection);
482}
483
484} // end anonymous namespace