diff options
Diffstat (limited to 'clangd/unittests/SymbolCollectorTests.cpp')
-rw-r--r-- | clangd/unittests/SymbolCollectorTests.cpp | 1257 |
1 files changed, 1257 insertions, 0 deletions
diff --git a/clangd/unittests/SymbolCollectorTests.cpp b/clangd/unittests/SymbolCollectorTests.cpp new file mode 100644 index 00000000..d372b1d6 --- /dev/null +++ b/clangd/unittests/SymbolCollectorTests.cpp @@ -0,0 +1,1257 @@ +//===-- 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/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-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::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(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); +} +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<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 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<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 = 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<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, 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<SymbolCollector> C, + const index::IndexingOptions &Opts, + CommentHandler *PragmaHandler) + : WrapperFrontendAction( + index::createIndexingAction(C, Opts, nullptr)), + PragmaHandler(PragmaHandler) {} + + std::unique_ptr<ASTConsumer> + CreateASTConsumer(CompilerInstance &CI, llvm::StringRef InFile) override { + if (PragmaHandler) + CI.getPreprocessor().addCommentHandler(PragmaHandler); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } + + bool BeginInvocation(CompilerInstance &CI) override { + // Make the compiler parse all comments. + CI.getLangOpts().CommentOpts.ParseAllComments = true; + return WrapperFrontendAction::BeginInvocation(CI); + } + + private: + index::IndexingOptions IndexOpts; + CommentHandler *PragmaHandler; + }; + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = false; + Collector = std::make_shared<SymbolCollector>(COpts); + return new WrappedIndexAction(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 = llvm::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(); + 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; + 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() {} + + 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("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:"), + QName("MyCategory"), 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, 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, 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; + addSystemHeadersMapping(&Includes); + CollectorOpts.Includes = &Includes; + runSymbolCollector("namespace std { class string {}; }", /*Main=*/""); + EXPECT_THAT(Symbols, + Contains(AllOf(QName("std::string"), DeclURI(TestHeaderURI), + IncludeHeader("<string>")))); +} + +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))); +} + +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"))); +} + +} // namespace +} // namespace clangd +} // namespace clang |