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