|  | //===-- SymbolCollectorTests.cpp  -------------------------------*- C++ -*-===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "Annotations.h" | 
|  | #include "TestFS.h" | 
|  | #include "TestTU.h" | 
|  | #include "index/SymbolCollector.h" | 
|  | #include "clang/Basic/FileManager.h" | 
|  | #include "clang/Basic/FileSystemOptions.h" | 
|  | #include "clang/Frontend/CompilerInstance.h" | 
|  | #include "clang/Index/IndexingAction.h" | 
|  | #include "clang/Index/IndexingOptions.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "llvm/ADT/IntrusiveRefCntPtr.h" | 
|  | #include "llvm/ADT/StringRef.h" | 
|  | #include "llvm/Support/MemoryBuffer.h" | 
|  | #include "llvm/Support/VirtualFileSystem.h" | 
|  | #include "gmock/gmock-matchers.h" | 
|  | #include "gmock/gmock-more-matchers.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  |  | 
|  | namespace clang { | 
|  | namespace clangd { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::AllOf; | 
|  | using ::testing::Contains; | 
|  | using ::testing::Each; | 
|  | using ::testing::ElementsAre; | 
|  | using ::testing::Field; | 
|  | using ::testing::IsEmpty; | 
|  | using ::testing::Not; | 
|  | using ::testing::Pair; | 
|  | using ::testing::UnorderedElementsAre; | 
|  | using ::testing::UnorderedElementsAreArray; | 
|  |  | 
|  | // GMock helpers for matching Symbol. | 
|  | MATCHER_P(labeled, Label, "") { | 
|  | return (arg.Name + arg.Signature).str() == Label; | 
|  | } | 
|  | MATCHER_P(returnType, D, "") { return arg.ReturnType == D; } | 
|  | MATCHER_P(doc, D, "") { return arg.Documentation == D; } | 
|  | MATCHER_P(snippet, S, "") { | 
|  | return (arg.Name + arg.CompletionSnippetSuffix).str() == S; | 
|  | } | 
|  | MATCHER_P(qName, Name, "") { return (arg.Scope + arg.Name).str() == Name; } | 
|  | MATCHER_P(hasName, Name, "") { return arg.Name == Name; } | 
|  | MATCHER_P(templateArgs, TemplArgs, "") { | 
|  | return arg.TemplateSpecializationArgs == TemplArgs; | 
|  | } | 
|  | MATCHER_P(declURI, P, "") { | 
|  | return StringRef(arg.CanonicalDeclaration.FileURI) == P; | 
|  | } | 
|  | MATCHER_P(defURI, P, "") { return StringRef(arg.Definition.FileURI) == P; } | 
|  | MATCHER(includeHeader, "") { return !arg.IncludeHeaders.empty(); } | 
|  | MATCHER_P(includeHeader, P, "") { | 
|  | return (arg.IncludeHeaders.size() == 1) && | 
|  | (arg.IncludeHeaders.begin()->IncludeHeader == P); | 
|  | } | 
|  | MATCHER_P2(IncludeHeaderWithRef, includeHeader, References, "") { | 
|  | return (arg.IncludeHeader == includeHeader) && (arg.References == References); | 
|  | } | 
|  | bool rangesMatch(const SymbolLocation &Loc, const Range &R) { | 
|  | return std::make_tuple(Loc.Start.line(), Loc.Start.column(), Loc.End.line(), | 
|  | Loc.End.column()) == | 
|  | std::make_tuple(R.start.line, R.start.character, R.end.line, | 
|  | R.end.character); | 
|  | } | 
|  | MATCHER_P(declRange, Pos, "") { | 
|  | return rangesMatch(arg.CanonicalDeclaration, Pos); | 
|  | } | 
|  | MATCHER_P(defRange, Pos, "") { return rangesMatch(arg.Definition, Pos); } | 
|  | MATCHER_P(refCount, R, "") { return int(arg.References) == R; } | 
|  | MATCHER_P(forCodeCompletion, IsIndexedForCodeCompletion, "") { | 
|  | return static_cast<bool>(arg.Flags & Symbol::IndexedForCodeCompletion) == | 
|  | IsIndexedForCodeCompletion; | 
|  | } | 
|  | MATCHER(deprecated, "") { return arg.Flags & Symbol::Deprecated; } | 
|  | MATCHER(implementationDetail, "") { | 
|  | return arg.Flags & Symbol::ImplementationDetail; | 
|  | } | 
|  | MATCHER(visibleOutsideFile, "") { | 
|  | return static_cast<bool>(arg.Flags & Symbol::VisibleOutsideFile); | 
|  | } | 
|  | MATCHER(refRange, "") { | 
|  | const Ref &Pos = ::testing::get<0>(arg); | 
|  | const Range &Range = ::testing::get<1>(arg); | 
|  | return rangesMatch(Pos.Location, Range); | 
|  | } | 
|  | MATCHER_P2(OverriddenBy, Subject, Object, "") { | 
|  | return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; | 
|  | } | 
|  | ::testing::Matcher<const std::vector<Ref> &> | 
|  | haveRanges(const std::vector<Range> Ranges) { | 
|  | return ::testing::UnorderedPointwise(refRange(), Ranges); | 
|  | } | 
|  |  | 
|  | class ShouldCollectSymbolTest : public ::testing::Test { | 
|  | public: | 
|  | void build(llvm::StringRef HeaderCode, llvm::StringRef Code = "") { | 
|  | File.HeaderFilename = HeaderName; | 
|  | File.Filename = FileName; | 
|  | File.HeaderCode = std::string(HeaderCode); | 
|  | File.Code = std::string(Code); | 
|  | AST = File.build(); | 
|  | } | 
|  |  | 
|  | // build() must have been called. | 
|  | bool shouldCollect(llvm::StringRef Name, bool Qualified = true) { | 
|  | assert(AST.hasValue()); | 
|  | const NamedDecl &ND = | 
|  | Qualified ? findDecl(*AST, Name) : findUnqualifiedDecl(*AST, Name); | 
|  | const SourceManager &SM = AST->getSourceManager(); | 
|  | bool MainFile = isInsideMainFile(ND.getBeginLoc(), SM); | 
|  | return SymbolCollector::shouldCollectSymbol( | 
|  | ND, AST->getASTContext(), SymbolCollector::Options(), MainFile); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | std::string HeaderName = "f.h"; | 
|  | std::string FileName = "f.cpp"; | 
|  | TestTU File; | 
|  | llvm::Optional<ParsedAST> AST; // Initialized after build. | 
|  | }; | 
|  |  | 
|  | TEST_F(ShouldCollectSymbolTest, ShouldCollectSymbol) { | 
|  | build(R"( | 
|  | namespace nx { | 
|  | class X{}; | 
|  | auto f() { int Local; } // auto ensures function body is parsed. | 
|  | struct { int x; } var; | 
|  | } | 
|  | )", | 
|  | R"( | 
|  | class InMain {}; | 
|  | namespace { class InAnonymous {}; } | 
|  | static void g(); | 
|  | )"); | 
|  | auto AST = File.build(); | 
|  | EXPECT_TRUE(shouldCollect("nx")); | 
|  | EXPECT_TRUE(shouldCollect("nx::X")); | 
|  | EXPECT_TRUE(shouldCollect("nx::f")); | 
|  | EXPECT_TRUE(shouldCollect("InMain")); | 
|  | EXPECT_TRUE(shouldCollect("InAnonymous", /*Qualified=*/false)); | 
|  | EXPECT_TRUE(shouldCollect("g")); | 
|  |  | 
|  | EXPECT_FALSE(shouldCollect("Local", /*Qualified=*/false)); | 
|  | } | 
|  |  | 
|  | TEST_F(ShouldCollectSymbolTest, CollectLocalClassesAndVirtualMethods) { | 
|  | build(R"( | 
|  | namespace nx { | 
|  | auto f() { | 
|  | int Local; | 
|  | auto LocalLambda = [&](){ | 
|  | Local++; | 
|  | class ClassInLambda{}; | 
|  | return Local; | 
|  | }; | 
|  | } // auto ensures function body is parsed. | 
|  | auto foo() { | 
|  | class LocalBase { | 
|  | virtual void LocalVirtual(); | 
|  | void LocalConcrete(); | 
|  | int BaseMember; | 
|  | }; | 
|  | } | 
|  | } // namespace nx | 
|  | )", | 
|  | ""); | 
|  | auto AST = File.build(); | 
|  | EXPECT_FALSE(shouldCollect("Local", /*Qualified=*/false)); | 
|  | EXPECT_TRUE(shouldCollect("ClassInLambda", /*Qualified=*/false)); | 
|  | EXPECT_TRUE(shouldCollect("LocalBase", /*Qualified=*/false)); | 
|  | EXPECT_TRUE(shouldCollect("LocalVirtual", /*Qualified=*/false)); | 
|  | EXPECT_TRUE(shouldCollect("LocalConcrete", /*Qualified=*/false)); | 
|  | EXPECT_FALSE(shouldCollect("BaseMember", /*Qualified=*/false)); | 
|  | EXPECT_FALSE(shouldCollect("Local", /*Qualified=*/false)); | 
|  | } | 
|  |  | 
|  | TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) { | 
|  | HeaderName = "f.proto.h"; | 
|  | build( | 
|  | R"(// Generated by the protocol buffer compiler.  DO NOT EDIT! | 
|  | namespace nx { | 
|  | class Top_Level {}; | 
|  | class TopLevel {}; | 
|  | enum Kind { | 
|  | KIND_OK, | 
|  | Kind_Not_Ok, | 
|  | }; | 
|  | })"); | 
|  | EXPECT_TRUE(shouldCollect("nx::TopLevel")); | 
|  | EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK")); | 
|  | EXPECT_TRUE(shouldCollect("nx::Kind")); | 
|  |  | 
|  | EXPECT_FALSE(shouldCollect("nx::Top_Level")); | 
|  | EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok")); | 
|  | } | 
|  |  | 
|  | TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) { | 
|  | HeaderName = "f.proto.h"; | 
|  | build(R"( | 
|  | namespace nx { | 
|  | class Top_Level {}; | 
|  | enum Kind { | 
|  | Kind_Fine | 
|  | }; | 
|  | } | 
|  | )"); | 
|  | EXPECT_TRUE(shouldCollect("nx::Top_Level")); | 
|  | EXPECT_TRUE(shouldCollect("nx::Kind_Fine")); | 
|  | } | 
|  |  | 
|  | class SymbolIndexActionFactory : public tooling::FrontendActionFactory { | 
|  | public: | 
|  | SymbolIndexActionFactory(SymbolCollector::Options COpts, | 
|  | CommentHandler *PragmaHandler) | 
|  | : COpts(std::move(COpts)), PragmaHandler(PragmaHandler) {} | 
|  |  | 
|  | std::unique_ptr<FrontendAction> create() override { | 
|  | class IndexAction : public ASTFrontendAction { | 
|  | public: | 
|  | IndexAction(std::shared_ptr<index::IndexDataConsumer> DataConsumer, | 
|  | const index::IndexingOptions &Opts, | 
|  | CommentHandler *PragmaHandler) | 
|  | : DataConsumer(std::move(DataConsumer)), Opts(Opts), | 
|  | PragmaHandler(PragmaHandler) {} | 
|  |  | 
|  | std::unique_ptr<ASTConsumer> | 
|  | CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { | 
|  | if (PragmaHandler) | 
|  | CI.getPreprocessor().addCommentHandler(PragmaHandler); | 
|  | return createIndexingASTConsumer(DataConsumer, Opts, | 
|  | CI.getPreprocessorPtr()); | 
|  | } | 
|  |  | 
|  | bool BeginInvocation(CompilerInstance &CI) override { | 
|  | // Make the compiler parse all comments. | 
|  | CI.getLangOpts().CommentOpts.ParseAllComments = true; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::shared_ptr<index::IndexDataConsumer> DataConsumer; | 
|  | index::IndexingOptions Opts; | 
|  | CommentHandler *PragmaHandler; | 
|  | }; | 
|  | index::IndexingOptions IndexOpts; | 
|  | IndexOpts.SystemSymbolFilter = | 
|  | index::IndexingOptions::SystemSymbolFilterKind::All; | 
|  | IndexOpts.IndexFunctionLocals = true; | 
|  | Collector = std::make_shared<SymbolCollector>(COpts); | 
|  | return std::make_unique<IndexAction>(Collector, std::move(IndexOpts), | 
|  | PragmaHandler); | 
|  | } | 
|  |  | 
|  | std::shared_ptr<SymbolCollector> Collector; | 
|  | SymbolCollector::Options COpts; | 
|  | CommentHandler *PragmaHandler; | 
|  | }; | 
|  |  | 
|  | class SymbolCollectorTest : public ::testing::Test { | 
|  | public: | 
|  | SymbolCollectorTest() | 
|  | : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), | 
|  | TestHeaderName(testPath("symbol.h")), | 
|  | TestFileName(testPath("symbol.cc")) { | 
|  | TestHeaderURI = URI::create(TestHeaderName).toString(); | 
|  | TestFileURI = URI::create(TestFileName).toString(); | 
|  | } | 
|  |  | 
|  | // Note that unlike TestTU, no automatic header guard is added. | 
|  | // HeaderCode should start with #pragma once to be treated as modular. | 
|  | bool runSymbolCollector(llvm::StringRef HeaderCode, llvm::StringRef MainCode, | 
|  | const std::vector<std::string> &ExtraArgs = {}) { | 
|  | llvm::IntrusiveRefCntPtr<FileManager> Files( | 
|  | new FileManager(FileSystemOptions(), InMemoryFileSystem)); | 
|  |  | 
|  | auto Factory = std::make_unique<SymbolIndexActionFactory>( | 
|  | CollectorOpts, PragmaHandler.get()); | 
|  |  | 
|  | std::vector<std::string> Args = {"symbol_collector", "-fsyntax-only", | 
|  | "-xc++", "-include", TestHeaderName}; | 
|  | Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); | 
|  | // This allows to override the "-xc++" with something else, i.e. | 
|  | // -xobjective-c++. | 
|  | Args.push_back(TestFileName); | 
|  |  | 
|  | tooling::ToolInvocation Invocation( | 
|  | Args, Factory->create(), Files.get(), | 
|  | std::make_shared<PCHContainerOperations>()); | 
|  |  | 
|  | InMemoryFileSystem->addFile(TestHeaderName, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer(HeaderCode)); | 
|  | InMemoryFileSystem->addFile(TestFileName, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer(MainCode)); | 
|  | Invocation.run(); | 
|  | Symbols = Factory->Collector->takeSymbols(); | 
|  | Refs = Factory->Collector->takeRefs(); | 
|  | Relations = Factory->Collector->takeRelations(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; | 
|  | std::string TestHeaderName; | 
|  | std::string TestHeaderURI; | 
|  | std::string TestFileName; | 
|  | std::string TestFileURI; | 
|  | SymbolSlab Symbols; | 
|  | RefSlab Refs; | 
|  | RelationSlab Relations; | 
|  | SymbolCollector::Options CollectorOpts; | 
|  | std::unique_ptr<CommentHandler> PragmaHandler; | 
|  | }; | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, CollectSymbols) { | 
|  | const std::string Header = R"( | 
|  | class Foo { | 
|  | Foo() {} | 
|  | Foo(int a) {} | 
|  | void f(); | 
|  | friend void f1(); | 
|  | friend class Friend; | 
|  | Foo& operator=(const Foo&); | 
|  | ~Foo(); | 
|  | class Nested { | 
|  | void f(); | 
|  | }; | 
|  | }; | 
|  | class Friend { | 
|  | }; | 
|  |  | 
|  | void f1(); | 
|  | inline void f2() {} | 
|  | static const int KInt = 2; | 
|  | const char* kStr = "123"; | 
|  |  | 
|  | namespace { | 
|  | void ff() {} // ignore | 
|  | } | 
|  |  | 
|  | void f1() { | 
|  | auto LocalLambda = [&](){ | 
|  | class ClassInLambda{}; | 
|  | }; | 
|  | } | 
|  |  | 
|  | namespace foo { | 
|  | // Type alias | 
|  | typedef int int32; | 
|  | using int32_t = int32; | 
|  |  | 
|  | // Variable | 
|  | int v1; | 
|  |  | 
|  | // Namespace | 
|  | namespace bar { | 
|  | int v2; | 
|  | } | 
|  | // Namespace alias | 
|  | namespace baz = bar; | 
|  |  | 
|  | using bar::v2; | 
|  | } // namespace foo | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAreArray( | 
|  | {AllOf(qName("Foo"), forCodeCompletion(true)), | 
|  | AllOf(qName("Foo::Foo"), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::Foo"), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::f"), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::~Foo"), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::operator="), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::Nested"), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::Nested::f"), forCodeCompletion(false)), | 
|  | AllOf(qName("ClassInLambda"), forCodeCompletion(false)), | 
|  | AllOf(qName("Friend"), forCodeCompletion(true)), | 
|  | AllOf(qName("f1"), forCodeCompletion(true)), | 
|  | AllOf(qName("f2"), forCodeCompletion(true)), | 
|  | AllOf(qName("KInt"), forCodeCompletion(true)), | 
|  | AllOf(qName("kStr"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::bar"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::int32"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::int32_t"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::v1"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::bar::v2"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::v2"), forCodeCompletion(true)), | 
|  | AllOf(qName("foo::baz"), forCodeCompletion(true))})); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, FileLocal) { | 
|  | const std::string Header = R"( | 
|  | class Foo {}; | 
|  | namespace { | 
|  | class Ignored {}; | 
|  | } | 
|  | void bar(); | 
|  | )"; | 
|  | const std::string Main = R"( | 
|  | class ForwardDecl; | 
|  | void bar() {} | 
|  | static void a(); | 
|  | class B {}; | 
|  | namespace { | 
|  | void c(); | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, Main); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), visibleOutsideFile()), | 
|  | AllOf(qName("bar"), visibleOutsideFile()), | 
|  | AllOf(qName("a"), Not(visibleOutsideFile())), | 
|  | AllOf(qName("B"), Not(visibleOutsideFile())), | 
|  | AllOf(qName("c"), Not(visibleOutsideFile())), | 
|  | // FIXME: ForwardDecl likely *is* visible outside. | 
|  | AllOf(qName("ForwardDecl"), Not(visibleOutsideFile())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Template) { | 
|  | Annotations Header(R"( | 
|  | // Primary template and explicit specialization are indexed, instantiation | 
|  | // is not. | 
|  | template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;}; | 
|  | template <> struct $specdecl[[Tmpl]]<int, bool> {}; | 
|  | template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {}; | 
|  | extern template struct Tmpl<float, bool>; | 
|  | template struct Tmpl<double, bool>; | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Tmpl"), declRange(Header.range()), | 
|  | forCodeCompletion(true)), | 
|  | AllOf(qName("Tmpl"), declRange(Header.range("specdecl")), | 
|  | forCodeCompletion(false)), | 
|  | AllOf(qName("Tmpl"), declRange(Header.range("partspecdecl")), | 
|  | forCodeCompletion(false)), | 
|  | AllOf(qName("Tmpl::x"), declRange(Header.range("xdecl")), | 
|  | forCodeCompletion(false)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, templateArgs) { | 
|  | Annotations Header(R"( | 
|  | template <class X> class $barclasstemp[[Bar]] {}; | 
|  | template <class T, class U, template<typename> class Z, int Q> | 
|  | struct [[Tmpl]] { T $xdecl[[x]] = 0; }; | 
|  |  | 
|  | // template-template, non-type and type full spec | 
|  | template <> struct $specdecl[[Tmpl]]<int, bool, Bar, 3> {}; | 
|  |  | 
|  | // template-template, non-type and type partial spec | 
|  | template <class U, int T> struct $partspecdecl[[Tmpl]]<bool, U, Bar, T> {}; | 
|  | // instantiation | 
|  | extern template struct Tmpl<float, bool, Bar, 8>; | 
|  | // instantiation | 
|  | template struct Tmpl<double, bool, Bar, 2>; | 
|  |  | 
|  | template <typename ...> class $fooclasstemp[[Foo]] {}; | 
|  | // parameter-packs full spec | 
|  | template<> class $parampack[[Foo]]<Bar<int>, int, double> {}; | 
|  | // parameter-packs partial spec | 
|  | template<class T> class $parampackpartial[[Foo]]<T, T> {}; | 
|  |  | 
|  | template <int ...> class $bazclasstemp[[Baz]] {}; | 
|  | // non-type parameter-packs full spec | 
|  | template<> class $parampacknontype[[Baz]]<3, 5, 8> {}; | 
|  | // non-type parameter-packs partial spec | 
|  | template<int T> class $parampacknontypepartial[[Baz]]<T, T> {}; | 
|  |  | 
|  | template <template <class> class ...> class $fozclasstemp[[Foz]] {}; | 
|  | // template-template parameter-packs full spec | 
|  | template<> class $parampacktempltempl[[Foz]]<Bar, Bar> {}; | 
|  | // template-template parameter-packs partial spec | 
|  | template<template <class> class T> | 
|  | class $parampacktempltemplpartial[[Foz]]<T, T> {}; | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | AllOf( | 
|  | Contains(AllOf(qName("Tmpl"), templateArgs("<int, bool, Bar, 3>"), | 
|  | declRange(Header.range("specdecl")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Tmpl"), templateArgs("<bool, U, Bar, T>"), | 
|  | declRange(Header.range("partspecdecl")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Foo"), templateArgs("<Bar<int>, int, double>"), | 
|  | declRange(Header.range("parampack")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Foo"), templateArgs("<T, T>"), | 
|  | declRange(Header.range("parampackpartial")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Baz"), templateArgs("<3, 5, 8>"), | 
|  | declRange(Header.range("parampacknontype")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Baz"), templateArgs("<T, T>"), | 
|  | declRange(Header.range("parampacknontypepartial")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Foz"), templateArgs("<Bar, Bar>"), | 
|  | declRange(Header.range("parampacktempltempl")), | 
|  | forCodeCompletion(false))), | 
|  | Contains(AllOf(qName("Foz"), templateArgs("<T, T>"), | 
|  | declRange(Header.range("parampacktempltemplpartial")), | 
|  | forCodeCompletion(false))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ObjCSymbols) { | 
|  | const std::string Header = R"( | 
|  | @interface Person | 
|  | - (void)someMethodName:(void*)name1 lastName:(void*)lName; | 
|  | @end | 
|  |  | 
|  | @implementation Person | 
|  | - (void)someMethodName:(void*)name1 lastName:(void*)lName{ | 
|  | int foo; | 
|  | ^(int param){ int bar; }; | 
|  | } | 
|  | @end | 
|  |  | 
|  | @interface Person (MyCategory) | 
|  | - (void)someMethodName2:(void*)name2; | 
|  | @end | 
|  |  | 
|  | @implementation Person (MyCategory) | 
|  | - (void)someMethodName2:(void*)name2 { | 
|  | int foo2; | 
|  | } | 
|  | @end | 
|  |  | 
|  | @protocol MyProtocol | 
|  | - (void)someMethodName3:(void*)name3; | 
|  | @end | 
|  | )"; | 
|  | TestFileName = testPath("test.m"); | 
|  | runSymbolCollector(Header, /*Main=*/"", {"-fblocks", "-xobjective-c++"}); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("Person"), qName("Person::someMethodName:lastName:"), | 
|  | AllOf(qName("MyCategory"), forCodeCompletion(false)), | 
|  | qName("Person::someMethodName2:"), qName("MyProtocol"), | 
|  | qName("MyProtocol::someMethodName3:"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ObjCPropertyImpl) { | 
|  | const std::string Header = R"( | 
|  | @interface Container | 
|  | @property(nonatomic) int magic; | 
|  | @end | 
|  |  | 
|  | @implementation Container | 
|  | @end | 
|  | )"; | 
|  | TestFileName = testPath("test.m"); | 
|  | runSymbolCollector(Header, /*Main=*/"", {"-xobjective-c++"}); | 
|  | EXPECT_THAT(Symbols, Contains(qName("Container"))); | 
|  | EXPECT_THAT(Symbols, Contains(qName("Container::magic"))); | 
|  | // FIXME: Results also contain Container::_magic on some platforms. | 
|  | //        Figure out why it's platform-dependent. | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ObjCLocations) { | 
|  | Annotations Header(R"( | 
|  | // Declared in header, defined in main. | 
|  | @interface $dogdecl[[Dog]] | 
|  | @end | 
|  | @interface $fluffydecl[[Dog]] (Fluffy) | 
|  | @end | 
|  | )"); | 
|  | Annotations Main(R"( | 
|  | @interface Dog () | 
|  | @end | 
|  | @implementation $dogdef[[Dog]] | 
|  | @end | 
|  | @implementation $fluffydef[[Dog]] (Fluffy) | 
|  | @end | 
|  | // Category with no declaration (only implementation). | 
|  | @implementation $ruff[[Dog]] (Ruff) | 
|  | @end | 
|  | // Implicitly defined interface. | 
|  | @implementation $catdog[[CatDog]] | 
|  | @end | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), Main.code(), | 
|  | {"-xobjective-c++", "-Wno-objc-root-class"}); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Dog"), declRange(Header.range("dogdecl")), | 
|  | defRange(Main.range("dogdef"))), | 
|  | AllOf(qName("Fluffy"), declRange(Header.range("fluffydecl")), | 
|  | defRange(Main.range("fluffydef"))), | 
|  | AllOf(qName("CatDog"), declRange(Main.range("catdog")), | 
|  | defRange(Main.range("catdog"))), | 
|  | AllOf(qName("Ruff"), declRange(Main.range("ruff")), | 
|  | defRange(Main.range("ruff"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ObjCForwardDecls) { | 
|  | Annotations Header(R"( | 
|  | // Forward declared in header, declared and defined in main. | 
|  | @protocol Barker; | 
|  | @class Dog; | 
|  | // Never fully declared so Clang latches onto this decl. | 
|  | @class $catdogdecl[[CatDog]]; | 
|  | )"); | 
|  | Annotations Main(R"( | 
|  | @protocol $barkerdecl[[Barker]] | 
|  | - (void)woof; | 
|  | @end | 
|  | @interface $dogdecl[[Dog]]<Barker> | 
|  | - (void)woof; | 
|  | @end | 
|  | @implementation $dogdef[[Dog]] | 
|  | - (void)woof {} | 
|  | @end | 
|  | @implementation $catdogdef[[CatDog]] | 
|  | @end | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), Main.code(), | 
|  | {"-xobjective-c++", "-Wno-objc-root-class"}); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("CatDog"), declRange(Header.range("catdogdecl")), | 
|  | defRange(Main.range("catdogdef"))), | 
|  | AllOf(qName("Dog"), declRange(Main.range("dogdecl")), | 
|  | defRange(Main.range("dogdef"))), | 
|  | AllOf(qName("Barker"), declRange(Main.range("barkerdecl"))), | 
|  | qName("Barker::woof"), qName("Dog::woof"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ObjCClassExtensions) { | 
|  | Annotations Header(R"( | 
|  | @interface $catdecl[[Cat]] | 
|  | @end | 
|  | )"); | 
|  | Annotations Main(R"( | 
|  | @interface Cat () | 
|  | - (void)meow; | 
|  | @end | 
|  | @interface Cat () | 
|  | - (void)pur; | 
|  | @end | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), Main.code(), | 
|  | {"-xobjective-c++", "-Wno-objc-root-class"}); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Cat"), declRange(Header.range("catdecl"))), | 
|  | qName("Cat::meow"), qName("Cat::pur"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Locations) { | 
|  | Annotations Header(R"cpp( | 
|  | // Declared in header, defined in main. | 
|  | extern int $xdecl[[X]]; | 
|  | class $clsdecl[[Cls]]; | 
|  | void $printdecl[[print]](); | 
|  |  | 
|  | // Declared in header, defined nowhere. | 
|  | extern int $zdecl[[Z]]; | 
|  |  | 
|  | void $foodecl[[fo\ | 
|  | o]](); | 
|  | )cpp"); | 
|  | Annotations Main(R"cpp( | 
|  | int $xdef[[X]] = 42; | 
|  | class $clsdef[[Cls]] {}; | 
|  | void $printdef[[print]]() {} | 
|  |  | 
|  | // Declared/defined in main only. | 
|  | int $ydecl[[Y]]; | 
|  | )cpp"); | 
|  | runSymbolCollector(Header.code(), Main.code()); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("X"), declRange(Header.range("xdecl")), | 
|  | defRange(Main.range("xdef"))), | 
|  | AllOf(qName("Cls"), declRange(Header.range("clsdecl")), | 
|  | defRange(Main.range("clsdef"))), | 
|  | AllOf(qName("print"), declRange(Header.range("printdecl")), | 
|  | defRange(Main.range("printdef"))), | 
|  | AllOf(qName("Z"), declRange(Header.range("zdecl"))), | 
|  | AllOf(qName("foo"), declRange(Header.range("foodecl"))), | 
|  | AllOf(qName("Y"), declRange(Main.range("ydecl"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Refs) { | 
|  | Annotations Header(R"( | 
|  | #define MACRO(X) (X + 1) | 
|  | class Foo { | 
|  | public: | 
|  | Foo() {} | 
|  | Foo(int); | 
|  | }; | 
|  | class Bar; | 
|  | void func(); | 
|  |  | 
|  | namespace NS {} // namespace ref is ignored | 
|  | )"); | 
|  | Annotations Main(R"( | 
|  | class $bar[[Bar]] {}; | 
|  |  | 
|  | void $func[[func]](); | 
|  |  | 
|  | void fff() { | 
|  | $foo[[Foo]] foo; | 
|  | $bar[[Bar]] bar; | 
|  | $func[[func]](); | 
|  | int abc = 0; | 
|  | $foo[[Foo]] foo2 = abc; | 
|  | abc = $macro[[MACRO]](1); | 
|  | } | 
|  | )"); | 
|  | Annotations SymbolsOnlyInMainCode(R"( | 
|  | #define FUNC(X) (X+1) | 
|  | int a; | 
|  | void b() {} | 
|  | static const int c = FUNC(1); | 
|  | class d {}; | 
|  | )"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.CollectMacro = true; | 
|  | runSymbolCollector(Header.code(), | 
|  | (Main.code() + SymbolsOnlyInMainCode.code()).str()); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Main.ranges("foo"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Bar").ID, | 
|  | haveRanges(Main.ranges("bar"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "func").ID, | 
|  | haveRanges(Main.ranges("func"))))); | 
|  | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "NS").ID, _)))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO").ID, | 
|  | haveRanges(Main.ranges("macro"))))); | 
|  | // - (a, b) externally visible and should have refs. | 
|  | // - (c, FUNC) externally invisible and had no refs collected. | 
|  | auto MainSymbols = | 
|  | TestTU::withHeaderCode(SymbolsOnlyInMainCode.code()).headerSymbols(); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "a").ID, _))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(MainSymbols, "b").ID, _))); | 
|  | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "c").ID, _)))); | 
|  | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "FUNC").ID, _)))); | 
|  |  | 
|  | // Run the collector again with CollectMainFileRefs = true. | 
|  | // We need to recreate InMemoryFileSystem because runSymbolCollector() | 
|  | // calls MemoryBuffer::getMemBuffer(), which makes the buffers unusable | 
|  | // after runSymbolCollector() exits. | 
|  | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem(); | 
|  | CollectorOpts.CollectMainFileRefs = true; | 
|  | runSymbolCollector(Header.code(), | 
|  | (Main.code() + SymbolsOnlyInMainCode.code()).str()); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "a").ID, _))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "b").ID, _))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "c").ID, _))); | 
|  | // However, references to main-file macros are not collected. | 
|  | EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(Symbols, "FUNC").ID, _)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, RefContainers) { | 
|  | Annotations Code(R"cpp( | 
|  | int $toplevel1[[f1]](int); | 
|  | void f2() { | 
|  | (void) $ref1a[[f1]](1); | 
|  | auto fptr = &$ref1b[[f1]]; | 
|  | } | 
|  | int $toplevel2[[v1]] = $ref2[[f1]](2); | 
|  | void f3(int arg = $ref3[[f1]](3)); | 
|  | struct S1 { | 
|  | int $classscope1[[member1]] = $ref4[[f1]](4); | 
|  | int $classscope2[[member2]] = 42; | 
|  | }; | 
|  | constexpr int f4(int x) { return x + 1; } | 
|  | template <int I = $ref5[[f4]](0)> struct S2 {}; | 
|  | S2<$ref6[[f4]](0)> v2; | 
|  | S2<$ref7a[[f4]](0)> f5(S2<$ref7b[[f4]](0)>); | 
|  | namespace N { | 
|  | void $namespacescope1[[f6]](); | 
|  | int $namespacescope2[[v3]]; | 
|  | } | 
|  | )cpp"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.CollectMainFileRefs = true; | 
|  | runSymbolCollector("", Code.code()); | 
|  | auto FindRefWithRange = [&](Range R) -> Optional<Ref> { | 
|  | for (auto &Entry : Refs) { | 
|  | for (auto &Ref : Entry.second) { | 
|  | if (rangesMatch(Ref.Location, R)) | 
|  | return Ref; | 
|  | } | 
|  | } | 
|  | return llvm::None; | 
|  | }; | 
|  | auto Container = [&](llvm::StringRef RangeName) { | 
|  | auto Ref = FindRefWithRange(Code.range(RangeName)); | 
|  | EXPECT_TRUE(bool(Ref)); | 
|  | return Ref->Container; | 
|  | }; | 
|  | EXPECT_EQ(Container("ref1a"), | 
|  | findSymbol(Symbols, "f2").ID); // function body (call) | 
|  | EXPECT_EQ(Container("ref1b"), | 
|  | findSymbol(Symbols, "f2").ID); // function body (address-of) | 
|  | EXPECT_EQ(Container("ref2"), | 
|  | findSymbol(Symbols, "v1").ID); // variable initializer | 
|  | EXPECT_EQ(Container("ref3"), | 
|  | findSymbol(Symbols, "f3").ID); // function parameter default value | 
|  | EXPECT_EQ(Container("ref4"), | 
|  | findSymbol(Symbols, "S1::member1").ID); // member initializer | 
|  | EXPECT_EQ(Container("ref5"), | 
|  | findSymbol(Symbols, "S2").ID); // template parameter default value | 
|  | EXPECT_EQ(Container("ref6"), | 
|  | findSymbol(Symbols, "v2").ID); // type of variable | 
|  | EXPECT_EQ(Container("ref7a"), | 
|  | findSymbol(Symbols, "f5").ID); // return type of function | 
|  | EXPECT_EQ(Container("ref7b"), | 
|  | findSymbol(Symbols, "f5").ID); // parameter type of function | 
|  |  | 
|  | EXPECT_FALSE(Container("classscope1").isNull()); | 
|  | EXPECT_FALSE(Container("namespacescope1").isNull()); | 
|  |  | 
|  | EXPECT_EQ(Container("toplevel1"), Container("toplevel2")); | 
|  | EXPECT_EQ(Container("classscope1"), Container("classscope2")); | 
|  | EXPECT_EQ(Container("namespacescope1"), Container("namespacescope2")); | 
|  |  | 
|  | EXPECT_NE(Container("toplevel1"), Container("namespacescope1")); | 
|  | EXPECT_NE(Container("toplevel1"), Container("classscope1")); | 
|  | EXPECT_NE(Container("classscope1"), Container("namespacescope1")); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, MacroRefInHeader) { | 
|  | Annotations Header(R"( | 
|  | #define $foo[[FOO]](X) (X + 1) | 
|  | #define $bar[[BAR]](X) (X + 2) | 
|  |  | 
|  | // Macro defined multiple times. | 
|  | #define $ud1[[UD]] 1 | 
|  | int ud_1 = $ud1[[UD]]; | 
|  | #undef UD | 
|  |  | 
|  | #define $ud2[[UD]] 2 | 
|  | int ud_2 = $ud2[[UD]]; | 
|  | #undef UD | 
|  |  | 
|  | // Macros from token concatenations not included. | 
|  | #define $concat[[CONCAT]](X) X##A() | 
|  | #define $prepend[[PREPEND]](X) MACRO##X() | 
|  | #define $macroa[[MACROA]]() 123 | 
|  | int B = $concat[[CONCAT]](MACRO); | 
|  | int D = $prepend[[PREPEND]](A); | 
|  |  | 
|  | void fff() { | 
|  | int abc = $foo[[FOO]](1) + $bar[[BAR]]($foo[[FOO]](1)); | 
|  | } | 
|  | )"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | // Need this to get the SymbolID for macros for tests. | 
|  | CollectorOpts.CollectMacro = true; | 
|  |  | 
|  | runSymbolCollector(Header.code(), ""); | 
|  |  | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "FOO").ID, | 
|  | haveRanges(Header.ranges("foo"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "BAR").ID, | 
|  | haveRanges(Header.ranges("bar"))))); | 
|  | // No unique ID for multiple symbols named UD. Check for ranges only. | 
|  | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud1"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("ud2"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "CONCAT").ID, | 
|  | haveRanges(Header.ranges("concat"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PREPEND").ID, | 
|  | haveRanges(Header.ranges("prepend"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACROA").ID, | 
|  | haveRanges(Header.ranges("macroa"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, MacroRefWithoutCollectingSymbol) { | 
|  | Annotations Header(R"( | 
|  | #define $foo[[FOO]](X) (X + 1) | 
|  | int abc = $foo[[FOO]](1); | 
|  | )"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | CollectorOpts.CollectMacro = false; | 
|  | runSymbolCollector(Header.code(), ""); | 
|  | EXPECT_THAT(Refs, Contains(Pair(_, haveRanges(Header.ranges("foo"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, MacrosWithRefFilter) { | 
|  | Annotations Header("#define $macro[[MACRO]](X) (X + 1)"); | 
|  | Annotations Main("void foo() { int x = $macro[[MACRO]](1); }"); | 
|  | CollectorOpts.RefFilter = RefKind::Unknown; | 
|  | runSymbolCollector(Header.code(), Main.code()); | 
|  | EXPECT_THAT(Refs, IsEmpty()); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SpelledReferences) { | 
|  | struct { | 
|  | llvm::StringRef Header; | 
|  | llvm::StringRef Main; | 
|  | llvm::StringRef TargetSymbolName; | 
|  | } TestCases[] = { | 
|  | { | 
|  | R"cpp( | 
|  | struct Foo; | 
|  | #define MACRO Foo | 
|  | )cpp", | 
|  | R"cpp( | 
|  | struct $spelled[[Foo]] { | 
|  | $spelled[[Foo]](); | 
|  | ~$spelled[[Foo]](); | 
|  | }; | 
|  | $spelled[[Foo]] Variable1; | 
|  | $implicit[[MACRO]] Variable2; | 
|  | )cpp", | 
|  | "Foo", | 
|  | }, | 
|  | { | 
|  | R"cpp( | 
|  | class Foo { | 
|  | public: | 
|  | Foo() = default; | 
|  | }; | 
|  | )cpp", | 
|  | R"cpp( | 
|  | void f() { Foo $implicit[[f]]; f = $spelled[[Foo]]();} | 
|  | )cpp", | 
|  | "Foo::Foo" /// constructor. | 
|  | }, | 
|  | }; | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = false; | 
|  | for (const auto& T : TestCases) { | 
|  | Annotations Header(T.Header); | 
|  | Annotations Main(T.Main); | 
|  | // Reset the file system. | 
|  | InMemoryFileSystem = new llvm::vfs::InMemoryFileSystem; | 
|  | runSymbolCollector(Header.code(), Main.code()); | 
|  |  | 
|  | const auto SpelledRanges = Main.ranges("spelled"); | 
|  | const auto ImplicitRanges = Main.ranges("implicit"); | 
|  | RefSlab::Builder SpelledSlabBuilder, ImplicitSlabBuilder; | 
|  | const auto TargetID = findSymbol(Symbols, T.TargetSymbolName).ID; | 
|  | for (const auto &SymbolAndRefs : Refs) { | 
|  | const auto ID = SymbolAndRefs.first; | 
|  | if (ID != TargetID) | 
|  | continue; | 
|  | for (const auto &Ref : SymbolAndRefs.second) | 
|  | if ((Ref.Kind & RefKind::Spelled) != RefKind::Unknown) | 
|  | SpelledSlabBuilder.insert(ID, Ref); | 
|  | else | 
|  | ImplicitSlabBuilder.insert(ID, Ref); | 
|  | } | 
|  | const auto SpelledRefs = std::move(SpelledSlabBuilder).build(), | 
|  | ImplicitRefs = std::move(ImplicitSlabBuilder).build(); | 
|  | EXPECT_THAT(SpelledRefs, | 
|  | Contains(Pair(TargetID, haveRanges(SpelledRanges)))); | 
|  | EXPECT_THAT(ImplicitRefs, | 
|  | Contains(Pair(TargetID, haveRanges(ImplicitRanges)))); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, NameReferences) { | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | Annotations Header(R"( | 
|  | class [[Foo]] { | 
|  | public: | 
|  | [[Foo]]() {} | 
|  | ~[[Foo]]() {} | 
|  | }; | 
|  | )"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | runSymbolCollector(Header.code(), ""); | 
|  | // When we find references for class Foo, we expect to see all | 
|  | // constructor/destructor references. | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, RefsOnMacros) { | 
|  | // Refs collected from SymbolCollector behave in the same way as | 
|  | // AST-based xrefs. | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | Annotations Header(R"( | 
|  | #define TYPE(X) X | 
|  | #define FOO Foo | 
|  | #define CAT(X, Y) X##Y | 
|  | class [[Foo]] {}; | 
|  | void test() { | 
|  | TYPE([[Foo]]) foo; | 
|  | [[FOO]] foo2; | 
|  | TYPE(TYPE([[Foo]])) foo3; | 
|  | [[CAT]](Fo, o) foo4; | 
|  | } | 
|  | )"); | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | runSymbolCollector(Header.code(), ""); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, HeaderAsMainFile) { | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | Annotations Header(R"( | 
|  | class $Foo[[Foo]] {}; | 
|  |  | 
|  | void $Func[[Func]]() { | 
|  | $Foo[[Foo]] fo; | 
|  | } | 
|  | )"); | 
|  | // We should collect refs to main-file symbols in all cases: | 
|  |  | 
|  | // 1. The main file is normal .cpp file. | 
|  | TestFileName = testPath("foo.cpp"); | 
|  | runSymbolCollector("", Header.code()); | 
|  | EXPECT_THAT(Refs, | 
|  | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges("Foo"))), | 
|  | Pair(findSymbol(Symbols, "Func").ID, | 
|  | haveRanges(Header.ranges("Func"))))); | 
|  |  | 
|  | // 2. Run the .h file as main file. | 
|  | TestFileName = testPath("foo.h"); | 
|  | runSymbolCollector("", Header.code(), | 
|  | /*ExtraArgs=*/{"-xobjective-c++-header"}); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo"), qName("Func"))); | 
|  | EXPECT_THAT(Refs, | 
|  | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges("Foo"))), | 
|  | Pair(findSymbol(Symbols, "Func").ID, | 
|  | haveRanges(Header.ranges("Func"))))); | 
|  |  | 
|  | // 3. Run the .hh file as main file (without "-x c++-header"). | 
|  | TestFileName = testPath("foo.hh"); | 
|  | runSymbolCollector("", Header.code()); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo"), qName("Func"))); | 
|  | EXPECT_THAT(Refs, | 
|  | UnorderedElementsAre(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges("Foo"))), | 
|  | Pair(findSymbol(Symbols, "Func").ID, | 
|  | haveRanges(Header.ranges("Func"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, RefsInHeaders) { | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | CollectorOpts.CollectMacro = true; | 
|  | Annotations Header(R"( | 
|  | #define $macro[[MACRO]](x) (x+1) | 
|  | class $foo[[Foo]] {}; | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), ""); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, | 
|  | haveRanges(Header.ranges("foo"))))); | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "MACRO").ID, | 
|  | haveRanges(Header.ranges("macro"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, BaseOfRelations) { | 
|  | std::string Header = R"( | 
|  | class Base {}; | 
|  | class Derived : public Base {}; | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | const Symbol &Base = findSymbol(Symbols, "Base"); | 
|  | const Symbol &Derived = findSymbol(Symbols, "Derived"); | 
|  | EXPECT_THAT(Relations, | 
|  | Contains(Relation{Base.ID, RelationKind::BaseOf, Derived.ID})); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, OverrideRelationsSimpleInheritance) { | 
|  | std::string Header = R"cpp( | 
|  | class A { | 
|  | virtual void foo(); | 
|  | }; | 
|  | class B : public A { | 
|  | void foo() override;  // A::foo | 
|  | virtual void bar(); | 
|  | }; | 
|  | class C : public B { | 
|  | void bar() override;  // B::bar | 
|  | }; | 
|  | class D: public C { | 
|  | void foo() override;  // B::foo | 
|  | void bar() override;  // C::bar | 
|  | }; | 
|  | )cpp"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | const Symbol &AFoo = findSymbol(Symbols, "A::foo"); | 
|  | const Symbol &BFoo = findSymbol(Symbols, "B::foo"); | 
|  | const Symbol &DFoo = findSymbol(Symbols, "D::foo"); | 
|  |  | 
|  | const Symbol &BBar = findSymbol(Symbols, "B::bar"); | 
|  | const Symbol &CBar = findSymbol(Symbols, "C::bar"); | 
|  | const Symbol &DBar = findSymbol(Symbols, "D::bar"); | 
|  |  | 
|  | std::vector<Relation> Result; | 
|  | for (const Relation &R : Relations) | 
|  | if (R.Predicate == RelationKind::OverriddenBy) | 
|  | Result.push_back(R); | 
|  | EXPECT_THAT(Result, UnorderedElementsAre( | 
|  | OverriddenBy(AFoo, BFoo), OverriddenBy(BBar, CBar), | 
|  | OverriddenBy(BFoo, DFoo), OverriddenBy(CBar, DBar))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, OverrideRelationsMultipleInheritance) { | 
|  | std::string Header = R"cpp( | 
|  | class A { | 
|  | virtual void foo(); | 
|  | }; | 
|  | class B { | 
|  | virtual void bar(); | 
|  | }; | 
|  | class C : public B { | 
|  | void bar() override;  // B::bar | 
|  | virtual void baz(); | 
|  | } | 
|  | class D : public A, C { | 
|  | void foo() override;  // A::foo | 
|  | void bar() override;  // C::bar | 
|  | void baz() override;  // C::baz | 
|  | }; | 
|  | )cpp"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | const Symbol &AFoo = findSymbol(Symbols, "A::foo"); | 
|  | const Symbol &BBar = findSymbol(Symbols, "B::bar"); | 
|  | const Symbol &CBar = findSymbol(Symbols, "C::bar"); | 
|  | const Symbol &CBaz = findSymbol(Symbols, "C::baz"); | 
|  | const Symbol &DFoo = findSymbol(Symbols, "D::foo"); | 
|  | const Symbol &DBar = findSymbol(Symbols, "D::bar"); | 
|  | const Symbol &DBaz = findSymbol(Symbols, "D::baz"); | 
|  |  | 
|  | std::vector<Relation> Result; | 
|  | for (const Relation &R : Relations) | 
|  | if (R.Predicate == RelationKind::OverriddenBy) | 
|  | Result.push_back(R); | 
|  | EXPECT_THAT(Result, UnorderedElementsAre( | 
|  | OverriddenBy(BBar, CBar), OverriddenBy(AFoo, DFoo), | 
|  | OverriddenBy(CBar, DBar), OverriddenBy(CBaz, DBaz))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, CountReferences) { | 
|  | const std::string Header = R"( | 
|  | class W; | 
|  | class X {}; | 
|  | class Y; | 
|  | class Z {}; // not used anywhere | 
|  | Y* y = nullptr;  // used in header doesn't count | 
|  | #define GLOBAL_Z(name) Z name; | 
|  | )"; | 
|  | const std::string Main = R"( | 
|  | W* w = nullptr; | 
|  | W* w2 = nullptr; // only one usage counts | 
|  | X x(); | 
|  | class V; | 
|  | class Y{}; // definition doesn't count as a reference | 
|  | V* v = nullptr; | 
|  | GLOBAL_Z(z); // Not a reference to Z, we don't spell the type. | 
|  | )"; | 
|  | CollectorOpts.CountReferences = true; | 
|  | runSymbolCollector(Header, Main); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAreArray( | 
|  | {AllOf(qName("W"), refCount(1)), AllOf(qName("X"), refCount(1)), | 
|  | AllOf(qName("Y"), refCount(0)), AllOf(qName("Z"), refCount(0)), | 
|  | AllOf(qName("y"), refCount(0)), AllOf(qName("z"), refCount(0)), | 
|  | AllOf(qName("x"), refCount(0)), AllOf(qName("w"), refCount(0)), | 
|  | AllOf(qName("w2"), refCount(0)), AllOf(qName("V"), refCount(1)), | 
|  | AllOf(qName("v"), refCount(0))})); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolRelativeNoFallback) { | 
|  | runSymbolCollector("class Foo {};", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI(TestHeaderURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolRelativeWithFallback) { | 
|  | TestHeaderName = "x.h"; | 
|  | TestFileName = "x.cpp"; | 
|  | TestHeaderURI = URI::create(testPath(TestHeaderName)).toString(); | 
|  | CollectorOpts.FallbackDir = testRoot(); | 
|  | runSymbolCollector("class Foo {};", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI(TestHeaderURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, UnittestURIScheme) { | 
|  | // Use test URI scheme from URITests.cpp | 
|  | TestHeaderName = testPath("x.h"); | 
|  | TestFileName = testPath("x.cpp"); | 
|  | runSymbolCollector("class Foo {};", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI("unittest:///x.h")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, IncludeEnums) { | 
|  | const std::string Header = R"( | 
|  | enum { | 
|  | Red | 
|  | }; | 
|  | enum Color { | 
|  | Green | 
|  | }; | 
|  | enum class Color2 { | 
|  | Yellow | 
|  | }; | 
|  | namespace ns { | 
|  | enum { | 
|  | Black | 
|  | }; | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Red"), forCodeCompletion(true)), | 
|  | AllOf(qName("Color"), forCodeCompletion(true)), | 
|  | AllOf(qName("Green"), forCodeCompletion(true)), | 
|  | AllOf(qName("Color2"), forCodeCompletion(true)), | 
|  | AllOf(qName("Color2::Yellow"), forCodeCompletion(false)), | 
|  | AllOf(qName("ns"), forCodeCompletion(true)), | 
|  | AllOf(qName("ns::Black"), forCodeCompletion(true)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, NamelessSymbols) { | 
|  | const std::string Header = R"( | 
|  | struct { | 
|  | int a; | 
|  | } Foo; | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("Foo"), | 
|  | qName("(anonymous struct)::a"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolFormedFromRegisteredSchemeFromMacro) { | 
|  |  | 
|  | Annotations Header(R"( | 
|  | #define FF(name) \ | 
|  | class name##_Test {}; | 
|  |  | 
|  | $expansion[[FF]](abc); | 
|  |  | 
|  | #define FF2() \ | 
|  | class $spelling[[Test]] {}; | 
|  |  | 
|  | FF2(); | 
|  | )"); | 
|  |  | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("abc_Test"), declRange(Header.range("expansion")), | 
|  | declURI(TestHeaderURI)), | 
|  | AllOf(qName("Test"), declRange(Header.range("spelling")), | 
|  | declURI(TestHeaderURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolFormedByCLI) { | 
|  | Annotations Header(R"( | 
|  | #ifdef NAME | 
|  | class $expansion[[NAME]] {}; | 
|  | #endif | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/"", /*ExtraArgs=*/{"-DNAME=name"}); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( | 
|  | qName("name"), declRange(Header.range("expansion")), | 
|  | declURI(TestHeaderURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolsInMainFile) { | 
|  | const std::string Main = R"( | 
|  | class Foo {}; | 
|  | void f1(); | 
|  | inline void f2() {} | 
|  |  | 
|  | namespace { | 
|  | void ff() {} | 
|  | } | 
|  | namespace foo { | 
|  | namespace { | 
|  | class Bar {}; | 
|  | } | 
|  | } | 
|  | void main_f() {} | 
|  | void f1() {} | 
|  | )"; | 
|  | runSymbolCollector(/*Header=*/"", Main); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | qName("Foo"), qName("f1"), qName("f2"), qName("ff"), | 
|  | qName("foo"), qName("foo::Bar"), qName("main_f"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Documentation) { | 
|  | const std::string Header = R"( | 
|  | // doc Foo | 
|  | class Foo { | 
|  | // doc f | 
|  | int f(); | 
|  | }; | 
|  | )"; | 
|  | CollectorOpts.StoreAllDocumentation = false; | 
|  | runSymbolCollector(Header, /* Main */ ""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), doc("doc Foo"), forCodeCompletion(true)), | 
|  | AllOf(qName("Foo::f"), doc(""), returnType(""), | 
|  | forCodeCompletion(false)))); | 
|  |  | 
|  | CollectorOpts.StoreAllDocumentation = true; | 
|  | runSymbolCollector(Header, /* Main */ ""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), doc("doc Foo"), forCodeCompletion(true)), | 
|  | AllOf(qName("Foo::f"), doc("doc f"), returnType(""), | 
|  | forCodeCompletion(false)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ClassMembers) { | 
|  | const std::string Header = R"( | 
|  | class Foo { | 
|  | void f() {} | 
|  | void g(); | 
|  | static void sf() {} | 
|  | static void ssf(); | 
|  | static int x; | 
|  | }; | 
|  | )"; | 
|  | const std::string Main = R"( | 
|  | void Foo::g() {} | 
|  | void Foo::ssf() {} | 
|  | )"; | 
|  | runSymbolCollector(Header, Main); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("Foo"), | 
|  | AllOf(qName("Foo::f"), returnType(""), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::g"), returnType(""), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::sf"), returnType(""), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::ssf"), returnType(""), forCodeCompletion(false)), | 
|  | AllOf(qName("Foo::x"), returnType(""), forCodeCompletion(false)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Scopes) { | 
|  | const std::string Header = R"( | 
|  | namespace na { | 
|  | class Foo {}; | 
|  | namespace nb { | 
|  | class Bar {}; | 
|  | } | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre(qName("na"), qName("na::nb"), | 
|  | qName("na::Foo"), qName("na::nb::Bar"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ExternC) { | 
|  | const std::string Header = R"( | 
|  | extern "C" { class Foo {}; } | 
|  | namespace na { | 
|  | extern "C" { class Bar {}; } | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("na"), qName("Foo"), | 
|  | qName("na::Bar"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SkipInlineNamespace) { | 
|  | const std::string Header = R"( | 
|  | namespace na { | 
|  | inline namespace nb { | 
|  | class Foo {}; | 
|  | } | 
|  | } | 
|  | namespace na { | 
|  | // This is still inlined. | 
|  | namespace nb { | 
|  | class Bar {}; | 
|  | } | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre(qName("na"), qName("na::nb"), | 
|  | qName("na::Foo"), qName("na::Bar"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { | 
|  | const std::string Header = R"( | 
|  | namespace nx { | 
|  | /// Foo comment. | 
|  | int ff(int x, double y) { return 0; } | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("nx"), AllOf(qName("nx::ff"), labeled("ff(int x, double y)"), | 
|  | returnType("int"), doc("Foo comment.")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, snippet) { | 
|  | const std::string Header = R"( | 
|  | namespace nx { | 
|  | void f() {} | 
|  | int ff(int x, double y) { return 0; } | 
|  | } | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("nx"), | 
|  | AllOf(qName("nx::f"), labeled("f()"), snippet("f()")), | 
|  | AllOf(qName("nx::ff"), labeled("ff(int x, double y)"), | 
|  | snippet("ff(${1:int x}, ${2:double y})")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | runSymbolCollector("#pragma once\nclass Foo {};", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI(TestHeaderURI)))); | 
|  | EXPECT_THAT(Symbols.begin()->IncludeHeaders, | 
|  | UnorderedElementsAre(IncludeHeaderWithRef(TestHeaderURI, 1u))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | CanonicalIncludes Includes; | 
|  | auto Language = LangOptions(); | 
|  | Language.CPlusPlus = true; | 
|  | Includes.addSystemHeadersMapping(Language); | 
|  | CollectorOpts.Includes = &Includes; | 
|  | runSymbolCollector( | 
|  | R"cpp( | 
|  | namespace std { | 
|  | class string {}; | 
|  | // Move overloads have special handling. | 
|  | template <typename _T> T&& move(_T&& __value); | 
|  | template <typename _I, typename _O> _O move(_I, _I, _O); | 
|  | } | 
|  | )cpp", | 
|  | /*Main=*/""); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("std"), | 
|  | AllOf(qName("std::string"), declURI(TestHeaderURI), | 
|  | includeHeader("<string>")), | 
|  | // Parameter names are demangled. | 
|  | AllOf(labeled("move(T &&value)"), includeHeader("<utility>")), | 
|  | AllOf(labeled("move(I, I, O)"), includeHeader("<algorithm>")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, IWYUPragma) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | CanonicalIncludes Includes; | 
|  | PragmaHandler = collectIWYUHeaderMaps(&Includes); | 
|  | CollectorOpts.Includes = &Includes; | 
|  | const std::string Header = R"( | 
|  | // IWYU pragma: private, include the/good/header.h | 
|  | class Foo {}; | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI(TestHeaderURI), | 
|  | includeHeader("\"the/good/header.h\"")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, IWYUPragmaWithDoubleQuotes) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | CanonicalIncludes Includes; | 
|  | PragmaHandler = collectIWYUHeaderMaps(&Includes); | 
|  | CollectorOpts.Includes = &Includes; | 
|  | const std::string Header = R"( | 
|  | // IWYU pragma: private, include "the/good/header.h" | 
|  | class Foo {}; | 
|  | )"; | 
|  | runSymbolCollector(Header, /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("Foo"), declURI(TestHeaderURI), | 
|  | includeHeader("\"the/good/header.h\"")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, SkipIncFileWhenCanonicalizeHeaders) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | CanonicalIncludes Includes; | 
|  | Includes.addMapping(TestHeaderName, "<canonical>"); | 
|  | CollectorOpts.Includes = &Includes; | 
|  | auto IncFile = testPath("test.inc"); | 
|  | auto IncURI = URI::create(IncFile).toString(); | 
|  | InMemoryFileSystem->addFile(IncFile, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("class X {};")); | 
|  | runSymbolCollector("#include \"test.inc\"\nclass Y {};", /*Main=*/"", | 
|  | /*ExtraArgs=*/{"-I", testRoot()}); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre(AllOf(qName("X"), declURI(IncURI), | 
|  | includeHeader("<canonical>")), | 
|  | AllOf(qName("Y"), declURI(TestHeaderURI), | 
|  | includeHeader("<canonical>")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | // To make this case as hard as possible, we won't tell clang main is a | 
|  | // header. No extension, no -x c++-header. | 
|  | TestFileName = testPath("no_ext_main"); | 
|  | TestFileURI = URI::create(TestFileName).toString(); | 
|  | auto IncFile = testPath("test.inc"); | 
|  | auto IncURI = URI::create(IncFile).toString(); | 
|  | InMemoryFileSystem->addFile(IncFile, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("class X {};")); | 
|  | runSymbolCollector("", R"cpp( | 
|  | // Can't use #pragma once in a main file clang doesn't think is a header. | 
|  | #ifndef MAIN_H_ | 
|  | #define MAIN_H_ | 
|  | #include "test.inc" | 
|  | #endif | 
|  | )cpp", | 
|  | /*ExtraArgs=*/{"-I", testRoot()}); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X"), declURI(IncURI), | 
|  | includeHeader(TestFileURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, IncFileInNonHeader) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | TestFileName = testPath("main.cc"); | 
|  | TestFileURI = URI::create(TestFileName).toString(); | 
|  | auto IncFile = testPath("test.inc"); | 
|  | auto IncURI = URI::create(IncFile).toString(); | 
|  | InMemoryFileSystem->addFile(IncFile, 0, | 
|  | llvm::MemoryBuffer::getMemBuffer("class X {};")); | 
|  | runSymbolCollector("", R"cpp( | 
|  | #include "test.inc" | 
|  | )cpp", | 
|  | /*ExtraArgs=*/{"-I", testRoot()}); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X"), declURI(IncURI), | 
|  | Not(includeHeader())))); | 
|  | } | 
|  |  | 
|  | // Features that depend on header-guards are fragile. Header guards are only | 
|  | // recognized when the file ends, so we have to defer checking for them. | 
|  | TEST_F(SymbolCollectorTest, HeaderGuardDetected) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | CollectorOpts.CollectMacro = true; | 
|  | runSymbolCollector(R"cpp( | 
|  | #ifndef HEADER_GUARD_ | 
|  | #define HEADER_GUARD_ | 
|  |  | 
|  | // Symbols are seen before the header guard is complete. | 
|  | #define MACRO | 
|  | int decl(); | 
|  |  | 
|  | #endif // Header guard is recognized here. | 
|  | )cpp", | 
|  | ""); | 
|  | EXPECT_THAT(Symbols, Not(Contains(qName("HEADER_GUARD_")))); | 
|  | EXPECT_THAT(Symbols, Each(includeHeader())); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, NonModularHeader) { | 
|  | auto TU = TestTU::withHeaderCode("int x();"); | 
|  | EXPECT_THAT(TU.headerSymbols(), ElementsAre(includeHeader())); | 
|  |  | 
|  | // Files missing include guards aren't eligible for insertion. | 
|  | TU.ImplicitHeaderGuard = false; | 
|  | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); | 
|  |  | 
|  | // We recognize some patterns of trying to prevent insertion. | 
|  | TU = TestTU::withHeaderCode(R"cpp( | 
|  | #ifndef SECRET | 
|  | #error "This file isn't safe to include directly" | 
|  | #endif | 
|  | int x(); | 
|  | )cpp"); | 
|  | TU.ExtraArgs.push_back("-DSECRET"); // *we're* able to include it. | 
|  | EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(includeHeader()))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | Annotations Header(R"( | 
|  | #pragma once | 
|  | // Forward declarations of TagDecls. | 
|  | class C; | 
|  | struct S; | 
|  | union U; | 
|  |  | 
|  | // Canonical declarations. | 
|  | class $cdecl[[C]] {}; | 
|  | struct $sdecl[[S]] {}; | 
|  | union $udecl[[U]] {int $xdecl[[x]]; bool $ydecl[[y]];}; | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("C"), declURI(TestHeaderURI), | 
|  | declRange(Header.range("cdecl")), includeHeader(TestHeaderURI), | 
|  | defURI(TestHeaderURI), defRange(Header.range("cdecl"))), | 
|  | AllOf(qName("S"), declURI(TestHeaderURI), | 
|  | declRange(Header.range("sdecl")), includeHeader(TestHeaderURI), | 
|  | defURI(TestHeaderURI), defRange(Header.range("sdecl"))), | 
|  | AllOf(qName("U"), declURI(TestHeaderURI), | 
|  | declRange(Header.range("udecl")), includeHeader(TestHeaderURI), | 
|  | defURI(TestHeaderURI), defRange(Header.range("udecl"))), | 
|  | AllOf(qName("U::x"), declURI(TestHeaderURI), | 
|  | declRange(Header.range("xdecl")), defURI(TestHeaderURI), | 
|  | defRange(Header.range("xdecl"))), | 
|  | AllOf(qName("U::y"), declURI(TestHeaderURI), | 
|  | declRange(Header.range("ydecl")), defURI(TestHeaderURI), | 
|  | defRange(Header.range("ydecl"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | runSymbolCollector(/*Header=*/"#pragma once\nclass X;", | 
|  | /*Main=*/"class X {};"); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf( | 
|  | qName("X"), declURI(TestHeaderURI), | 
|  | includeHeader(TestHeaderURI), defURI(TestFileURI)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, UTF16Character) { | 
|  | // ö is 2-bytes. | 
|  | Annotations Header(/*Header=*/"class [[pörk]] {};"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("pörk"), declRange(Header.range())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, DoNotIndexSymbolsInFriendDecl) { | 
|  | Annotations Header(R"( | 
|  | namespace nx { | 
|  | class $z[[Z]] {}; | 
|  | class X { | 
|  | friend class Y; | 
|  | friend class Z; | 
|  | friend void foo(); | 
|  | friend void $bar[[bar]]() {} | 
|  | }; | 
|  | class $y[[Y]] {}; | 
|  | void $foo[[foo]](); | 
|  | } | 
|  | )"); | 
|  | runSymbolCollector(Header.code(), /*Main=*/""); | 
|  |  | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("nx"), qName("nx::X"), | 
|  | AllOf(qName("nx::Y"), declRange(Header.range("y"))), | 
|  | AllOf(qName("nx::Z"), declRange(Header.range("z"))), | 
|  | AllOf(qName("nx::foo"), declRange(Header.range("foo"))), | 
|  | AllOf(qName("nx::bar"), declRange(Header.range("bar"))))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, ReferencesInFriendDecl) { | 
|  | const std::string Header = R"( | 
|  | class X; | 
|  | class Y; | 
|  | )"; | 
|  | const std::string Main = R"( | 
|  | class C { | 
|  | friend ::X; | 
|  | friend class Y; | 
|  | }; | 
|  | )"; | 
|  | CollectorOpts.CountReferences = true; | 
|  | runSymbolCollector(Header, Main); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(qName("X"), refCount(1)), | 
|  | AllOf(qName("Y"), refCount(1)), | 
|  | AllOf(qName("C"), refCount(0)))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Origin) { | 
|  | CollectorOpts.Origin = SymbolOrigin::Static; | 
|  | runSymbolCollector("class Foo {};", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | Field(&Symbol::Origin, SymbolOrigin::Static))); | 
|  | runSymbolCollector("#define FOO", /*Main=*/""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | Field(&Symbol::Origin, SymbolOrigin::Static))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, CollectMacros) { | 
|  | CollectorOpts.CollectIncludePath = true; | 
|  | Annotations Header(R"( | 
|  | #pragma once | 
|  | #define X 1 | 
|  | #define $mac[[MAC]](x) int x | 
|  | #define $used[[USED]](y) float y; | 
|  |  | 
|  | MAC(p); | 
|  | )"); | 
|  |  | 
|  | Annotations Main(R"( | 
|  | #define $main[[MAIN]] 1 | 
|  | USED(t); | 
|  | )"); | 
|  | CollectorOpts.CountReferences = true; | 
|  | CollectorOpts.CollectMacro = true; | 
|  | runSymbolCollector(Header.code(), Main.code()); | 
|  | EXPECT_THAT( | 
|  | Symbols, | 
|  | UnorderedElementsAre( | 
|  | qName("p"), qName("t"), | 
|  | AllOf(qName("X"), declURI(TestHeaderURI), | 
|  | includeHeader(TestHeaderURI)), | 
|  | AllOf(labeled("MAC(x)"), refCount(0), | 
|  |  | 
|  | declRange(Header.range("mac")), visibleOutsideFile()), | 
|  | AllOf(labeled("USED(y)"), refCount(1), | 
|  | declRange(Header.range("used")), visibleOutsideFile()), | 
|  | AllOf(labeled("MAIN"), refCount(0), declRange(Main.range("main")), | 
|  | Not(visibleOutsideFile())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, DeprecatedSymbols) { | 
|  | const std::string Header = R"( | 
|  | void TestClangc() __attribute__((deprecated("", ""))); | 
|  | void TestClangd(); | 
|  | )"; | 
|  | runSymbolCollector(Header, /**/ ""); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre( | 
|  | AllOf(qName("TestClangc"), deprecated()), | 
|  | AllOf(qName("TestClangd"), Not(deprecated())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, implementationDetail) { | 
|  | const std::string Header = R"( | 
|  | #define DECL_NAME(x, y) x##_##y##_Decl | 
|  | #define DECL(x, y) class DECL_NAME(x, y) {}; | 
|  | DECL(X, Y); // X_Y_Decl | 
|  |  | 
|  | class Public {}; | 
|  | )"; | 
|  | runSymbolCollector(Header, /**/ ""); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre( | 
|  | AllOf(qName("X_Y_Decl"), implementationDetail()), | 
|  | AllOf(qName("Public"), Not(implementationDetail())))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, UsingDecl) { | 
|  | const char *Header = R"( | 
|  | void foo(); | 
|  | namespace std { | 
|  | using ::foo; | 
|  | })"; | 
|  | runSymbolCollector(Header, /**/ ""); | 
|  | EXPECT_THAT(Symbols, Contains(qName("std::foo"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, CBuiltins) { | 
|  | // In C, printf in stdio.h is a redecl of an implicit builtin. | 
|  | const char *Header = R"( | 
|  | extern int printf(const char*, ...); | 
|  | )"; | 
|  | runSymbolCollector(Header, /**/ "", {"-xc"}); | 
|  | EXPECT_THAT(Symbols, Contains(qName("printf"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, InvalidSourceLoc) { | 
|  | const char *Header = R"( | 
|  | void operator delete(void*) | 
|  | __attribute__((__externally_visible__));)"; | 
|  | runSymbolCollector(Header, /**/ ""); | 
|  | EXPECT_THAT(Symbols, Contains(qName("operator delete"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, BadUTF8) { | 
|  | // Extracted from boost/spirit/home/support/char_encoding/iso8859_1.hpp | 
|  | // This looks like UTF-8 and fools clang, but has high-ISO-8859-1 comments. | 
|  | const char *Header = "int PUNCT = 0;\n" | 
|  | "/* \xa1 */ int types[] = { /* \xa1 */PUNCT };"; | 
|  | CollectorOpts.RefFilter = RefKind::All; | 
|  | CollectorOpts.RefsInHeaders = true; | 
|  | runSymbolCollector(Header, ""); | 
|  | EXPECT_THAT(Symbols, Contains(AllOf(qName("types"), doc("\xef\xbf\xbd ")))); | 
|  | EXPECT_THAT(Symbols, Contains(qName("PUNCT"))); | 
|  | // Reference is stored, although offset within line is not reliable. | 
|  | EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "PUNCT").ID, _))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, MacrosInHeaders) { | 
|  | CollectorOpts.CollectMacro = true; | 
|  | TestFileName = testPath("test.h"); | 
|  | runSymbolCollector("", "#define X"); | 
|  | EXPECT_THAT(Symbols, | 
|  | UnorderedElementsAre(AllOf(qName("X"), forCodeCompletion(true)))); | 
|  | } | 
|  |  | 
|  | // Regression test for a crash-bug we used to have. | 
|  | TEST_F(SymbolCollectorTest, UndefOfModuleMacro) { | 
|  | auto TU = TestTU::withCode(R"cpp(#include "bar.h")cpp"); | 
|  | TU.AdditionalFiles["bar.h"] = R"cpp( | 
|  | #include "foo.h" | 
|  | #undef X | 
|  | )cpp"; | 
|  | TU.AdditionalFiles["foo.h"] = "#define X 1"; | 
|  | TU.AdditionalFiles["module.map"] = R"cpp( | 
|  | module foo { | 
|  | header "foo.h" | 
|  | export * | 
|  | } | 
|  | )cpp"; | 
|  | TU.ExtraArgs.push_back("-fmodules"); | 
|  | TU.ExtraArgs.push_back("-fmodule-map-file=" + testPath("module.map")); | 
|  | TU.OverlayRealFileSystemForModules = true; | 
|  |  | 
|  | TU.build(); | 
|  | // We mostly care about not crashing, but verify that we didn't insert garbage | 
|  | // about X too. | 
|  | EXPECT_THAT(TU.headerSymbols(), Not(Contains(qName("X")))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, NoCrashOnObjCMethodCStyleParam) { | 
|  | auto TU = TestTU::withCode(R"objc( | 
|  | @interface Foo | 
|  | - (void)fun:(bool)foo, bool bar; | 
|  | @end | 
|  | )objc"); | 
|  | TU.ExtraArgs.push_back("-xobjective-c++"); | 
|  |  | 
|  | TU.build(); | 
|  | // We mostly care about not crashing. | 
|  | EXPECT_THAT(TU.headerSymbols(), | 
|  | UnorderedElementsAre(qName("Foo"), qName("Foo::fun:"))); | 
|  | } | 
|  |  | 
|  | TEST_F(SymbolCollectorTest, Reserved) { | 
|  | const char *Header = R"cpp( | 
|  | void __foo(); | 
|  | namespace _X { int secret; } | 
|  | )cpp"; | 
|  |  | 
|  | CollectorOpts.CollectReserved = true; | 
|  | runSymbolCollector("", Header); | 
|  | EXPECT_THAT(Symbols, UnorderedElementsAre(qName("__foo"), qName("_X"), | 
|  | qName("_X::secret"))); | 
|  |  | 
|  | CollectorOpts.CollectReserved = false; | 
|  | runSymbolCollector("", Header); // | 
|  | EXPECT_THAT(Symbols, IsEmpty()); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  | } // namespace clangd | 
|  | } // namespace clang |