1 | //===-- SymbolCollectorTests.cpp -------------------------------*- C++ -*-===// |
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 "Annotations.h" |
10 | #include "TestFS.h" |
11 | #include "TestTU.h" |
12 | #include "URI.h" |
13 | #include "clang-include-cleaner/Record.h" |
14 | #include "index/SymbolCollector.h" |
15 | #include "clang/Basic/FileManager.h" |
16 | #include "clang/Basic/FileSystemOptions.h" |
17 | #include "clang/Basic/SourceLocation.h" |
18 | #include "clang/Frontend/CompilerInstance.h" |
19 | #include "clang/Index/IndexingAction.h" |
20 | #include "clang/Index/IndexingOptions.h" |
21 | #include "clang/Tooling/Tooling.h" |
22 | #include "llvm/ADT/IntrusiveRefCntPtr.h" |
23 | #include "llvm/ADT/StringRef.h" |
24 | #include "llvm/Support/MemoryBuffer.h" |
25 | #include "llvm/Support/VirtualFileSystem.h" |
26 | #include "gmock/gmock-matchers.h" |
27 | #include "gmock/gmock.h" |
28 | #include "gtest/gtest.h" |
29 | |
30 | #include <memory> |
31 | #include <optional> |
32 | #include <string> |
33 | #include <utility> |
34 | |
35 | namespace clang { |
36 | namespace clangd { |
37 | namespace { |
38 | |
39 | using ::testing::_; |
40 | using ::testing::AllOf; |
41 | using ::testing::Contains; |
42 | using ::testing::Each; |
43 | using ::testing::ElementsAre; |
44 | using ::testing::Field; |
45 | using ::testing::IsEmpty; |
46 | using ::testing::Not; |
47 | using ::testing::Pair; |
48 | using ::testing::UnorderedElementsAre; |
49 | using ::testing::UnorderedElementsAreArray; |
50 | |
51 | // GMock helpers for matching Symbol. |
52 | MATCHER_P(labeled, Label, "" ) { |
53 | return (arg.Name + arg.Signature).str() == Label; |
54 | } |
55 | MATCHER_P(returnType, D, "" ) { return arg.ReturnType == D; } |
56 | MATCHER_P(doc, D, "" ) { return arg.Documentation == D; } |
57 | MATCHER_P(snippet, S, "" ) { |
58 | return (arg.Name + arg.CompletionSnippetSuffix).str() == S; |
59 | } |
60 | MATCHER_P(qName, Name, "" ) { return (arg.Scope + arg.Name).str() == Name; } |
61 | MATCHER_P(hasName, Name, "" ) { return arg.Name == Name; } |
62 | MATCHER_P(templateArgs, TemplArgs, "" ) { |
63 | return arg.TemplateSpecializationArgs == TemplArgs; |
64 | } |
65 | MATCHER_P(hasKind, Kind, "" ) { return arg.SymInfo.Kind == Kind; } |
66 | MATCHER_P(declURI, P, "" ) { |
67 | return StringRef(arg.CanonicalDeclaration.FileURI) == P; |
68 | } |
69 | MATCHER_P(defURI, P, "" ) { return StringRef(arg.Definition.FileURI) == P; } |
70 | MATCHER(, "" ) { return !arg.IncludeHeaders.empty(); } |
71 | MATCHER_P(, , "" ) { |
72 | return (arg.IncludeHeaders.size() == 1) && |
73 | (arg.IncludeHeaders.begin()->IncludeHeader == P); |
74 | } |
75 | MATCHER_P2(, , , "" ) { |
76 | return (arg.IncludeHeader == includeHeader) && (arg.References == References); |
77 | } |
78 | bool rangesMatch(const SymbolLocation &Loc, const Range &R) { |
79 | return std::make_tuple(args: Loc.Start.line(), args: Loc.Start.column(), args: Loc.End.line(), |
80 | args: Loc.End.column()) == |
81 | std::make_tuple(args: R.start.line, args: R.start.character, args: R.end.line, |
82 | args: R.end.character); |
83 | } |
84 | MATCHER_P(declRange, Pos, "" ) { |
85 | return rangesMatch(arg.CanonicalDeclaration, Pos); |
86 | } |
87 | MATCHER_P(defRange, Pos, "" ) { return rangesMatch(arg.Definition, Pos); } |
88 | MATCHER_P(refCount, R, "" ) { return int(arg.References) == R; } |
89 | MATCHER_P(forCodeCompletion, IsIndexedForCodeCompletion, "" ) { |
90 | return static_cast<bool>(arg.Flags & Symbol::IndexedForCodeCompletion) == |
91 | IsIndexedForCodeCompletion; |
92 | } |
93 | MATCHER(deprecated, "" ) { return arg.Flags & Symbol::Deprecated; } |
94 | MATCHER(implementationDetail, "" ) { |
95 | return arg.Flags & Symbol::ImplementationDetail; |
96 | } |
97 | MATCHER(visibleOutsideFile, "" ) { |
98 | return static_cast<bool>(arg.Flags & Symbol::VisibleOutsideFile); |
99 | } |
100 | MATCHER(refRange, "" ) { |
101 | const Ref &Pos = ::testing::get<0>(arg); |
102 | const Range &Range = ::testing::get<1>(arg); |
103 | return rangesMatch(Loc: Pos.Location, R: Range); |
104 | } |
105 | MATCHER_P2(OverriddenBy, Subject, Object, "" ) { |
106 | return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; |
107 | } |
108 | MATCHER(isSpelled, "" ) { |
109 | return static_cast<bool>(arg.Kind & RefKind::Spelled); |
110 | } |
111 | ::testing::Matcher<const std::vector<Ref> &> |
112 | haveRanges(const std::vector<Range> Ranges) { |
113 | return ::testing::UnorderedPointwise(tuple2_matcher: refRange(), rhs_container: Ranges); |
114 | } |
115 | |
116 | class ShouldCollectSymbolTest : public ::testing::Test { |
117 | public: |
118 | void build(llvm::StringRef , llvm::StringRef Code = "" ) { |
119 | File.HeaderFilename = HeaderName; |
120 | File.Filename = FileName; |
121 | File.HeaderCode = std::string(HeaderCode); |
122 | File.Code = std::string(Code); |
123 | AST = File.build(); |
124 | } |
125 | |
126 | // build() must have been called. |
127 | bool shouldCollect(llvm::StringRef Name, bool Qualified = true) { |
128 | assert(AST); |
129 | const NamedDecl &ND = |
130 | Qualified ? findDecl(AST&: *AST, QName: Name) : findUnqualifiedDecl(AST&: *AST, Name); |
131 | const SourceManager &SM = AST->getSourceManager(); |
132 | bool MainFile = isInsideMainFile(ND.getBeginLoc(), SM); |
133 | return SymbolCollector::shouldCollectSymbol( |
134 | ND, ASTCtx: AST->getASTContext(), Opts: SymbolCollector::Options(), IsMainFileSymbol: MainFile); |
135 | } |
136 | |
137 | protected: |
138 | std::string = "f.h" ; |
139 | std::string FileName = "f.cpp" ; |
140 | TestTU File; |
141 | std::optional<ParsedAST> AST; // Initialized after build. |
142 | }; |
143 | |
144 | TEST_F(ShouldCollectSymbolTest, ShouldCollectSymbol) { |
145 | build(HeaderCode: R"( |
146 | namespace nx { |
147 | class X{}; |
148 | auto f() { int Local; } // auto ensures function body is parsed. |
149 | struct { int x; } var; |
150 | } |
151 | )" , |
152 | Code: R"( |
153 | class InMain {}; |
154 | namespace { class InAnonymous {}; } |
155 | static void g(); |
156 | )" ); |
157 | auto AST = File.build(); |
158 | EXPECT_TRUE(shouldCollect("nx" )); |
159 | EXPECT_TRUE(shouldCollect("nx::X" )); |
160 | EXPECT_TRUE(shouldCollect("nx::f" )); |
161 | EXPECT_TRUE(shouldCollect("InMain" )); |
162 | EXPECT_TRUE(shouldCollect("InAnonymous" , /*Qualified=*/false)); |
163 | EXPECT_TRUE(shouldCollect("g" )); |
164 | |
165 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
166 | } |
167 | |
168 | TEST_F(ShouldCollectSymbolTest, CollectLocalClassesAndVirtualMethods) { |
169 | build(HeaderCode: R"( |
170 | namespace nx { |
171 | auto f() { |
172 | int Local; |
173 | auto LocalLambda = [&](){ |
174 | Local++; |
175 | class ClassInLambda{}; |
176 | return Local; |
177 | }; |
178 | } // auto ensures function body is parsed. |
179 | auto foo() { |
180 | class LocalBase { |
181 | virtual void LocalVirtual(); |
182 | void LocalConcrete(); |
183 | int BaseMember; |
184 | }; |
185 | } |
186 | } // namespace nx |
187 | )" , |
188 | Code: "" ); |
189 | auto AST = File.build(); |
190 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
191 | EXPECT_TRUE(shouldCollect("ClassInLambda" , /*Qualified=*/false)); |
192 | EXPECT_TRUE(shouldCollect("LocalBase" , /*Qualified=*/false)); |
193 | EXPECT_TRUE(shouldCollect("LocalVirtual" , /*Qualified=*/false)); |
194 | EXPECT_TRUE(shouldCollect("LocalConcrete" , /*Qualified=*/false)); |
195 | EXPECT_FALSE(shouldCollect("BaseMember" , /*Qualified=*/false)); |
196 | EXPECT_FALSE(shouldCollect("Local" , /*Qualified=*/false)); |
197 | } |
198 | |
199 | TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) { |
200 | HeaderName = "f.proto.h" ; |
201 | build( |
202 | HeaderCode: R"(// Generated by the protocol buffer compiler. DO NOT EDIT! |
203 | namespace nx { |
204 | class Top_Level {}; |
205 | class TopLevel {}; |
206 | enum Kind { |
207 | KIND_OK, |
208 | Kind_Not_Ok, |
209 | }; |
210 | })" ); |
211 | EXPECT_TRUE(shouldCollect("nx::TopLevel" )); |
212 | EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK" )); |
213 | EXPECT_TRUE(shouldCollect("nx::Kind" )); |
214 | |
215 | EXPECT_FALSE(shouldCollect("nx::Top_Level" )); |
216 | EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok" )); |
217 | } |
218 | |
219 | TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) { |
220 | HeaderName = "f.proto.h" ; |
221 | build(HeaderCode: R"( |
222 | namespace nx { |
223 | class Top_Level {}; |
224 | enum Kind { |
225 | Kind_Fine |
226 | }; |
227 | } |
228 | )" ); |
229 | EXPECT_TRUE(shouldCollect("nx::Top_Level" )); |
230 | EXPECT_TRUE(shouldCollect("nx::Kind_Fine" )); |
231 | } |
232 | |
233 | class SymbolIndexActionFactory : public tooling::FrontendActionFactory { |
234 | public: |
235 | SymbolIndexActionFactory(SymbolCollector::Options COpts) |
236 | : COpts(std::move(COpts)) {} |
237 | |
238 | std::unique_ptr<FrontendAction> create() override { |
239 | class IndexAction : public ASTFrontendAction { |
240 | public: |
241 | IndexAction(std::shared_ptr<index::IndexDataConsumer> DataConsumer, |
242 | const index::IndexingOptions &Opts, |
243 | std::shared_ptr<include_cleaner::PragmaIncludes> PI) |
244 | : DataConsumer(std::move(DataConsumer)), Opts(Opts), |
245 | PI(std::move(PI)) {} |
246 | |
247 | std::unique_ptr<ASTConsumer> |
248 | CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { |
249 | PI->record(CI); |
250 | return createIndexingASTConsumer(DataConsumer, Opts, |
251 | PP: CI.getPreprocessorPtr()); |
252 | } |
253 | |
254 | bool BeginInvocation(CompilerInstance &CI) override { |
255 | // Make the compiler parse all comments. |
256 | CI.getLangOpts().CommentOpts.ParseAllComments = true; |
257 | return true; |
258 | } |
259 | |
260 | private: |
261 | std::shared_ptr<index::IndexDataConsumer> DataConsumer; |
262 | index::IndexingOptions Opts; |
263 | std::shared_ptr<include_cleaner::PragmaIncludes> PI; |
264 | }; |
265 | index::IndexingOptions IndexOpts; |
266 | IndexOpts.SystemSymbolFilter = |
267 | index::IndexingOptions::SystemSymbolFilterKind::All; |
268 | IndexOpts.IndexFunctionLocals = true; |
269 | std::shared_ptr<include_cleaner::PragmaIncludes> PI = |
270 | std::make_shared<include_cleaner::PragmaIncludes>(); |
271 | COpts.PragmaIncludes = PI.get(); |
272 | Collector = std::make_shared<SymbolCollector>(args&: COpts); |
273 | return std::make_unique<IndexAction>(args&: Collector, args: std::move(IndexOpts), |
274 | args: std::move(PI)); |
275 | } |
276 | |
277 | std::shared_ptr<SymbolCollector> Collector; |
278 | SymbolCollector::Options COpts; |
279 | }; |
280 | |
281 | class SymbolCollectorTest : public ::testing::Test { |
282 | public: |
283 | SymbolCollectorTest() |
284 | : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), |
285 | TestHeaderName(testPath(File: "symbol.h" )), |
286 | TestFileName(testPath(File: "symbol.cc" )) { |
287 | TestHeaderURI = URI::create(AbsolutePath: TestHeaderName).toString(); |
288 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
289 | } |
290 | |
291 | // Note that unlike TestTU, no automatic header guard is added. |
292 | // HeaderCode should start with #pragma once to be treated as modular. |
293 | bool runSymbolCollector(llvm::StringRef , llvm::StringRef MainCode, |
294 | const std::vector<std::string> & = {}) { |
295 | llvm::IntrusiveRefCntPtr<FileManager> Files( |
296 | new FileManager(FileSystemOptions(), InMemoryFileSystem)); |
297 | |
298 | auto Factory = std::make_unique<SymbolIndexActionFactory>(args&: CollectorOpts); |
299 | |
300 | std::vector<std::string> Args = {"symbol_collector" , "-fsyntax-only" , |
301 | "-xc++" , "-include" , TestHeaderName}; |
302 | Args.insert(position: Args.end(), first: ExtraArgs.begin(), last: ExtraArgs.end()); |
303 | // This allows to override the "-xc++" with something else, i.e. |
304 | // -xobjective-c++. |
305 | Args.push_back(x: TestFileName); |
306 | |
307 | tooling::ToolInvocation Invocation( |
308 | Args, Factory->create(), Files.get(), |
309 | std::make_shared<PCHContainerOperations>()); |
310 | |
311 | // Multiple calls to runSymbolCollector with different contents will fail |
312 | // to update the filesystem! Why are we sharing one across tests, anyway? |
313 | EXPECT_TRUE(InMemoryFileSystem->addFile( |
314 | TestHeaderName, 0, llvm::MemoryBuffer::getMemBuffer(HeaderCode))); |
315 | EXPECT_TRUE(InMemoryFileSystem->addFile( |
316 | TestFileName, 0, llvm::MemoryBuffer::getMemBuffer(MainCode))); |
317 | Invocation.run(); |
318 | Symbols = Factory->Collector->takeSymbols(); |
319 | Refs = Factory->Collector->takeRefs(); |
320 | Relations = Factory->Collector->takeRelations(); |
321 | return true; |
322 | } |
323 | |
324 | protected: |
325 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; |
326 | std::string ; |
327 | std::string ; |
328 | std::string TestFileName; |
329 | std::string TestFileURI; |
330 | SymbolSlab Symbols; |
331 | RefSlab Refs; |
332 | RelationSlab Relations; |
333 | SymbolCollector::Options CollectorOpts; |
334 | }; |
335 | |
336 | TEST_F(SymbolCollectorTest, CollectSymbols) { |
337 | const std::string = R"( |
338 | class Foo { |
339 | Foo() {} |
340 | Foo(int a) {} |
341 | void f(); |
342 | friend void f1(); |
343 | friend class Friend; |
344 | Foo& operator=(const Foo&); |
345 | ~Foo(); |
346 | class Nested { |
347 | void f(); |
348 | }; |
349 | }; |
350 | class Friend { |
351 | }; |
352 | |
353 | void f1(); |
354 | inline void f2() {} |
355 | static const int KInt = 2; |
356 | const char* kStr = "123"; |
357 | |
358 | namespace { |
359 | void ff() {} // ignore |
360 | } |
361 | |
362 | void f1() { |
363 | auto LocalLambda = [&](){ |
364 | class ClassInLambda{}; |
365 | }; |
366 | } |
367 | |
368 | namespace foo { |
369 | // Type alias |
370 | typedef int int32; |
371 | using int32_t = int32; |
372 | |
373 | // Variable |
374 | int v1; |
375 | |
376 | // Namespace |
377 | namespace bar { |
378 | int v2; |
379 | } |
380 | // Namespace alias |
381 | namespace baz = bar; |
382 | |
383 | using bar::v2; |
384 | } // namespace foo |
385 | )" ; |
386 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
387 | EXPECT_THAT(Symbols, |
388 | UnorderedElementsAreArray( |
389 | {AllOf(qName("Foo" ), forCodeCompletion(true)), |
390 | AllOf(qName("Foo::Foo" ), forCodeCompletion(false)), |
391 | AllOf(qName("Foo::Foo" ), forCodeCompletion(false)), |
392 | AllOf(qName("Foo::f" ), forCodeCompletion(false)), |
393 | AllOf(qName("Foo::~Foo" ), forCodeCompletion(false)), |
394 | AllOf(qName("Foo::operator=" ), forCodeCompletion(false)), |
395 | AllOf(qName("Foo::Nested" ), forCodeCompletion(false)), |
396 | AllOf(qName("Foo::Nested::f" ), forCodeCompletion(false)), |
397 | AllOf(qName("ClassInLambda" ), forCodeCompletion(false)), |
398 | AllOf(qName("Friend" ), forCodeCompletion(true)), |
399 | AllOf(qName("f1" ), forCodeCompletion(true)), |
400 | AllOf(qName("f2" ), forCodeCompletion(true)), |
401 | AllOf(qName("KInt" ), forCodeCompletion(true)), |
402 | AllOf(qName("kStr" ), forCodeCompletion(true)), |
403 | AllOf(qName("foo" ), forCodeCompletion(true)), |
404 | AllOf(qName("foo::bar" ), forCodeCompletion(true)), |
405 | AllOf(qName("foo::int32" ), forCodeCompletion(true)), |
406 | AllOf(qName("foo::int32_t" ), forCodeCompletion(true)), |
407 | AllOf(qName("foo::v1" ), forCodeCompletion(true)), |
408 | AllOf(qName("foo::bar::v2" ), forCodeCompletion(true)), |
409 | AllOf(qName("foo::v2" ), forCodeCompletion(true)), |
410 | AllOf(qName("foo::baz" ), forCodeCompletion(true))})); |
411 | } |
412 | |
413 | TEST_F(SymbolCollectorTest, FileLocal) { |
414 | const std::string = R"( |
415 | class Foo {}; |
416 | namespace { |
417 | class Ignored {}; |
418 | } |
419 | void bar(); |
420 | )" ; |
421 | const std::string Main = R"( |
422 | class ForwardDecl; |
423 | void bar() {} |
424 | static void a(); |
425 | class B {}; |
426 | namespace { |
427 | void c(); |
428 | } |
429 | )" ; |
430 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
431 | EXPECT_THAT(Symbols, |
432 | UnorderedElementsAre( |
433 | AllOf(qName("Foo" ), visibleOutsideFile()), |
434 | AllOf(qName("bar" ), visibleOutsideFile()), |
435 | AllOf(qName("a" ), Not(visibleOutsideFile())), |
436 | AllOf(qName("B" ), Not(visibleOutsideFile())), |
437 | AllOf(qName("c" ), Not(visibleOutsideFile())), |
438 | // FIXME: ForwardDecl likely *is* visible outside. |
439 | AllOf(qName("ForwardDecl" ), Not(visibleOutsideFile())))); |
440 | } |
441 | |
442 | TEST_F(SymbolCollectorTest, Template) { |
443 | Annotations (R"( |
444 | // Primary template and explicit specialization are indexed, instantiation |
445 | // is not. |
446 | template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;}; |
447 | template <> struct $specdecl[[Tmpl]]<int, bool> {}; |
448 | template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {}; |
449 | extern template struct Tmpl<float, bool>; |
450 | template struct Tmpl<double, bool>; |
451 | )" ); |
452 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
453 | EXPECT_THAT(Symbols, |
454 | UnorderedElementsAre( |
455 | AllOf(qName("Tmpl" ), declRange(Header.range()), |
456 | forCodeCompletion(true)), |
457 | AllOf(qName("Tmpl" ), declRange(Header.range("specdecl" )), |
458 | forCodeCompletion(false)), |
459 | AllOf(qName("Tmpl" ), declRange(Header.range("partspecdecl" )), |
460 | forCodeCompletion(false)), |
461 | AllOf(qName("Tmpl::x" ), declRange(Header.range("xdecl" )), |
462 | forCodeCompletion(false)))); |
463 | } |
464 | |
465 | TEST_F(SymbolCollectorTest, templateArgs) { |
466 | Annotations (R"( |
467 | template <class X> class $barclasstemp[[Bar]] {}; |
468 | template <class T, class U, template<typename> class Z, int Q> |
469 | struct [[Tmpl]] { T $xdecl[[x]] = 0; }; |
470 | |
471 | // template-template, non-type and type full spec |
472 | template <> struct $specdecl[[Tmpl]]<int, bool, Bar, 3> {}; |
473 | |
474 | // template-template, non-type and type partial spec |
475 | template <class U, int T> struct $partspecdecl[[Tmpl]]<bool, U, Bar, T> {}; |
476 | // instantiation |
477 | extern template struct Tmpl<float, bool, Bar, 8>; |
478 | // instantiation |
479 | template struct Tmpl<double, bool, Bar, 2>; |
480 | |
481 | template <typename ...> class $fooclasstemp[[Foo]] {}; |
482 | // parameter-packs full spec |
483 | template<> class $parampack[[Foo]]<Bar<int>, int, double> {}; |
484 | // parameter-packs partial spec |
485 | template<class T> class $parampackpartial[[Foo]]<T, T> {}; |
486 | |
487 | template <int ...> class $bazclasstemp[[Baz]] {}; |
488 | // non-type parameter-packs full spec |
489 | template<> class $parampacknontype[[Baz]]<3, 5, 8> {}; |
490 | // non-type parameter-packs partial spec |
491 | template<int T> class $parampacknontypepartial[[Baz]]<T, T> {}; |
492 | |
493 | template <template <class> class ...> class $fozclasstemp[[Foz]] {}; |
494 | // template-template parameter-packs full spec |
495 | template<> class $parampacktempltempl[[Foz]]<Bar, Bar> {}; |
496 | // template-template parameter-packs partial spec |
497 | template<template <class> class T> |
498 | class $parampacktempltemplpartial[[Foz]]<T, T> {}; |
499 | )" ); |
500 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
501 | EXPECT_THAT( |
502 | Symbols, |
503 | AllOf( |
504 | Contains(AllOf(qName("Tmpl" ), templateArgs("<int, bool, Bar, 3>" ), |
505 | declRange(Header.range("specdecl" )), |
506 | forCodeCompletion(false))), |
507 | Contains(AllOf(qName("Tmpl" ), templateArgs("<bool, U, Bar, T>" ), |
508 | declRange(Header.range("partspecdecl" )), |
509 | forCodeCompletion(false))), |
510 | Contains(AllOf(qName("Foo" ), templateArgs("<Bar<int>, int, double>" ), |
511 | declRange(Header.range("parampack" )), |
512 | forCodeCompletion(false))), |
513 | Contains(AllOf(qName("Foo" ), templateArgs("<T, T>" ), |
514 | declRange(Header.range("parampackpartial" )), |
515 | forCodeCompletion(false))), |
516 | Contains(AllOf(qName("Baz" ), templateArgs("<3, 5, 8>" ), |
517 | declRange(Header.range("parampacknontype" )), |
518 | forCodeCompletion(false))), |
519 | Contains(AllOf(qName("Baz" ), templateArgs("<T, T>" ), |
520 | declRange(Header.range("parampacknontypepartial" )), |
521 | forCodeCompletion(false))), |
522 | Contains(AllOf(qName("Foz" ), templateArgs("<Bar, Bar>" ), |
523 | declRange(Header.range("parampacktempltempl" )), |
524 | forCodeCompletion(false))), |
525 | Contains(AllOf(qName("Foz" ), templateArgs("<T, T>" ), |
526 | declRange(Header.range("parampacktempltemplpartial" )), |
527 | forCodeCompletion(false))))); |
528 | } |
529 | |
530 | TEST_F(SymbolCollectorTest, ObjCRefs) { |
531 | Annotations (R"( |
532 | @interface Person |
533 | - (void)$talk[[talk]]; |
534 | - (void)$say[[say]]:(id)something; |
535 | @end |
536 | @interface Person (Category) |
537 | - (void)categoryMethod; |
538 | - (void)multiArg:(id)a method:(id)b; |
539 | @end |
540 | )" ); |
541 | Annotations Main(R"( |
542 | @implementation Person |
543 | - (void)$talk[[talk]] {} |
544 | - (void)$say[[say]]:(id)something {} |
545 | @end |
546 | |
547 | void fff(Person *p) { |
548 | [p $talk[[talk]]]; |
549 | [p $say[[say]]:0]; |
550 | [p categoryMethod]; |
551 | [p multiArg:0 method:0]; |
552 | } |
553 | )" ); |
554 | CollectorOpts.RefFilter = RefKind::All; |
555 | CollectorOpts.CollectMainFileRefs = true; |
556 | TestFileName = testPath(File: "test.m" ); |
557 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
558 | ExtraArgs: {"-fblocks" , "-xobjective-c++" , "-Wno-objc-root-class" }); |
559 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk" ).ID, |
560 | haveRanges(Main.ranges("talk" ))))); |
561 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:" ).ID, |
562 | haveRanges(Main.ranges("say" ))))); |
563 | EXPECT_THAT(Refs, |
564 | Contains(Pair(findSymbol(Symbols, "Person::categoryMethod" ).ID, |
565 | ElementsAre(isSpelled())))); |
566 | EXPECT_THAT(Refs, |
567 | Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:" ).ID, |
568 | ElementsAre(isSpelled())))); |
569 | } |
570 | |
571 | TEST_F(SymbolCollectorTest, ObjCSymbols) { |
572 | const std::string = R"( |
573 | @interface Person |
574 | - (void)someMethodName:(void*)name1 lastName:(void*)lName; |
575 | @end |
576 | |
577 | @implementation Person |
578 | - (void)someMethodName:(void*)name1 lastName:(void*)lName{ |
579 | int foo; |
580 | ^(int param){ int bar; }; |
581 | } |
582 | @end |
583 | |
584 | @interface Person (MyCategory) |
585 | - (void)someMethodName2:(void*)name2; |
586 | @end |
587 | |
588 | @implementation Person (MyCategory) |
589 | - (void)someMethodName2:(void*)name2 { |
590 | int foo2; |
591 | } |
592 | @end |
593 | |
594 | @protocol MyProtocol |
595 | - (void)someMethodName3:(void*)name3; |
596 | @end |
597 | )" ; |
598 | TestFileName = testPath(File: "test.m" ); |
599 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , ExtraArgs: {"-fblocks" , "-xobjective-c++" }); |
600 | EXPECT_THAT(Symbols, |
601 | UnorderedElementsAre( |
602 | qName("Person" ), qName("Person::someMethodName:lastName:" ), |
603 | AllOf(qName("MyCategory" ), forCodeCompletion(false)), |
604 | qName("Person::someMethodName2:" ), qName("MyProtocol" ), |
605 | qName("MyProtocol::someMethodName3:" ))); |
606 | } |
607 | |
608 | TEST_F(SymbolCollectorTest, ObjCPropertyImpl) { |
609 | const std::string = R"( |
610 | @interface Container |
611 | @property(nonatomic) int magic; |
612 | @end |
613 | |
614 | @implementation Container |
615 | @end |
616 | )" ; |
617 | TestFileName = testPath(File: "test.m" ); |
618 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , ExtraArgs: {"-xobjective-c++" }); |
619 | EXPECT_THAT(Symbols, Contains(qName("Container" ))); |
620 | EXPECT_THAT(Symbols, Contains(qName("Container::magic" ))); |
621 | // FIXME: Results also contain Container::_magic on some platforms. |
622 | // Figure out why it's platform-dependent. |
623 | } |
624 | |
625 | TEST_F(SymbolCollectorTest, ObjCLocations) { |
626 | Annotations (R"( |
627 | // Declared in header, defined in main. |
628 | @interface $dogdecl[[Dog]] |
629 | @end |
630 | @interface $fluffydecl[[Dog]] (Fluffy) |
631 | @end |
632 | )" ); |
633 | Annotations Main(R"( |
634 | @interface Dog () |
635 | @end |
636 | @implementation $dogdef[[Dog]] |
637 | @end |
638 | @implementation $fluffydef[[Dog]] (Fluffy) |
639 | @end |
640 | // Category with no declaration (only implementation). |
641 | @implementation $ruff[[Dog]] (Ruff) |
642 | @end |
643 | // Implicitly defined interface. |
644 | @implementation $catdog[[CatDog]] |
645 | @end |
646 | )" ); |
647 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
648 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
649 | EXPECT_THAT(Symbols, |
650 | UnorderedElementsAre( |
651 | AllOf(qName("Dog" ), declRange(Header.range("dogdecl" )), |
652 | defRange(Main.range("dogdef" ))), |
653 | AllOf(qName("Fluffy" ), declRange(Header.range("fluffydecl" )), |
654 | defRange(Main.range("fluffydef" ))), |
655 | AllOf(qName("CatDog" ), declRange(Main.range("catdog" )), |
656 | defRange(Main.range("catdog" ))), |
657 | AllOf(qName("Ruff" ), declRange(Main.range("ruff" )), |
658 | defRange(Main.range("ruff" ))))); |
659 | } |
660 | |
661 | TEST_F(SymbolCollectorTest, ObjCForwardDecls) { |
662 | Annotations (R"( |
663 | // Forward declared in header, declared and defined in main. |
664 | @protocol Barker; |
665 | @class Dog; |
666 | // Never fully declared so Clang latches onto this decl. |
667 | @class $catdogdecl[[CatDog]]; |
668 | )" ); |
669 | Annotations Main(R"( |
670 | @protocol $barkerdecl[[Barker]] |
671 | - (void)woof; |
672 | @end |
673 | @interface $dogdecl[[Dog]]<Barker> |
674 | - (void)woof; |
675 | @end |
676 | @implementation $dogdef[[Dog]] |
677 | - (void)woof {} |
678 | @end |
679 | @implementation $catdogdef[[CatDog]] |
680 | @end |
681 | )" ); |
682 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
683 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
684 | EXPECT_THAT(Symbols, |
685 | UnorderedElementsAre( |
686 | AllOf(qName("CatDog" ), declRange(Header.range("catdogdecl" )), |
687 | defRange(Main.range("catdogdef" ))), |
688 | AllOf(qName("Dog" ), declRange(Main.range("dogdecl" )), |
689 | defRange(Main.range("dogdef" ))), |
690 | AllOf(qName("Barker" ), declRange(Main.range("barkerdecl" ))), |
691 | qName("Barker::woof" ), qName("Dog::woof" ))); |
692 | } |
693 | |
694 | TEST_F(SymbolCollectorTest, ObjCClassExtensions) { |
695 | Annotations (R"( |
696 | @interface $catdecl[[Cat]] |
697 | @end |
698 | )" ); |
699 | Annotations Main(R"( |
700 | @interface Cat () |
701 | - (void)meow; |
702 | @end |
703 | @interface Cat () |
704 | - (void)pur; |
705 | @end |
706 | )" ); |
707 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code(), |
708 | ExtraArgs: {"-xobjective-c++" , "-Wno-objc-root-class" }); |
709 | EXPECT_THAT(Symbols, |
710 | UnorderedElementsAre( |
711 | AllOf(qName("Cat" ), declRange(Header.range("catdecl" ))), |
712 | qName("Cat::meow" ), qName("Cat::pur" ))); |
713 | } |
714 | |
715 | TEST_F(SymbolCollectorTest, ObjCFrameworkIncludeHeader) { |
716 | CollectorOpts.CollectIncludePath = true; |
717 | auto FrameworksPath = testPath(File: "Frameworks/" ); |
718 | std::string = R"( |
719 | __attribute((objc_root_class)) |
720 | @interface NSObject |
721 | @end |
722 | )" ; |
723 | InMemoryFileSystem->addFile( |
724 | Path: testPath(File: "Frameworks/Foundation.framework/Headers/NSObject.h" ), ModificationTime: 0, |
725 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: FrameworkHeader)); |
726 | std::string = R"( |
727 | #import <Foundation/NSObject.h> |
728 | |
729 | @interface PrivateClass : NSObject |
730 | @end |
731 | )" ; |
732 | InMemoryFileSystem->addFile( |
733 | Path: testPath( |
734 | File: "Frameworks/Foundation.framework/PrivateHeaders/NSObject+Private.h" ), |
735 | ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: PrivateFrameworkHeader)); |
736 | |
737 | std::string = R"( |
738 | #import <Foundation/NSObject+Private.h> |
739 | #import <Foundation/NSObject.h> |
740 | |
741 | @interface Container : NSObject |
742 | @end |
743 | )" ; |
744 | std::string Main = "" ; |
745 | TestFileName = testPath(File: "test.m" ); |
746 | runSymbolCollector(HeaderCode: Header, MainCode: Main, ExtraArgs: {"-F" , FrameworksPath, "-xobjective-c++" }); |
747 | EXPECT_THAT( |
748 | Symbols, |
749 | UnorderedElementsAre( |
750 | AllOf(qName("NSObject" ), includeHeader("<Foundation/NSObject.h>" )), |
751 | AllOf(qName("PrivateClass" ), |
752 | includeHeader("<Foundation/NSObject+Private.h>" )), |
753 | AllOf(qName("Container" )))); |
754 | |
755 | // After adding the umbrella headers, we should use that spelling instead. |
756 | std::string = R"( |
757 | #import <Foundation/NSObject.h> |
758 | )" ; |
759 | InMemoryFileSystem->addFile( |
760 | Path: testPath(File: "Frameworks/Foundation.framework/Headers/Foundation.h" ), ModificationTime: 0, |
761 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: UmbrellaHeader)); |
762 | std::string = R"( |
763 | #import <Foundation/NSObject+Private.h> |
764 | )" ; |
765 | InMemoryFileSystem->addFile( |
766 | Path: testPath(File: "Frameworks/Foundation.framework/PrivateHeaders/" |
767 | "Foundation_Private.h" ), |
768 | ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: PrivateUmbrellaHeader)); |
769 | runSymbolCollector(HeaderCode: Header, MainCode: Main, ExtraArgs: {"-F" , FrameworksPath, "-xobjective-c++" }); |
770 | EXPECT_THAT( |
771 | Symbols, |
772 | UnorderedElementsAre( |
773 | AllOf(qName("NSObject" ), includeHeader("<Foundation/Foundation.h>" )), |
774 | AllOf(qName("PrivateClass" ), |
775 | includeHeader("<Foundation/Foundation_Private.h>" )), |
776 | AllOf(qName("Container" )))); |
777 | |
778 | runSymbolCollector(HeaderCode: Header, MainCode: Main, |
779 | ExtraArgs: {"-iframework" , FrameworksPath, "-xobjective-c++" }); |
780 | EXPECT_THAT( |
781 | Symbols, |
782 | UnorderedElementsAre( |
783 | AllOf(qName("NSObject" ), includeHeader("<Foundation/Foundation.h>" )), |
784 | AllOf(qName("PrivateClass" ), |
785 | includeHeader("<Foundation/Foundation_Private.h>" )), |
786 | AllOf(qName("Container" )))); |
787 | } |
788 | |
789 | TEST_F(SymbolCollectorTest, Locations) { |
790 | Annotations (R"cpp( |
791 | // Declared in header, defined in main. |
792 | extern int $xdecl[[X]]; |
793 | class $clsdecl[[Cls]]; |
794 | void $printdecl[[print]](); |
795 | |
796 | // Declared in header, defined nowhere. |
797 | extern int $zdecl[[Z]]; |
798 | |
799 | void $foodecl[[fo\ |
800 | o]](); |
801 | )cpp" ); |
802 | Annotations Main(R"cpp( |
803 | int $xdef[[X]] = 42; |
804 | class $clsdef[[Cls]] {}; |
805 | void $printdef[[print]]() {} |
806 | |
807 | // Declared/defined in main only. |
808 | int $ydecl[[Y]]; |
809 | )cpp" ); |
810 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
811 | EXPECT_THAT(Symbols, |
812 | UnorderedElementsAre( |
813 | AllOf(qName("X" ), declRange(Header.range("xdecl" )), |
814 | defRange(Main.range("xdef" ))), |
815 | AllOf(qName("Cls" ), declRange(Header.range("clsdecl" )), |
816 | defRange(Main.range("clsdef" ))), |
817 | AllOf(qName("print" ), declRange(Header.range("printdecl" )), |
818 | defRange(Main.range("printdef" ))), |
819 | AllOf(qName("Z" ), declRange(Header.range("zdecl" ))), |
820 | AllOf(qName("foo" ), declRange(Header.range("foodecl" ))), |
821 | AllOf(qName("Y" ), declRange(Main.range("ydecl" ))))); |
822 | } |
823 | |
824 | TEST_F(SymbolCollectorTest, Refs) { |
825 | Annotations (R"( |
826 | #define MACRO(X) (X + 1) |
827 | class Foo { |
828 | public: |
829 | Foo() {} |
830 | Foo(int); |
831 | }; |
832 | class Bar; |
833 | void func(); |
834 | |
835 | namespace NS {} // namespace ref is ignored |
836 | )" ); |
837 | Annotations Main(R"( |
838 | class $bar[[Bar]] {}; |
839 | |
840 | void $func[[func]](); |
841 | |
842 | void fff() { |
843 | $foo[[Foo]] foo; |
844 | $bar[[Bar]] bar; |
845 | $func[[func]](); |
846 | int abc = 0; |
847 | $foo[[Foo]] foo2 = abc; |
848 | abc = $macro[[MACRO]](1); |
849 | } |
850 | )" ); |
851 | Annotations SymbolsOnlyInMainCode(R"( |
852 | #define FUNC(X) (X+1) |
853 | int a; |
854 | void b() {} |
855 | static const int c = FUNC(1); |
856 | class d {}; |
857 | )" ); |
858 | CollectorOpts.RefFilter = RefKind::All; |
859 | CollectorOpts.CollectMacro = true; |
860 | runSymbolCollector(HeaderCode: Header.code(), |
861 | MainCode: (Main.code() + SymbolsOnlyInMainCode.code()).str()); |
862 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
863 | haveRanges(Main.ranges("foo" ))))); |
864 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Bar" ).ID, |
865 | haveRanges(Main.ranges("bar" ))))); |
866 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "func" ).ID, |
867 | haveRanges(Main.ranges("func" ))))); |
868 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "NS" ).ID, _)))); |
869 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO" ).ID, |
870 | haveRanges(Main.ranges("macro" ))))); |
871 | // - (a, b) externally visible and should have refs. |
872 | // - (c, FUNC) externally invisible and had no refs collected. |
873 | auto MainSymbols = |
874 | TestTU::withHeaderCode(HeaderCode: SymbolsOnlyInMainCode.code()).headerSymbols(); |
875 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "a" ).ID, _))); |
876 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "b" ).ID, _))); |
877 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "c" ).ID, _)))); |
878 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "FUNC" ).ID, _)))); |
879 | |
880 | // Run the collector again with CollectMainFileRefs = true. |
881 | // We need to recreate InMemoryFileSystem because runSymbolCollector() |
882 | // calls MemoryBuffer::getMemBuffer(), which makes the buffers unusable |
883 | // after runSymbolCollector() exits. |
884 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem(); |
885 | CollectorOpts.CollectMainFileRefs = true; |
886 | runSymbolCollector(HeaderCode: Header.code(), |
887 | MainCode: (Main.code() + SymbolsOnlyInMainCode.code()).str()); |
888 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "a" ).ID, _))); |
889 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "b" ).ID, _))); |
890 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "c" ).ID, _))); |
891 | // However, references to main-file macros are not collected. |
892 | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "FUNC" ).ID, _)))); |
893 | } |
894 | |
895 | TEST_F(SymbolCollectorTest, RefContainers) { |
896 | Annotations Code(R"cpp( |
897 | int $toplevel1[[f1]](int); |
898 | void f2() { |
899 | (void) $ref1a[[f1]](1); |
900 | auto fptr = &$ref1b[[f1]]; |
901 | } |
902 | int $toplevel2[[v1]] = $ref2[[f1]](2); |
903 | void f3(int arg = $ref3[[f1]](3)); |
904 | struct S1 { |
905 | int $classscope1[[member1]] = $ref4[[f1]](4); |
906 | int $classscope2[[member2]] = 42; |
907 | }; |
908 | constexpr int f4(int x) { return x + 1; } |
909 | template <int I = $ref5[[f4]](0)> struct S2 {}; |
910 | S2<$ref6[[f4]](0)> v2; |
911 | S2<$ref7a[[f4]](0)> f5(S2<$ref7b[[f4]](0)>); |
912 | namespace N { |
913 | void $namespacescope1[[f6]](); |
914 | int $namespacescope2[[v3]]; |
915 | } |
916 | )cpp" ); |
917 | CollectorOpts.RefFilter = RefKind::All; |
918 | CollectorOpts.CollectMainFileRefs = true; |
919 | runSymbolCollector(HeaderCode: "" , MainCode: Code.code()); |
920 | auto FindRefWithRange = [&](Range R) -> std::optional<Ref> { |
921 | for (auto &Entry : Refs) { |
922 | for (auto &Ref : Entry.second) { |
923 | if (rangesMatch(Loc: Ref.Location, R)) |
924 | return Ref; |
925 | } |
926 | } |
927 | return std::nullopt; |
928 | }; |
929 | auto Container = [&](llvm::StringRef RangeName) { |
930 | auto Ref = FindRefWithRange(Code.range(Name: RangeName)); |
931 | EXPECT_TRUE(bool(Ref)); |
932 | return Ref->Container; |
933 | }; |
934 | EXPECT_EQ(Container("ref1a" ), |
935 | findSymbol(Symbols, "f2" ).ID); // function body (call) |
936 | EXPECT_EQ(Container("ref1b" ), |
937 | findSymbol(Symbols, "f2" ).ID); // function body (address-of) |
938 | EXPECT_EQ(Container("ref2" ), |
939 | findSymbol(Symbols, "v1" ).ID); // variable initializer |
940 | EXPECT_EQ(Container("ref3" ), |
941 | findSymbol(Symbols, "f3" ).ID); // function parameter default value |
942 | EXPECT_EQ(Container("ref4" ), |
943 | findSymbol(Symbols, "S1::member1" ).ID); // member initializer |
944 | EXPECT_EQ(Container("ref5" ), |
945 | findSymbol(Symbols, "S2" ).ID); // template parameter default value |
946 | EXPECT_EQ(Container("ref6" ), |
947 | findSymbol(Symbols, "v2" ).ID); // type of variable |
948 | EXPECT_EQ(Container("ref7a" ), |
949 | findSymbol(Symbols, "f5" ).ID); // return type of function |
950 | EXPECT_EQ(Container("ref7b" ), |
951 | findSymbol(Symbols, "f5" ).ID); // parameter type of function |
952 | |
953 | EXPECT_FALSE(Container("classscope1" ).isNull()); |
954 | EXPECT_FALSE(Container("namespacescope1" ).isNull()); |
955 | |
956 | EXPECT_EQ(Container("toplevel1" ), Container("toplevel2" )); |
957 | EXPECT_EQ(Container("classscope1" ), Container("classscope2" )); |
958 | EXPECT_EQ(Container("namespacescope1" ), Container("namespacescope2" )); |
959 | |
960 | EXPECT_NE(Container("toplevel1" ), Container("namespacescope1" )); |
961 | EXPECT_NE(Container("toplevel1" ), Container("classscope1" )); |
962 | EXPECT_NE(Container("classscope1" ), Container("namespacescope1" )); |
963 | } |
964 | |
965 | TEST_F(SymbolCollectorTest, MacroRefInHeader) { |
966 | Annotations (R"( |
967 | #define $foo[[FOO]](X) (X + 1) |
968 | #define $bar[[BAR]](X) (X + 2) |
969 | |
970 | // Macro defined multiple times. |
971 | #define $ud1[[UD]] 1 |
972 | int ud_1 = $ud1[[UD]]; |
973 | #undef UD |
974 | |
975 | #define $ud2[[UD]] 2 |
976 | int ud_2 = $ud2[[UD]]; |
977 | #undef UD |
978 | |
979 | // Macros from token concatenations not included. |
980 | #define $concat[[CONCAT]](X) X##A() |
981 | #define $prepend[[PREPEND]](X) MACRO##X() |
982 | #define $macroa[[MACROA]]() 123 |
983 | int B = $concat[[CONCAT]](MACRO); |
984 | int D = $prepend[[PREPEND]](A); |
985 | |
986 | void fff() { |
987 | int abc = $foo[[FOO]](1) + $bar[[BAR]]($foo[[FOO]](1)); |
988 | } |
989 | )" ); |
990 | CollectorOpts.RefFilter = RefKind::All; |
991 | CollectorOpts.RefsInHeaders = true; |
992 | // Need this to get the SymbolID for macros for tests. |
993 | CollectorOpts.CollectMacro = true; |
994 | |
995 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
996 | |
997 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "FOO" ).ID, |
998 | haveRanges(Header.ranges("foo" ))))); |
999 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "BAR" ).ID, |
1000 | haveRanges(Header.ranges("bar" ))))); |
1001 | // No unique ID for multiple symbols named UD. Check for ranges only. |
1002 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud1" ))))); |
1003 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud2" ))))); |
1004 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "CONCAT" ).ID, |
1005 | haveRanges(Header.ranges("concat" ))))); |
1006 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PREPEND" ).ID, |
1007 | haveRanges(Header.ranges("prepend" ))))); |
1008 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACROA" ).ID, |
1009 | haveRanges(Header.ranges("macroa" ))))); |
1010 | } |
1011 | |
1012 | TEST_F(SymbolCollectorTest, MacroRefWithoutCollectingSymbol) { |
1013 | Annotations (R"( |
1014 | #define $foo[[FOO]](X) (X + 1) |
1015 | int abc = $foo[[FOO]](1); |
1016 | )" ); |
1017 | CollectorOpts.RefFilter = RefKind::All; |
1018 | CollectorOpts.RefsInHeaders = true; |
1019 | CollectorOpts.CollectMacro = false; |
1020 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
1021 | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("foo" ))))); |
1022 | } |
1023 | |
1024 | TEST_F(SymbolCollectorTest, MacrosWithRefFilter) { |
1025 | Annotations ("#define $macro[[MACRO]](X) (X + 1)" ); |
1026 | Annotations Main("void foo() { int x = $macro[[MACRO]](1); }" ); |
1027 | CollectorOpts.RefFilter = RefKind::Unknown; |
1028 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
1029 | EXPECT_THAT(Refs, IsEmpty()); |
1030 | } |
1031 | |
1032 | TEST_F(SymbolCollectorTest, SpelledReferences) { |
1033 | struct { |
1034 | llvm::StringRef ; |
1035 | llvm::StringRef Main; |
1036 | llvm::StringRef TargetSymbolName; |
1037 | } TestCases[] = { |
1038 | { |
1039 | .Header: R"cpp( |
1040 | struct Foo; |
1041 | #define MACRO Foo |
1042 | )cpp" , |
1043 | .Main: R"cpp( |
1044 | struct $spelled[[Foo]] { |
1045 | $spelled[[Foo]](); |
1046 | ~$spelled[[Foo]](); |
1047 | }; |
1048 | $spelled[[Foo]] Variable1; |
1049 | $implicit[[MACRO]] Variable2; |
1050 | )cpp" , |
1051 | .TargetSymbolName: "Foo" , |
1052 | }, |
1053 | { |
1054 | .Header: R"cpp( |
1055 | class Foo { |
1056 | public: |
1057 | Foo() = default; |
1058 | }; |
1059 | )cpp" , |
1060 | .Main: R"cpp( |
1061 | void f() { Foo $implicit[[f]]; f = $spelled[[Foo]]();} |
1062 | )cpp" , |
1063 | .TargetSymbolName: "Foo::Foo" /// constructor. |
1064 | }, |
1065 | { // Unclean identifiers |
1066 | .Header: R"cpp( |
1067 | struct Foo {}; |
1068 | )cpp" , |
1069 | .Main: R"cpp( |
1070 | $spelled[[Fo\ |
1071 | o]] f{}; |
1072 | )cpp" , |
1073 | .TargetSymbolName: "Foo" , |
1074 | }, |
1075 | }; |
1076 | CollectorOpts.RefFilter = RefKind::All; |
1077 | CollectorOpts.RefsInHeaders = false; |
1078 | for (const auto& T : TestCases) { |
1079 | SCOPED_TRACE(T.Header + "\n---\n" + T.Main); |
1080 | Annotations (T.Header); |
1081 | Annotations Main(T.Main); |
1082 | // Reset the file system. |
1083 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
1084 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
1085 | |
1086 | const auto SpelledRanges = Main.ranges(Name: "spelled" ); |
1087 | const auto ImplicitRanges = Main.ranges(Name: "implicit" ); |
1088 | RefSlab::Builder SpelledSlabBuilder, ImplicitSlabBuilder; |
1089 | const auto TargetID = findSymbol(Symbols, QName: T.TargetSymbolName).ID; |
1090 | for (const auto &SymbolAndRefs : Refs) { |
1091 | const auto ID = SymbolAndRefs.first; |
1092 | if (ID != TargetID) |
1093 | continue; |
1094 | for (const auto &Ref : SymbolAndRefs.second) |
1095 | if ((Ref.Kind & RefKind::Spelled) != RefKind::Unknown) |
1096 | SpelledSlabBuilder.insert(ID, S: Ref); |
1097 | else |
1098 | ImplicitSlabBuilder.insert(ID, S: Ref); |
1099 | } |
1100 | const auto SpelledRefs = std::move(SpelledSlabBuilder).build(), |
1101 | ImplicitRefs = std::move(ImplicitSlabBuilder).build(); |
1102 | EXPECT_EQ(SpelledRanges.empty(), SpelledRefs.empty()); |
1103 | EXPECT_EQ(ImplicitRanges.empty(), ImplicitRefs.empty()); |
1104 | if (!SpelledRanges.empty()) |
1105 | EXPECT_THAT(SpelledRefs, |
1106 | Contains(Pair(TargetID, haveRanges(SpelledRanges)))); |
1107 | if (!ImplicitRanges.empty()) |
1108 | EXPECT_THAT(ImplicitRefs, |
1109 | Contains(Pair(TargetID, haveRanges(ImplicitRanges)))); |
1110 | } |
1111 | } |
1112 | |
1113 | TEST_F(SymbolCollectorTest, NameReferences) { |
1114 | CollectorOpts.RefFilter = RefKind::All; |
1115 | CollectorOpts.RefsInHeaders = true; |
1116 | Annotations (R"( |
1117 | class [[Foo]] { |
1118 | public: |
1119 | [[Foo]]() {} |
1120 | ~[[Foo]]() {} |
1121 | }; |
1122 | )" ); |
1123 | CollectorOpts.RefFilter = RefKind::All; |
1124 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
1125 | // When we find references for class Foo, we expect to see all |
1126 | // constructor/destructor references. |
1127 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
1128 | haveRanges(Header.ranges())))); |
1129 | } |
1130 | |
1131 | TEST_F(SymbolCollectorTest, RefsOnMacros) { |
1132 | // Refs collected from SymbolCollector behave in the same way as |
1133 | // AST-based xrefs. |
1134 | CollectorOpts.RefFilter = RefKind::All; |
1135 | CollectorOpts.RefsInHeaders = true; |
1136 | Annotations (R"( |
1137 | #define TYPE(X) X |
1138 | #define FOO Foo |
1139 | #define CAT(X, Y) X##Y |
1140 | class [[Foo]] {}; |
1141 | void test() { |
1142 | TYPE([[Foo]]) foo; |
1143 | [[FOO]] foo2; |
1144 | TYPE(TYPE([[Foo]])) foo3; |
1145 | [[CAT]](Fo, o) foo4; |
1146 | } |
1147 | )" ); |
1148 | CollectorOpts.RefFilter = RefKind::All; |
1149 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
1150 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
1151 | haveRanges(Header.ranges())))); |
1152 | } |
1153 | |
1154 | TEST_F(SymbolCollectorTest, HeaderAsMainFile) { |
1155 | CollectorOpts.RefFilter = RefKind::All; |
1156 | Annotations (R"( |
1157 | class $Foo[[Foo]] {}; |
1158 | |
1159 | void $Func[[Func]]() { |
1160 | $Foo[[Foo]] fo; |
1161 | } |
1162 | )" ); |
1163 | // We should collect refs to main-file symbols in all cases: |
1164 | |
1165 | // 1. The main file is normal .cpp file. |
1166 | TestFileName = testPath(File: "foo.cpp" ); |
1167 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code()); |
1168 | EXPECT_THAT(Refs, |
1169 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
1170 | haveRanges(Header.ranges("Foo" ))), |
1171 | Pair(findSymbol(Symbols, "Func" ).ID, |
1172 | haveRanges(Header.ranges("Func" ))))); |
1173 | |
1174 | // 2. Run the .h file as main file. |
1175 | TestFileName = testPath(File: "foo.h" ); |
1176 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code(), |
1177 | /*ExtraArgs=*/{"-xobjective-c++-header" }); |
1178 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), qName("Func" ))); |
1179 | EXPECT_THAT(Refs, |
1180 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
1181 | haveRanges(Header.ranges("Foo" ))), |
1182 | Pair(findSymbol(Symbols, "Func" ).ID, |
1183 | haveRanges(Header.ranges("Func" ))))); |
1184 | |
1185 | // 3. Run the .hh file as main file (without "-x c++-header"). |
1186 | TestFileName = testPath(File: "foo.hh" ); |
1187 | runSymbolCollector(HeaderCode: "" , MainCode: Header.code()); |
1188 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), qName("Func" ))); |
1189 | EXPECT_THAT(Refs, |
1190 | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo" ).ID, |
1191 | haveRanges(Header.ranges("Foo" ))), |
1192 | Pair(findSymbol(Symbols, "Func" ).ID, |
1193 | haveRanges(Header.ranges("Func" ))))); |
1194 | } |
1195 | |
1196 | TEST_F(SymbolCollectorTest, RefsInHeaders) { |
1197 | CollectorOpts.RefFilter = RefKind::All; |
1198 | CollectorOpts.RefsInHeaders = true; |
1199 | CollectorOpts.CollectMacro = true; |
1200 | Annotations (R"( |
1201 | #define $macro[[MACRO]](x) (x+1) |
1202 | class $foo[[Foo]] {}; |
1203 | )" ); |
1204 | runSymbolCollector(HeaderCode: Header.code(), MainCode: "" ); |
1205 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo" ).ID, |
1206 | haveRanges(Header.ranges("foo" ))))); |
1207 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO" ).ID, |
1208 | haveRanges(Header.ranges("macro" ))))); |
1209 | } |
1210 | |
1211 | TEST_F(SymbolCollectorTest, BaseOfRelations) { |
1212 | std::string = R"( |
1213 | class Base {}; |
1214 | class Derived : public Base {}; |
1215 | )" ; |
1216 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1217 | const Symbol &Base = findSymbol(Symbols, QName: "Base" ); |
1218 | const Symbol &Derived = findSymbol(Symbols, QName: "Derived" ); |
1219 | EXPECT_THAT(Relations, |
1220 | Contains(Relation{Base.ID, RelationKind::BaseOf, Derived.ID})); |
1221 | } |
1222 | |
1223 | TEST_F(SymbolCollectorTest, OverrideRelationsSimpleInheritance) { |
1224 | std::string = R"cpp( |
1225 | class A { |
1226 | virtual void foo(); |
1227 | }; |
1228 | class B : public A { |
1229 | void foo() override; // A::foo |
1230 | virtual void bar(); |
1231 | }; |
1232 | class C : public B { |
1233 | void bar() override; // B::bar |
1234 | }; |
1235 | class D: public C { |
1236 | void foo() override; // B::foo |
1237 | void bar() override; // C::bar |
1238 | }; |
1239 | )cpp" ; |
1240 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1241 | const Symbol &AFoo = findSymbol(Symbols, QName: "A::foo" ); |
1242 | const Symbol &BFoo = findSymbol(Symbols, QName: "B::foo" ); |
1243 | const Symbol &DFoo = findSymbol(Symbols, QName: "D::foo" ); |
1244 | |
1245 | const Symbol &BBar = findSymbol(Symbols, QName: "B::bar" ); |
1246 | const Symbol &CBar = findSymbol(Symbols, QName: "C::bar" ); |
1247 | const Symbol &DBar = findSymbol(Symbols, QName: "D::bar" ); |
1248 | |
1249 | std::vector<Relation> Result; |
1250 | for (const Relation &R : Relations) |
1251 | if (R.Predicate == RelationKind::OverriddenBy) |
1252 | Result.push_back(x: R); |
1253 | EXPECT_THAT(Result, UnorderedElementsAre( |
1254 | OverriddenBy(AFoo, BFoo), OverriddenBy(BBar, CBar), |
1255 | OverriddenBy(BFoo, DFoo), OverriddenBy(CBar, DBar))); |
1256 | } |
1257 | |
1258 | TEST_F(SymbolCollectorTest, OverrideRelationsMultipleInheritance) { |
1259 | std::string = R"cpp( |
1260 | class A { |
1261 | virtual void foo(); |
1262 | }; |
1263 | class B { |
1264 | virtual void bar(); |
1265 | }; |
1266 | class C : public B { |
1267 | void bar() override; // B::bar |
1268 | virtual void baz(); |
1269 | } |
1270 | class D : public A, C { |
1271 | void foo() override; // A::foo |
1272 | void bar() override; // C::bar |
1273 | void baz() override; // C::baz |
1274 | }; |
1275 | )cpp" ; |
1276 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1277 | const Symbol &AFoo = findSymbol(Symbols, QName: "A::foo" ); |
1278 | const Symbol &BBar = findSymbol(Symbols, QName: "B::bar" ); |
1279 | const Symbol &CBar = findSymbol(Symbols, QName: "C::bar" ); |
1280 | const Symbol &CBaz = findSymbol(Symbols, QName: "C::baz" ); |
1281 | const Symbol &DFoo = findSymbol(Symbols, QName: "D::foo" ); |
1282 | const Symbol &DBar = findSymbol(Symbols, QName: "D::bar" ); |
1283 | const Symbol &DBaz = findSymbol(Symbols, QName: "D::baz" ); |
1284 | |
1285 | std::vector<Relation> Result; |
1286 | for (const Relation &R : Relations) |
1287 | if (R.Predicate == RelationKind::OverriddenBy) |
1288 | Result.push_back(x: R); |
1289 | EXPECT_THAT(Result, UnorderedElementsAre( |
1290 | OverriddenBy(BBar, CBar), OverriddenBy(AFoo, DFoo), |
1291 | OverriddenBy(CBar, DBar), OverriddenBy(CBaz, DBaz))); |
1292 | } |
1293 | |
1294 | TEST_F(SymbolCollectorTest, CountReferences) { |
1295 | const std::string = R"( |
1296 | class W; |
1297 | class X {}; |
1298 | class Y; |
1299 | class Z {}; // not used anywhere |
1300 | Y* y = nullptr; // used in header doesn't count |
1301 | #define GLOBAL_Z(name) Z name; |
1302 | )" ; |
1303 | const std::string Main = R"( |
1304 | W* w = nullptr; |
1305 | W* w2 = nullptr; // only one usage counts |
1306 | X x(); |
1307 | class V; |
1308 | class Y{}; // definition doesn't count as a reference |
1309 | V* v = nullptr; |
1310 | GLOBAL_Z(z); // Not a reference to Z, we don't spell the type. |
1311 | )" ; |
1312 | CollectorOpts.CountReferences = true; |
1313 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
1314 | EXPECT_THAT( |
1315 | Symbols, |
1316 | UnorderedElementsAreArray( |
1317 | {AllOf(qName("W" ), refCount(1)), AllOf(qName("X" ), refCount(1)), |
1318 | AllOf(qName("Y" ), refCount(0)), AllOf(qName("Z" ), refCount(0)), |
1319 | AllOf(qName("y" ), refCount(0)), AllOf(qName("z" ), refCount(0)), |
1320 | AllOf(qName("x" ), refCount(0)), AllOf(qName("w" ), refCount(0)), |
1321 | AllOf(qName("w2" ), refCount(0)), AllOf(qName("V" ), refCount(1)), |
1322 | AllOf(qName("v" ), refCount(0))})); |
1323 | } |
1324 | |
1325 | TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { |
1326 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
1327 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1328 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
1329 | } |
1330 | |
1331 | TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { |
1332 | TestHeaderName = "x.h" ; |
1333 | TestFileName = "x.cpp" ; |
1334 | TestHeaderURI = URI::create(AbsolutePath: testPath(File: TestHeaderName)).toString(); |
1335 | CollectorOpts.FallbackDir = testRoot(); |
1336 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
1337 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1338 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
1339 | } |
1340 | |
1341 | TEST_F(SymbolCollectorTest, UnittestURIScheme) { |
1342 | // Use test URI scheme from URITests.cpp |
1343 | TestHeaderName = testPath(File: "x.h" ); |
1344 | TestFileName = testPath(File: "x.cpp" ); |
1345 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
1346 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1347 | AllOf(qName("Foo" ), declURI("unittest:///x.h" )))); |
1348 | } |
1349 | |
1350 | TEST_F(SymbolCollectorTest, IncludeEnums) { |
1351 | const std::string = R"( |
1352 | enum { |
1353 | Red |
1354 | }; |
1355 | enum Color { |
1356 | Green |
1357 | }; |
1358 | enum class Color2 { |
1359 | Yellow |
1360 | }; |
1361 | namespace ns { |
1362 | enum { |
1363 | Black |
1364 | }; |
1365 | } |
1366 | class Color3 { |
1367 | enum { |
1368 | Blue |
1369 | }; |
1370 | }; |
1371 | )" ; |
1372 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1373 | EXPECT_THAT(Symbols, |
1374 | UnorderedElementsAre( |
1375 | AllOf(qName("Red" ), forCodeCompletion(true)), |
1376 | AllOf(qName("Color" ), forCodeCompletion(true)), |
1377 | AllOf(qName("Green" ), forCodeCompletion(true)), |
1378 | AllOf(qName("Color2" ), forCodeCompletion(true)), |
1379 | AllOf(qName("Color2::Yellow" ), forCodeCompletion(true)), |
1380 | AllOf(qName("ns" ), forCodeCompletion(true)), |
1381 | AllOf(qName("ns::Black" ), forCodeCompletion(true)), |
1382 | AllOf(qName("Color3" ), forCodeCompletion(true)), |
1383 | AllOf(qName("Color3::Blue" ), forCodeCompletion(true)))); |
1384 | } |
1385 | |
1386 | TEST_F(SymbolCollectorTest, NamelessSymbols) { |
1387 | const std::string = R"( |
1388 | struct { |
1389 | int a; |
1390 | } Foo; |
1391 | )" ; |
1392 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1393 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo" ), |
1394 | qName("(anonymous struct)::a" ))); |
1395 | } |
1396 | |
1397 | TEST_F(SymbolCollectorTest, SymbolFormedFromRegisteredSchemeFromMacro) { |
1398 | |
1399 | Annotations (R"( |
1400 | #define FF(name) \ |
1401 | class name##_Test {}; |
1402 | |
1403 | $expansion[[FF]](abc); |
1404 | |
1405 | #define FF2() \ |
1406 | class $spelling[[Test]] {}; |
1407 | |
1408 | FF2(); |
1409 | )" ); |
1410 | |
1411 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
1412 | EXPECT_THAT(Symbols, |
1413 | UnorderedElementsAre( |
1414 | AllOf(qName("abc_Test" ), declRange(Header.range("expansion" )), |
1415 | declURI(TestHeaderURI)), |
1416 | AllOf(qName("Test" ), declRange(Header.range("spelling" )), |
1417 | declURI(TestHeaderURI)))); |
1418 | } |
1419 | |
1420 | TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { |
1421 | Annotations (R"( |
1422 | #ifdef NAME |
1423 | class $expansion[[NAME]] {}; |
1424 | #endif |
1425 | )" ); |
1426 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" , /*ExtraArgs=*/{"-DNAME=name" }); |
1427 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
1428 | qName("name" ), declRange(Header.range("expansion" )), |
1429 | declURI(TestHeaderURI)))); |
1430 | } |
1431 | |
1432 | TEST_F(SymbolCollectorTest, SymbolsInMainFile) { |
1433 | const std::string Main = R"( |
1434 | class Foo {}; |
1435 | void f1(); |
1436 | inline void f2() {} |
1437 | |
1438 | namespace { |
1439 | void ff() {} |
1440 | } |
1441 | namespace foo { |
1442 | namespace { |
1443 | class Bar {}; |
1444 | } |
1445 | } |
1446 | void main_f() {} |
1447 | void f1() {} |
1448 | )" ; |
1449 | runSymbolCollector(/*Header=*/HeaderCode: "" , MainCode: Main); |
1450 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1451 | qName("Foo" ), qName("f1" ), qName("f2" ), qName("ff" ), |
1452 | qName("foo" ), qName("foo::Bar" ), qName("main_f" ))); |
1453 | } |
1454 | |
1455 | TEST_F(SymbolCollectorTest, Documentation) { |
1456 | const std::string = R"( |
1457 | // doc Foo |
1458 | class Foo { |
1459 | // doc f |
1460 | int f(); |
1461 | }; |
1462 | )" ; |
1463 | CollectorOpts.StoreAllDocumentation = false; |
1464 | runSymbolCollector(HeaderCode: Header, /* Main */ MainCode: "" ); |
1465 | EXPECT_THAT(Symbols, |
1466 | UnorderedElementsAre( |
1467 | AllOf(qName("Foo" ), doc("doc Foo" ), forCodeCompletion(true)), |
1468 | AllOf(qName("Foo::f" ), doc("" ), returnType("" ), |
1469 | forCodeCompletion(false)))); |
1470 | |
1471 | CollectorOpts.StoreAllDocumentation = true; |
1472 | runSymbolCollector(HeaderCode: Header, /* Main */ MainCode: "" ); |
1473 | EXPECT_THAT(Symbols, |
1474 | UnorderedElementsAre( |
1475 | AllOf(qName("Foo" ), doc("doc Foo" ), forCodeCompletion(true)), |
1476 | AllOf(qName("Foo::f" ), doc("doc f" ), returnType("" ), |
1477 | forCodeCompletion(false)))); |
1478 | } |
1479 | |
1480 | TEST_F(SymbolCollectorTest, ClassMembers) { |
1481 | const std::string = R"( |
1482 | class Foo { |
1483 | void f() {} |
1484 | void g(); |
1485 | static void sf() {} |
1486 | static void ssf(); |
1487 | static int x; |
1488 | }; |
1489 | )" ; |
1490 | const std::string Main = R"( |
1491 | void Foo::g() {} |
1492 | void Foo::ssf() {} |
1493 | )" ; |
1494 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
1495 | EXPECT_THAT( |
1496 | Symbols, |
1497 | UnorderedElementsAre( |
1498 | qName("Foo" ), |
1499 | AllOf(qName("Foo::f" ), returnType("" ), forCodeCompletion(false)), |
1500 | AllOf(qName("Foo::g" ), returnType("" ), forCodeCompletion(false)), |
1501 | AllOf(qName("Foo::sf" ), returnType("" ), forCodeCompletion(false)), |
1502 | AllOf(qName("Foo::ssf" ), returnType("" ), forCodeCompletion(false)), |
1503 | AllOf(qName("Foo::x" ), returnType("" ), forCodeCompletion(false)))); |
1504 | } |
1505 | |
1506 | TEST_F(SymbolCollectorTest, Scopes) { |
1507 | const std::string = R"( |
1508 | namespace na { |
1509 | class Foo {}; |
1510 | namespace nb { |
1511 | class Bar {}; |
1512 | } |
1513 | } |
1514 | )" ; |
1515 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1516 | EXPECT_THAT(Symbols, |
1517 | UnorderedElementsAre(qName("na" ), qName("na::nb" ), |
1518 | qName("na::Foo" ), qName("na::nb::Bar" ))); |
1519 | } |
1520 | |
1521 | TEST_F(SymbolCollectorTest, ExternC) { |
1522 | const std::string = R"( |
1523 | extern "C" { class Foo {}; } |
1524 | namespace na { |
1525 | extern "C" { class Bar {}; } |
1526 | } |
1527 | )" ; |
1528 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1529 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("na" ), qName("Foo" ), |
1530 | qName("na::Bar" ))); |
1531 | } |
1532 | |
1533 | TEST_F(SymbolCollectorTest, SkipInlineNamespace) { |
1534 | const std::string = R"( |
1535 | namespace na { |
1536 | inline namespace nb { |
1537 | class Foo {}; |
1538 | } |
1539 | } |
1540 | namespace na { |
1541 | // This is still inlined. |
1542 | namespace nb { |
1543 | class Bar {}; |
1544 | } |
1545 | } |
1546 | )" ; |
1547 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1548 | EXPECT_THAT(Symbols, |
1549 | UnorderedElementsAre(qName("na" ), qName("na::nb" ), |
1550 | qName("na::Foo" ), qName("na::Bar" ))); |
1551 | } |
1552 | |
1553 | TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { |
1554 | const std::string = R"( |
1555 | namespace nx { |
1556 | /// Foo comment. |
1557 | int ff(int x, double y) { return 0; } |
1558 | } |
1559 | )" ; |
1560 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1561 | EXPECT_THAT( |
1562 | Symbols, |
1563 | UnorderedElementsAre( |
1564 | qName("nx" ), AllOf(qName("nx::ff" ), labeled("ff(int x, double y)" ), |
1565 | returnType("int" ), doc("Foo comment." )))); |
1566 | } |
1567 | |
1568 | TEST_F(SymbolCollectorTest, snippet) { |
1569 | const std::string = R"( |
1570 | namespace nx { |
1571 | void f() {} |
1572 | int ff(int x, double y) { return 0; } |
1573 | } |
1574 | )" ; |
1575 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1576 | EXPECT_THAT(Symbols, |
1577 | UnorderedElementsAre( |
1578 | qName("nx" ), |
1579 | AllOf(qName("nx::f" ), labeled("f()" ), snippet("f()" )), |
1580 | AllOf(qName("nx::ff" ), labeled("ff(int x, double y)" ), |
1581 | snippet("ff(${1:int x}, ${2:double y})" )))); |
1582 | } |
1583 | |
1584 | TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { |
1585 | CollectorOpts.CollectIncludePath = true; |
1586 | runSymbolCollector(HeaderCode: "#pragma once\nclass Foo {};" , /*Main=*/MainCode: "" ); |
1587 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1588 | AllOf(qName("Foo" ), declURI(TestHeaderURI)))); |
1589 | EXPECT_THAT(Symbols.begin()->IncludeHeaders, |
1590 | UnorderedElementsAre(IncludeHeaderWithRef(TestHeaderURI, 1u))); |
1591 | } |
1592 | |
1593 | TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { |
1594 | CollectorOpts.CollectIncludePath = true; |
1595 | runSymbolCollector( |
1596 | HeaderCode: R"cpp( |
1597 | namespace std { |
1598 | class string {}; |
1599 | // Move overloads have special handling. |
1600 | template <typename _T> T&& move(_T&& __value); |
1601 | template <typename _I, typename _O> _O move(_I, _I, _O); |
1602 | template <typename _T, typename _O, typename _I> _O move( |
1603 | _T&&, _O, _O, _I); |
1604 | } |
1605 | )cpp" , |
1606 | /*Main=*/MainCode: "" ); |
1607 | EXPECT_THAT( |
1608 | Symbols, |
1609 | UnorderedElementsAre( |
1610 | qName("std" ), |
1611 | AllOf(qName("std::string" ), declURI(TestHeaderURI), |
1612 | includeHeader("<string>" )), |
1613 | // Parameter names are demangled. |
1614 | AllOf(labeled("move(T &&value)" ), includeHeader("<utility>" )), |
1615 | AllOf(labeled("move(I, I, O)" ), includeHeader("<algorithm>" )), |
1616 | AllOf(labeled("move(T &&, O, O, I)" ), includeHeader("<algorithm>" )))); |
1617 | } |
1618 | |
1619 | TEST_F(SymbolCollectorTest, IWYUPragma) { |
1620 | CollectorOpts.CollectIncludePath = true; |
1621 | const std::string = R"( |
1622 | // IWYU pragma: private, include the/good/header.h |
1623 | class Foo {}; |
1624 | )" ; |
1625 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1626 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1627 | AllOf(qName("Foo" ), declURI(TestHeaderURI), |
1628 | includeHeader("\"the/good/header.h\"" )))); |
1629 | } |
1630 | |
1631 | TEST_F(SymbolCollectorTest, IWYUPragmaWithDoubleQuotes) { |
1632 | CollectorOpts.CollectIncludePath = true; |
1633 | const std::string = R"( |
1634 | // IWYU pragma: private, include "the/good/header.h" |
1635 | class Foo {}; |
1636 | )" ; |
1637 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" ); |
1638 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1639 | AllOf(qName("Foo" ), declURI(TestHeaderURI), |
1640 | includeHeader("\"the/good/header.h\"" )))); |
1641 | } |
1642 | |
1643 | TEST_F(SymbolCollectorTest, IWYUPragmaExport) { |
1644 | CollectorOpts.CollectIncludePath = true; |
1645 | const std::string = R"cpp(#pragma once |
1646 | #include "exporter.h" |
1647 | )cpp" ; |
1648 | auto ExporterFile = testPath(File: "exporter.h" ); |
1649 | InMemoryFileSystem->addFile( |
1650 | Path: ExporterFile, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: R"cpp(#pragma once |
1651 | #include "private.h" // IWYU pragma: export |
1652 | )cpp" )); |
1653 | auto PrivateFile = testPath(File: "private.h" ); |
1654 | InMemoryFileSystem->addFile( |
1655 | Path: PrivateFile, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class Foo {};" )); |
1656 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , |
1657 | /*ExtraArgs=*/{"-I" , testRoot()}); |
1658 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
1659 | qName("Foo" ), |
1660 | includeHeader(URI::create(ExporterFile).toString()), |
1661 | declURI(URI::create(PrivateFile).toString())))); |
1662 | } |
1663 | |
1664 | TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { |
1665 | CollectorOpts.CollectIncludePath = true; |
1666 | // To make this case as hard as possible, we won't tell clang main is a |
1667 | // header. No extension, no -x c++-header. |
1668 | TestFileName = testPath(File: "no_ext_main" ); |
1669 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
1670 | auto IncFile = testPath(File: "test.inc" ); |
1671 | auto IncURI = URI::create(AbsolutePath: IncFile).toString(); |
1672 | InMemoryFileSystem->addFile(Path: IncFile, ModificationTime: 0, |
1673 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class X {};" )); |
1674 | runSymbolCollector(HeaderCode: "" , MainCode: R"cpp( |
1675 | // Can't use #pragma once in a main file clang doesn't think is a header. |
1676 | #ifndef MAIN_H_ |
1677 | #define MAIN_H_ |
1678 | #include "test.inc" |
1679 | #endif |
1680 | )cpp" , |
1681 | /*ExtraArgs=*/{"-I" , testRoot()}); |
1682 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), declURI(IncURI), |
1683 | includeHeader(TestFileURI)))); |
1684 | } |
1685 | |
1686 | TEST_F(SymbolCollectorTest, IncFileInNonHeader) { |
1687 | CollectorOpts.CollectIncludePath = true; |
1688 | TestFileName = testPath(File: "main.cc" ); |
1689 | TestFileURI = URI::create(AbsolutePath: TestFileName).toString(); |
1690 | auto IncFile = testPath(File: "test.inc" ); |
1691 | auto IncURI = URI::create(AbsolutePath: IncFile).toString(); |
1692 | InMemoryFileSystem->addFile(Path: IncFile, ModificationTime: 0, |
1693 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "class X {};" )); |
1694 | runSymbolCollector(HeaderCode: "" , MainCode: R"cpp( |
1695 | #include "test.inc" |
1696 | )cpp" , |
1697 | /*ExtraArgs=*/{"-I" , testRoot()}); |
1698 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), declURI(IncURI), |
1699 | Not(includeHeader())))); |
1700 | } |
1701 | |
1702 | // Features that depend on header-guards are fragile. Header guards are only |
1703 | // recognized when the file ends, so we have to defer checking for them. |
1704 | TEST_F(SymbolCollectorTest, HeaderGuardDetected) { |
1705 | CollectorOpts.CollectIncludePath = true; |
1706 | CollectorOpts.CollectMacro = true; |
1707 | runSymbolCollector(HeaderCode: R"cpp( |
1708 | #ifndef HEADER_GUARD_ |
1709 | #define HEADER_GUARD_ |
1710 | |
1711 | // Symbols are seen before the header guard is complete. |
1712 | #define MACRO |
1713 | int decl(); |
1714 | |
1715 | #endif // Header guard is recognized here. |
1716 | )cpp" , |
1717 | MainCode: "" ); |
1718 | EXPECT_THAT(Symbols, Not(Contains(qName("HEADER_GUARD_" )))); |
1719 | EXPECT_THAT(Symbols, Each(includeHeader())); |
1720 | } |
1721 | |
1722 | TEST_F(SymbolCollectorTest, NonModularHeader) { |
1723 | auto TU = TestTU::withHeaderCode(HeaderCode: "int x();" ); |
1724 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(includeHeader())); |
1725 | |
1726 | // Files missing include guards aren't eligible for insertion. |
1727 | TU.ImplicitHeaderGuard = false; |
1728 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); |
1729 | |
1730 | // We recognize some patterns of trying to prevent insertion. |
1731 | TU = TestTU::withHeaderCode(HeaderCode: R"cpp( |
1732 | #ifndef SECRET |
1733 | #error "This file isn't safe to include directly" |
1734 | #endif |
1735 | int x(); |
1736 | )cpp" ); |
1737 | TU.ExtraArgs.push_back(x: "-DSECRET" ); // *we're* able to include it. |
1738 | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); |
1739 | } |
1740 | |
1741 | TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { |
1742 | CollectorOpts.CollectIncludePath = true; |
1743 | Annotations (R"( |
1744 | #pragma once |
1745 | // Forward declarations of TagDecls. |
1746 | class C; |
1747 | struct S; |
1748 | union U; |
1749 | |
1750 | // Canonical declarations. |
1751 | class $cdecl[[C]] {}; |
1752 | struct $sdecl[[S]] {}; |
1753 | union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; |
1754 | )" ); |
1755 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
1756 | EXPECT_THAT( |
1757 | Symbols, |
1758 | UnorderedElementsAre( |
1759 | AllOf(qName("C" ), declURI(TestHeaderURI), |
1760 | declRange(Header.range("cdecl" )), includeHeader(TestHeaderURI), |
1761 | defURI(TestHeaderURI), defRange(Header.range("cdecl" ))), |
1762 | AllOf(qName("S" ), declURI(TestHeaderURI), |
1763 | declRange(Header.range("sdecl" )), includeHeader(TestHeaderURI), |
1764 | defURI(TestHeaderURI), defRange(Header.range("sdecl" ))), |
1765 | AllOf(qName("U" ), declURI(TestHeaderURI), |
1766 | declRange(Header.range("udecl" )), includeHeader(TestHeaderURI), |
1767 | defURI(TestHeaderURI), defRange(Header.range("udecl" ))), |
1768 | AllOf(qName("U::x" ), declURI(TestHeaderURI), |
1769 | declRange(Header.range("xdecl" )), defURI(TestHeaderURI), |
1770 | defRange(Header.range("xdecl" ))), |
1771 | AllOf(qName("U::y" ), declURI(TestHeaderURI), |
1772 | declRange(Header.range("ydecl" )), defURI(TestHeaderURI), |
1773 | defRange(Header.range("ydecl" ))))); |
1774 | } |
1775 | |
1776 | TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { |
1777 | CollectorOpts.CollectIncludePath = true; |
1778 | runSymbolCollector(/*Header=*/HeaderCode: "#pragma once\nclass X;" , |
1779 | /*Main=*/MainCode: "class X {};" ); |
1780 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
1781 | qName("X" ), declURI(TestHeaderURI), |
1782 | includeHeader(TestHeaderURI), defURI(TestFileURI)))); |
1783 | } |
1784 | |
1785 | TEST_F(SymbolCollectorTest, UTF16Character) { |
1786 | // ö is 2-bytes. |
1787 | Annotations (/*Header=*/"class [[pörk]] {};" ); |
1788 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
1789 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1790 | AllOf(qName("pörk" ), declRange(Header.range())))); |
1791 | } |
1792 | |
1793 | TEST_F(SymbolCollectorTest, DoNotIndexSymbolsInFriendDecl) { |
1794 | Annotations (R"( |
1795 | namespace nx { |
1796 | class $z[[Z]] {}; |
1797 | class X { |
1798 | friend class Y; |
1799 | friend class Z; |
1800 | friend void foo(); |
1801 | friend void $bar[[bar]]() {} |
1802 | }; |
1803 | class $y[[Y]] {}; |
1804 | void $foo[[foo]](); |
1805 | } |
1806 | )" ); |
1807 | runSymbolCollector(HeaderCode: Header.code(), /*Main=*/MainCode: "" ); |
1808 | |
1809 | EXPECT_THAT(Symbols, |
1810 | UnorderedElementsAre( |
1811 | qName("nx" ), qName("nx::X" ), |
1812 | AllOf(qName("nx::Y" ), declRange(Header.range("y" ))), |
1813 | AllOf(qName("nx::Z" ), declRange(Header.range("z" ))), |
1814 | AllOf(qName("nx::foo" ), declRange(Header.range("foo" ))), |
1815 | AllOf(qName("nx::bar" ), declRange(Header.range("bar" ))))); |
1816 | } |
1817 | |
1818 | TEST_F(SymbolCollectorTest, ReferencesInFriendDecl) { |
1819 | const std::string = R"( |
1820 | class X; |
1821 | class Y; |
1822 | )" ; |
1823 | const std::string Main = R"( |
1824 | class C { |
1825 | friend ::X; |
1826 | friend class Y; |
1827 | }; |
1828 | )" ; |
1829 | CollectorOpts.CountReferences = true; |
1830 | runSymbolCollector(HeaderCode: Header, MainCode: Main); |
1831 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X" ), refCount(1)), |
1832 | AllOf(qName("Y" ), refCount(1)), |
1833 | AllOf(qName("C" ), refCount(0)))); |
1834 | } |
1835 | |
1836 | TEST_F(SymbolCollectorTest, Origin) { |
1837 | CollectorOpts.Origin = SymbolOrigin::Static; |
1838 | runSymbolCollector(HeaderCode: "class Foo {};" , /*Main=*/MainCode: "" ); |
1839 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1840 | Field(&Symbol::Origin, SymbolOrigin::Static))); |
1841 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
1842 | CollectorOpts.CollectMacro = true; |
1843 | runSymbolCollector(HeaderCode: "#define FOO" , /*Main=*/MainCode: "" ); |
1844 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1845 | Field(&Symbol::Origin, SymbolOrigin::Static))); |
1846 | } |
1847 | |
1848 | TEST_F(SymbolCollectorTest, CollectMacros) { |
1849 | CollectorOpts.CollectIncludePath = true; |
1850 | Annotations (R"( |
1851 | #pragma once |
1852 | #define X 1 |
1853 | #define $mac[[MAC]](x) int x |
1854 | #define $used[[USED]](y) float y; |
1855 | |
1856 | MAC(p); |
1857 | )" ); |
1858 | |
1859 | Annotations Main(R"( |
1860 | #define $main[[MAIN]] 1 |
1861 | USED(t); |
1862 | )" ); |
1863 | CollectorOpts.CountReferences = true; |
1864 | CollectorOpts.CollectMacro = true; |
1865 | runSymbolCollector(HeaderCode: Header.code(), MainCode: Main.code()); |
1866 | EXPECT_THAT( |
1867 | Symbols, |
1868 | UnorderedElementsAre( |
1869 | qName("p" ), qName("t" ), |
1870 | AllOf(qName("X" ), declURI(TestHeaderURI), |
1871 | includeHeader(TestHeaderURI)), |
1872 | AllOf(labeled("MAC(x)" ), refCount(0), |
1873 | |
1874 | declRange(Header.range("mac" )), visibleOutsideFile()), |
1875 | AllOf(labeled("USED(y)" ), refCount(1), |
1876 | declRange(Header.range("used" )), visibleOutsideFile()), |
1877 | AllOf(labeled("MAIN" ), refCount(0), declRange(Main.range("main" )), |
1878 | Not(visibleOutsideFile())))); |
1879 | } |
1880 | |
1881 | TEST_F(SymbolCollectorTest, DeprecatedSymbols) { |
1882 | const std::string = R"( |
1883 | void TestClangc() __attribute__((deprecated("", ""))); |
1884 | void TestClangd(); |
1885 | )" ; |
1886 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
1887 | EXPECT_THAT(Symbols, UnorderedElementsAre( |
1888 | AllOf(qName("TestClangc" ), deprecated()), |
1889 | AllOf(qName("TestClangd" ), Not(deprecated())))); |
1890 | } |
1891 | |
1892 | TEST_F(SymbolCollectorTest, implementationDetail) { |
1893 | const std::string = R"( |
1894 | #define DECL_NAME(x, y) x##_##y##_Decl |
1895 | #define DECL(x, y) class DECL_NAME(x, y) {}; |
1896 | DECL(X, Y); // X_Y_Decl |
1897 | |
1898 | class Public {}; |
1899 | )" ; |
1900 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
1901 | EXPECT_THAT(Symbols, |
1902 | UnorderedElementsAre( |
1903 | AllOf(qName("X_Y_Decl" ), implementationDetail()), |
1904 | AllOf(qName("Public" ), Not(implementationDetail())))); |
1905 | } |
1906 | |
1907 | TEST_F(SymbolCollectorTest, UsingDecl) { |
1908 | const char * = R"( |
1909 | void foo(); |
1910 | namespace std { |
1911 | using ::foo; |
1912 | })" ; |
1913 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
1914 | EXPECT_THAT(Symbols, Contains(qName("std::foo" ))); |
1915 | } |
1916 | |
1917 | TEST_F(SymbolCollectorTest, CBuiltins) { |
1918 | // In C, printf in stdio.h is a redecl of an implicit builtin. |
1919 | const char * = R"( |
1920 | extern int printf(const char*, ...); |
1921 | )" ; |
1922 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" , ExtraArgs: {"-xc" }); |
1923 | EXPECT_THAT(Symbols, Contains(qName("printf" ))); |
1924 | } |
1925 | |
1926 | TEST_F(SymbolCollectorTest, InvalidSourceLoc) { |
1927 | const char * = R"( |
1928 | void operator delete(void*) |
1929 | __attribute__((__externally_visible__));)" ; |
1930 | runSymbolCollector(HeaderCode: Header, /**/ MainCode: "" ); |
1931 | EXPECT_THAT(Symbols, Contains(qName("operator delete" ))); |
1932 | } |
1933 | |
1934 | TEST_F(SymbolCollectorTest, BadUTF8) { |
1935 | // Extracted from boost/spirit/home/support/char_encoding/iso8859_1.hpp |
1936 | // This looks like UTF-8 and fools clang, but has high-ISO-8859-1 comments. |
1937 | const char * = "int PUNCT = 0;\n" |
1938 | "/* \xa1 */ int types[] = { /* \xa1 */PUNCT };" ; |
1939 | CollectorOpts.RefFilter = RefKind::All; |
1940 | CollectorOpts.RefsInHeaders = true; |
1941 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
1942 | EXPECT_THAT(Symbols, Contains(AllOf(qName("types" ), doc("\xef\xbf\xbd " )))); |
1943 | EXPECT_THAT(Symbols, Contains(qName("PUNCT" ))); |
1944 | // Reference is stored, although offset within line is not reliable. |
1945 | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PUNCT" ).ID, _))); |
1946 | } |
1947 | |
1948 | TEST_F(SymbolCollectorTest, MacrosInHeaders) { |
1949 | CollectorOpts.CollectMacro = true; |
1950 | TestFileName = testPath(File: "test.h" ); |
1951 | runSymbolCollector(HeaderCode: "" , MainCode: "#define X" ); |
1952 | EXPECT_THAT(Symbols, |
1953 | UnorderedElementsAre(AllOf(qName("X" ), forCodeCompletion(true)))); |
1954 | } |
1955 | |
1956 | // Regression test for a crash-bug we used to have. |
1957 | TEST_F(SymbolCollectorTest, UndefOfModuleMacro) { |
1958 | auto TU = TestTU::withCode(Code: R"cpp(#include "bar.h")cpp" ); |
1959 | TU.AdditionalFiles["bar.h" ] = R"cpp( |
1960 | #include "foo.h" |
1961 | #undef X |
1962 | )cpp" ; |
1963 | TU.AdditionalFiles["foo.h" ] = "#define X 1" ; |
1964 | TU.AdditionalFiles["module.modulemap" ] = R"cpp( |
1965 | module foo { |
1966 | header "foo.h" |
1967 | export * |
1968 | } |
1969 | )cpp" ; |
1970 | TU.ExtraArgs.push_back(x: "-fmodules" ); |
1971 | TU.ExtraArgs.push_back(x: "-fmodule-map-file=" + testPath(File: "module.modulemap" )); |
1972 | TU.OverlayRealFileSystemForModules = true; |
1973 | |
1974 | TU.build(); |
1975 | // We mostly care about not crashing, but verify that we didn't insert garbage |
1976 | // about X too. |
1977 | EXPECT_THAT(TU.headerSymbols(), Not(Contains(qName("X" )))); |
1978 | } |
1979 | |
1980 | TEST_F(SymbolCollectorTest, NoCrashOnObjCMethodCStyleParam) { |
1981 | auto TU = TestTU::withCode(Code: R"objc( |
1982 | @interface Foo |
1983 | - (void)fun:(bool)foo, bool bar; |
1984 | @end |
1985 | )objc" ); |
1986 | TU.ExtraArgs.push_back(x: "-xobjective-c++" ); |
1987 | |
1988 | TU.build(); |
1989 | // We mostly care about not crashing. |
1990 | EXPECT_THAT(TU.headerSymbols(), |
1991 | UnorderedElementsAre(qName("Foo" ), qName("Foo::fun:" ))); |
1992 | } |
1993 | |
1994 | TEST_F(SymbolCollectorTest, Reserved) { |
1995 | const char * = R"cpp( |
1996 | #pragma once |
1997 | void __foo(); |
1998 | namespace _X { int secret; } |
1999 | )cpp" ; |
2000 | |
2001 | CollectorOpts.CollectReserved = true; |
2002 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
2003 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo" ), qName("_X" ), |
2004 | qName("_X::secret" ))); |
2005 | |
2006 | CollectorOpts.CollectReserved = false; |
2007 | runSymbolCollector(HeaderCode: Header, MainCode: "" ); |
2008 | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo" ), qName("_X" ), |
2009 | qName("_X::secret" ))); |
2010 | |
2011 | // Ugly: for some reason we reuse the test filesystem across tests. |
2012 | // You can't overwrite the same filename with new content! |
2013 | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; |
2014 | runSymbolCollector(HeaderCode: "#pragma GCC system_header\n" + std::string(Header), MainCode: "" ); |
2015 | EXPECT_THAT(Symbols, IsEmpty()); |
2016 | } |
2017 | |
2018 | TEST_F(SymbolCollectorTest, Concepts) { |
2019 | const char * = R"cpp( |
2020 | template <class T> |
2021 | concept A = sizeof(T) <= 8; |
2022 | )cpp" ; |
2023 | runSymbolCollector(HeaderCode: "" , MainCode: Header, ExtraArgs: {"-std=c++20" }); |
2024 | EXPECT_THAT(Symbols, |
2025 | UnorderedElementsAre(AllOf( |
2026 | qName("A" ), hasKind(clang::index::SymbolKind::Concept)))); |
2027 | } |
2028 | |
2029 | TEST_F(SymbolCollectorTest, IncludeHeaderForwardDecls) { |
2030 | CollectorOpts.CollectIncludePath = true; |
2031 | const std::string = R"cpp(#pragma once |
2032 | struct Foo; |
2033 | #include "full.h" |
2034 | )cpp" ; |
2035 | auto FullFile = testPath(File: "full.h" ); |
2036 | InMemoryFileSystem->addFile(Path: FullFile, ModificationTime: 0, |
2037 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: R"cpp( |
2038 | #pragma once |
2039 | struct Foo {};)cpp" )); |
2040 | runSymbolCollector(HeaderCode: Header, /*Main=*/MainCode: "" , |
2041 | /*ExtraArgs=*/{"-I" , testRoot()}); |
2042 | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( |
2043 | qName("Foo" ), |
2044 | includeHeader(URI::create(FullFile).toString())))) |
2045 | << *Symbols.begin(); |
2046 | } |
2047 | } // namespace |
2048 | } // namespace clangd |
2049 | } // namespace clang |
2050 | |