//===-- SymbolCollectorTests.cpp -------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #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/Tooling/Tooling.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/VirtualFileSystem.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace clang { namespace clangd { namespace { using testing::_; using testing::AllOf; using testing::Contains; using testing::Eq; 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(DeclURI, P, "") { return StringRef(arg.CanonicalDeclaration.FileURI) == P; } MATCHER_P(DefURI, P, "") { return StringRef(arg.Definition.FileURI) == P; } 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); } MATCHER_P(DeclRange, Pos, "") { return std::make_tuple(arg.CanonicalDeclaration.Start.line(), arg.CanonicalDeclaration.Start.column(), arg.CanonicalDeclaration.End.line(), arg.CanonicalDeclaration.End.column()) == std::make_tuple(Pos.start.line, Pos.start.character, Pos.end.line, Pos.end.character); } MATCHER_P(DefRange, Pos, "") { return std::make_tuple( arg.Definition.Start.line(), arg.Definition.Start.column(), arg.Definition.End.line(), arg.Definition.End.column()) == std::make_tuple(Pos.start.line, Pos.start.character, Pos.end.line, Pos.end.character); } MATCHER_P(RefCount, R, "") { return int(arg.References) == R; } MATCHER_P(ForCodeCompletion, IsIndexedForCodeCompletion, "") { return static_cast(arg.Flags & Symbol::IndexedForCodeCompletion) == IsIndexedForCodeCompletion; } MATCHER(Deprecated, "") { return arg.Flags & Symbol::Deprecated; } MATCHER(ImplementationDetail, "") { return arg.Flags & Symbol::ImplementationDetail; } MATCHER(VisibleOutsideFile, "") { return static_cast(arg.Flags & Symbol::VisibleOutsideFile); } MATCHER(RefRange, "") { const Ref &Pos = testing::get<0>(arg); const Range &Range = testing::get<1>(arg); return std::make_tuple(Pos.Location.Start.line(), Pos.Location.Start.column(), Pos.Location.End.line(), Pos.Location.End.column()) == std::make_tuple(Range.start.line, Range.start.character, Range.end.line, Range.end.character); } testing::Matcher &> HaveRanges(const std::vector 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 = HeaderCode; File.Code = 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); ASTContext& Ctx = AST->getASTContext(); const SourceManager& SM = Ctx.getSourceManager(); bool MainFile = SM.isWrittenInMainFile(SM.getExpansionLoc(ND.getBeginLoc())); return SymbolCollector::shouldCollectSymbol( ND, Ctx, SymbolCollector::Options(), MainFile); } protected: std::string HeaderName = "f.h"; std::string FileName = "f.cpp"; TestTU File; llvm::Optional 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, 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) {} clang::FrontendAction *create() override { class WrappedIndexAction : public WrapperFrontendAction { public: WrappedIndexAction(std::shared_ptr C, const index::IndexingOptions &Opts, CommentHandler *PragmaHandler) : WrapperFrontendAction( index::createIndexingAction(C, Opts, nullptr)), PragmaHandler(PragmaHandler) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { if (PragmaHandler) CI.getPreprocessor().addCommentHandler(PragmaHandler); return WrapperFrontendAction::CreateASTConsumer(CI, InFile); } private: index::IndexingOptions IndexOpts; CommentHandler *PragmaHandler; }; index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = false; Collector = std::make_shared(COpts); return new WrappedIndexAction(Collector, std::move(IndexOpts), PragmaHandler); } std::shared_ptr 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(); } bool runSymbolCollector(llvm::StringRef HeaderCode, llvm::StringRef MainCode, const std::vector &ExtraArgs = {}) { llvm::IntrusiveRefCntPtr Files( new FileManager(FileSystemOptions(), InMemoryFileSystem)); auto Factory = llvm::make_unique( CollectorOpts, PragmaHandler.get()); std::vector Args = { "symbol_collector", "-fsyntax-only", "-xc++", "-std=c++11", "-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()); 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(); return true; } protected: llvm::IntrusiveRefCntPtr InMemoryFileSystem; std::string TestHeaderName; std::string TestHeaderURI; std::string TestFileName; std::string TestFileURI; SymbolSlab Symbols; RefSlab Refs; SymbolCollector::Options CollectorOpts; std::unique_ptr 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() {} namespace foo { // Type alias typedef int int32; using int32_t = int32; // Variable int v1; // Namespace namespace bar { int v2; } // Namespace alias namespace baz = bar; // FIXME: using declaration is not supported as the IndexAction will ignore // implicit declarations (the implicit using shadow declaration) by default, // and there is no way to customize this behavior at the moment. 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("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::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"( // Template is indexed, specialization and instantiation is not. template struct [[Tmpl]] {T $xdecl[[x]] = 0;}; template <> struct Tmpl {}; extern template struct Tmpl; template struct Tmpl; )"); runSymbolCollector(Header.code(), /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAreArray( {AllOf(QName("Tmpl"), DeclRange(Header.range())), AllOf(QName("Tmpl::x"), DeclRange(Header.range("xdecl")))})); } 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:"), QName("MyCategory"), QName("Person::someMethodName2:"), QName("MyProtocol"), QName("MyProtocol::someMethodName3:"))); } 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"( class $foo[[Foo]] { public: $foo[[Foo]]() {} $foo[[Foo]](int); }; class $bar[[Bar]]; void $func[[func]](); namespace $ns[[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; } )"); Annotations SymbolsOnlyInMainCode(R"( int a; void b() {} static const int c = 0; class d {}; )"); CollectorOpts.RefFilter = RefKind::All; runSymbolCollector(Header.code(), (Main.code() + SymbolsOnlyInMainCode.code()).str()); auto HeaderSymbols = TestTU::withHeaderCode(Header.code()).headerSymbols(); 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, _)))); // Symbols *only* in the main file (a, b, c) had no refs collected. auto MainSymbols = TestTU::withHeaderCode(SymbolsOnlyInMainCode.code()).headerSymbols(); EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "a").ID, _)))); EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "b").ID, _)))); EXPECT_THAT(Refs, Not(Contains(Pair(findSymbol(MainSymbols, "c").ID, _)))); } TEST_F(SymbolCollectorTest, RefsInHeaders) { CollectorOpts.RefFilter = RefKind::All; CollectorOpts.RefsInHeaders = true; Annotations Header(R"( class [[Foo]] {}; )"); runSymbolCollector(Header.code(), ""); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Foo").ID, HaveRanges(Header.ranges())))); } TEST_F(SymbolCollectorTest, References) { 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, 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("class Foo {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( AllOf(QName("Foo"), DeclURI(TestHeaderURI)))); EXPECT_THAT(Symbols.begin()->IncludeHeaders, UnorderedElementsAre(IncludeHeaderWithRef(TestHeaderURI, 1u))); } #ifndef _WIN32 TEST_F(SymbolCollectorTest, CanonicalSTLHeader) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); CollectorOpts.Includes = &Includes; // bits/basic_string.h$ should be mapped to TestHeaderName = "/nasty/bits/basic_string.h"; TestFileName = "/nasty/bits/basic_string.cpp"; TestHeaderURI = URI::create(TestHeaderName).toString(); runSymbolCollector("class string {};", /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("string"), DeclURI(TestHeaderURI), IncludeHeader("")))); } #endif TEST_F(SymbolCollectorTest, STLiosfwd) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; addSystemHeadersMapping(&Includes); CollectorOpts.Includes = &Includes; // Symbols from should be mapped individually. TestHeaderName = testPath("iosfwd"); TestFileName = testPath("iosfwd.cpp"); std::string Header = R"( namespace std { class no_map {}; class ios {}; class ostream {}; class filebuf {}; } // namespace std )"; runSymbolCollector(Header, /*Main=*/""); EXPECT_THAT(Symbols, UnorderedElementsAre( QName("std"), AllOf(QName("std::no_map"), IncludeHeader("")), AllOf(QName("std::ios"), IncludeHeader("")), AllOf(QName("std::ostream"), IncludeHeader("")), AllOf(QName("std::filebuf"), IncludeHeader("")))); } 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, ""); 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("")), AllOf(QName("Y"), DeclURI(TestHeaderURI), IncludeHeader("")))); } TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; CollectorOpts.Includes = &Includes; TestFileName = testPath("main.h"); 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("", /*Main=*/"#include \"test.inc\"", /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(TestFileURI)))); } TEST_F(SymbolCollectorTest, MainFileIsHeaderWithoutExtensionWhenSkipIncFile) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; CollectorOpts.Includes = &Includes; 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("", /*Main=*/"#include \"test.inc\"", /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(TestFileURI)))); } TEST_F(SymbolCollectorTest, FallbackToIncFileWhenIncludingFileIsCC) { CollectorOpts.CollectIncludePath = true; CanonicalIncludes Includes; CollectorOpts.Includes = &Includes; auto IncFile = testPath("test.inc"); auto IncURI = URI::create(IncFile).toString(); InMemoryFileSystem->addFile(IncFile, 0, llvm::MemoryBuffer::getMemBuffer("class X {};")); runSymbolCollector("", /*Main=*/"#include \"test.inc\"", /*ExtraArgs=*/{"-I", testRoot()}); EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI), IncludeHeader(IncURI)))); } TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) { CollectorOpts.CollectIncludePath = true; Annotations Header(R"( // 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=*/"class 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))); } TEST_F(SymbolCollectorTest, CollectMacros) { CollectorOpts.CollectIncludePath = true; Annotations Header(R"( #define X 1 #define $mac[[MAC]](x) int x #define $used[[USED]](y) float y; MAC(p); )"); const std::string Main = R"( #define MAIN 1 // not indexed USED(t); )"; CollectorOpts.CountReferences = true; CollectorOpts.CollectMacro = true; runSymbolCollector(Header.code(), Main); 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"))), AllOf(Labeled("USED(y)"), RefCount(1), DeclRange(Header.range("used"))))); } 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())))); } } // namespace } // namespace clangd } // namespace clang