//===-- CodeCompleteTests.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 "ASTSignals.h" #include "Annotations.h" #include "ClangdServer.h" #include "CodeComplete.h" #include "Compiler.h" #include "Feature.h" #include "Matchers.h" #include "Protocol.h" #include "Quality.h" #include "SourceCode.h" #include "SyncAPI.h" #include "TestFS.h" #include "TestIndex.h" #include "TestTU.h" #include "index/Index.h" #include "index/MemIndex.h" #include "index/SymbolOrigin.h" #include "support/Threading.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Testing/Annotations/Annotations.h" #include "llvm/Testing/Support/Error.h" #include "llvm/Testing/Support/SupportHelpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include #include namespace clang { namespace clangd { namespace { using ::llvm::Failed; using ::testing::AllOf; using ::testing::Contains; using ::testing::ElementsAre; using ::testing::Field; using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::Not; using ::testing::UnorderedElementsAre; using ContextKind = CodeCompletionContext::Kind; // GMock helpers for matching completion items. MATCHER_P(named, Name, "") { return arg.Name == Name; } MATCHER_P(mainFileRefs, Refs, "") { return arg.MainFileRefs == Refs; } MATCHER_P(scopeRefs, Refs, "") { return arg.ScopeRefsInFile == Refs; } MATCHER_P(nameStartsWith, Prefix, "") { return llvm::StringRef(arg.Name).starts_with(Prefix); } MATCHER_P(filterText, F, "") { return arg.FilterText == F; } MATCHER_P(scope, S, "") { return arg.Scope == S; } MATCHER_P(qualifier, Q, "") { return arg.RequiredQualifier == Q; } MATCHER_P(labeled, Label, "") { return arg.RequiredQualifier + arg.Name + arg.Signature == Label; } MATCHER_P(sigHelpLabeled, Label, "") { return arg.label == Label; } MATCHER_P(kind, K, "") { return arg.Kind == K; } MATCHER_P(doc, D, "") { return arg.Documentation && arg.Documentation->asPlainText() == D; } MATCHER_P(returnType, D, "") { return arg.ReturnType == D; } MATCHER_P(hasInclude, IncludeHeader, "") { return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader; } MATCHER_P(insertInclude, IncludeHeader, "") { return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader && bool(arg.Includes[0].Insertion); } MATCHER_P(insertIncludeText, InsertedText, "") { return !arg.Includes.empty() && arg.Includes[0].Insertion && arg.Includes[0].Insertion->newText == InsertedText; } MATCHER(insertInclude, "") { return !arg.Includes.empty() && bool(arg.Includes[0].Insertion); } MATCHER_P(snippetSuffix, Text, "") { return arg.SnippetSuffix == Text; } MATCHER_P(origin, OriginSet, "") { return arg.Origin == OriginSet; } MATCHER_P(signature, S, "") { return arg.Signature == S; } MATCHER_P(replacesRange, Range, "") { return arg.CompletionTokenRange == Range; } // Shorthand for Contains(named(Name)). Matcher &> has(std::string Name) { return Contains(named(std::move(Name))); } Matcher &> has(std::string Name, CompletionItemKind K) { return Contains(AllOf(named(std::move(Name)), kind(K))); } MATCHER(isDocumented, "") { return arg.Documentation.has_value(); } MATCHER(deprecated, "") { return arg.Deprecated; } std::unique_ptr memIndex(std::vector Symbols) { SymbolSlab::Builder Slab; for (const auto &Sym : Symbols) Slab.insert(Sym); return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab()); } // Runs code completion. // If IndexSymbols is non-empty, an index will be built and passed to opts. CodeCompleteResult completions(const TestTU &TU, Position Point, std::vector IndexSymbols = {}, clangd::CodeCompleteOptions Opts = {}) { std::unique_ptr OverrideIndex; if (!IndexSymbols.empty()) { assert(!Opts.Index && "both Index and IndexSymbols given!"); OverrideIndex = memIndex(std::move(IndexSymbols)); Opts.Index = OverrideIndex.get(); } MockFS FS; auto Inputs = TU.inputs(FS); IgnoreDiagnostics Diags; auto CI = buildCompilerInvocation(Inputs, Diags); if (!CI) { ADD_FAILURE() << "Couldn't build CompilerInvocation"; return {}; } auto Preamble = buildPreamble(testPath(TU.Filename), *CI, Inputs, /*InMemory=*/true, /*Callback=*/nullptr); return codeComplete(testPath(TU.Filename), Point, Preamble.get(), Inputs, Opts); } // Runs code completion. CodeCompleteResult completions(llvm::StringRef Text, std::vector IndexSymbols = {}, clangd::CodeCompleteOptions Opts = {}, PathRef FilePath = "foo.cpp") { Annotations Test(Text); auto TU = TestTU::withCode(Test.code()); // To make sure our tests for completiopns inside templates work on Windows. TU.Filename = FilePath.str(); return completions(TU, Test.point(), std::move(IndexSymbols), std::move(Opts)); } // Runs code completion without the clang parser. CodeCompleteResult completionsNoCompile(llvm::StringRef Text, std::vector IndexSymbols = {}, clangd::CodeCompleteOptions Opts = {}, PathRef FilePath = "foo.cpp") { std::unique_ptr OverrideIndex; if (!IndexSymbols.empty()) { assert(!Opts.Index && "both Index and IndexSymbols given!"); OverrideIndex = memIndex(std::move(IndexSymbols)); Opts.Index = OverrideIndex.get(); } MockFS FS; Annotations Test(Text); ParseInputs ParseInput{tooling::CompileCommand(), &FS, Test.code().str()}; return codeComplete(FilePath, Test.point(), /*Preamble=*/nullptr, ParseInput, Opts); } Symbol withReferences(int N, Symbol S) { S.References = N; return S; } #if CLANGD_DECISION_FOREST TEST(DecisionForestRankingModel, NameMatchSanityTest) { clangd::CodeCompleteOptions Opts; Opts.RankingModel = CodeCompleteOptions::DecisionForest; auto Results = completions( R"cpp( struct MemberAccess { int ABG(); int AlphaBetaGamma(); }; int func() { MemberAccess().ABG^ } )cpp", /*IndexSymbols=*/{}, Opts); EXPECT_THAT(Results.Completions, ElementsAre(named("ABG"), named("AlphaBetaGamma"))); } TEST(DecisionForestRankingModel, ReferencesAffectRanking) { clangd::CodeCompleteOptions Opts; Opts.RankingModel = CodeCompleteOptions::DecisionForest; constexpr int NumReferences = 100000; EXPECT_THAT( completions("int main() { clang^ }", {ns("clangA"), withReferences(NumReferences, func("clangD"))}, Opts) .Completions, ElementsAre(named("clangD"), named("clangA"))); EXPECT_THAT( completions("int main() { clang^ }", {withReferences(NumReferences, ns("clangA")), func("clangD")}, Opts) .Completions, ElementsAre(named("clangA"), named("clangD"))); } #endif // CLANGD_DECISION_FOREST TEST(DecisionForestRankingModel, DecisionForestScorerCallbackTest) { clangd::CodeCompleteOptions Opts; constexpr float MagicNumber = 1234.5678f; Opts.RankingModel = CodeCompleteOptions::DecisionForest; Opts.DecisionForestScorer = [&](const SymbolQualitySignals &, const SymbolRelevanceSignals &, float Base) { DecisionForestScores Scores; Scores.Total = MagicNumber; Scores.ExcludingName = MagicNumber; return Scores; }; llvm::StringRef Code = "int func() { int xyz; xy^ }"; auto Results = completions(Code, /*IndexSymbols=*/{}, Opts); ASSERT_EQ(Results.Completions.size(), 1u); EXPECT_EQ(Results.Completions[0].Score.Total, MagicNumber); EXPECT_EQ(Results.Completions[0].Score.ExcludingName, MagicNumber); // Do not use DecisionForestScorer for heuristics model. Opts.RankingModel = CodeCompleteOptions::Heuristics; Results = completions(Code, /*IndexSymbols=*/{}, Opts); ASSERT_EQ(Results.Completions.size(), 1u); EXPECT_NE(Results.Completions[0].Score.Total, MagicNumber); EXPECT_NE(Results.Completions[0].Score.ExcludingName, MagicNumber); } TEST(CompletionTest, Limit) { clangd::CodeCompleteOptions Opts; Opts.Limit = 2; auto Results = completions(R"cpp( struct ClassWithMembers { int AAA(); int BBB(); int CCC(); }; int main() { ClassWithMembers().^ } )cpp", /*IndexSymbols=*/{}, Opts); EXPECT_TRUE(Results.HasMore); EXPECT_THAT(Results.Completions, ElementsAre(named("AAA"), named("BBB"))); } TEST(CompletionTest, Filter) { std::string Body = R"cpp( #define MotorCar int Car; struct S { int FooBar; int FooBaz; int Qux; }; )cpp"; // Only items matching the fuzzy query are returned. EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").Completions, AllOf(has("FooBar"), has("FooBaz"), Not(has("Qux")))); // Macros require prefix match, either from index or AST. Symbol Sym = var("MotorCarIndex"); Sym.SymInfo.Kind = index::SymbolKind::Macro; EXPECT_THAT( completions(Body + "int main() { C^ }", {Sym}).Completions, AllOf(has("Car"), Not(has("MotorCar")), Not(has("MotorCarIndex")))); EXPECT_THAT(completions(Body + "int main() { M^ }", {Sym}).Completions, AllOf(has("MotorCar"), has("MotorCarIndex"))); } void testAfterDotCompletion(clangd::CodeCompleteOptions Opts) { auto Results = completions( R"cpp( int global_var; int global_func(); // Make sure this is not in preamble. #define MACRO X struct GlobalClass {}; struct ClassWithMembers { /// doc for method. int method(); int field; private: int private_field; }; int test() { struct LocalClass {}; /// doc for local_var. int local_var; ClassWithMembers().^ } )cpp", {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); EXPECT_TRUE(Results.RanParser); // Class members. The only items that must be present in after-dot // completion. EXPECT_THAT(Results.Completions, AllOf(has("method"), has("field"), Not(has("ClassWithMembers")), Not(has("operator=")), Not(has("~ClassWithMembers")))); EXPECT_IFF(Opts.IncludeIneligibleResults, Results.Completions, has("private_field")); // Global items. EXPECT_THAT( Results.Completions, Not(AnyOf(has("global_var"), has("index_var"), has("global_func"), has("global_func()"), has("index_func"), has("GlobalClass"), has("IndexClass"), has("MACRO"), has("LocalClass")))); // There should be no code patterns (aka snippets) in after-dot // completion. At least there aren't any we're aware of. EXPECT_THAT(Results.Completions, Not(Contains(kind(CompletionItemKind::Snippet)))); // Check documentation. EXPECT_THAT(Results.Completions, Contains(isDocumented())); } void testGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { auto Results = completions( R"cpp( int global_var; int global_func(); // Make sure this is not in preamble. #define MACRO X struct GlobalClass {}; struct ClassWithMembers { /// doc for method. int method(); }; int test() { struct LocalClass {}; /// doc for local_var. int local_var; ^ } )cpp", {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); EXPECT_TRUE(Results.RanParser); // Class members. Should never be present in global completions. EXPECT_THAT(Results.Completions, Not(AnyOf(has("method"), has("method()"), has("field")))); // Global items. EXPECT_THAT(Results.Completions, AllOf(has("global_var"), has("index_var"), has("global_func"), has("index_func" /* our fake symbol doesn't include () */), has("GlobalClass"), has("IndexClass"))); // A macro. EXPECT_THAT(Results.Completions, has("MACRO")); // Local items. Must be present always. EXPECT_THAT(Results.Completions, AllOf(has("local_var"), has("LocalClass"), Contains(kind(CompletionItemKind::Snippet)))); // Check documentation. EXPECT_THAT(Results.Completions, Contains(isDocumented())); } TEST(CompletionTest, CompletionOptions) { auto Test = [&](const clangd::CodeCompleteOptions &Opts) { testAfterDotCompletion(Opts); testGlobalScopeCompletion(Opts); }; // We used to test every combination of options, but that got too slow (2^N). auto Flags = { &clangd::CodeCompleteOptions::IncludeIneligibleResults, }; // Test default options. Test({}); // Test with one flag flipped. for (auto &F : Flags) { clangd::CodeCompleteOptions O; O.*F ^= true; Test(O); } } TEST(CompletionTest, Accessible) { auto Internal = completions(R"cpp( class Foo { public: void pub(); protected: void prot(); private: void priv(); }; void Foo::pub() { this->^ } )cpp"); EXPECT_THAT(Internal.Completions, AllOf(has("priv"), has("prot"), has("pub"))); auto External = completions(R"cpp( class Foo { public: void pub(); protected: void prot(); private: void priv(); }; void test() { Foo F; F.^ } )cpp"); EXPECT_THAT(External.Completions, AllOf(has("pub"), Not(has("prot")), Not(has("priv")))); auto Results = completions(R"cpp( struct Foo { public: void pub(); protected: void prot(); private: void priv(); }; struct Bar : public Foo { private: using Foo::pub; }; void test() { Bar B; B.^ } )cpp"); EXPECT_THAT(Results.Completions, AllOf(Not(has("priv")), Not(has("prot")), Not(has("pub")))); } TEST(CompletionTest, Qualifiers) { auto Results = completions(R"cpp( class Foo { public: int foo() const; int bar() const; }; class Bar : public Foo { int foo() const; }; void test() { Bar().^ } )cpp"); EXPECT_THAT(Results.Completions, Contains(AllOf(qualifier(""), named("bar")))); // Hidden members are not shown. EXPECT_THAT(Results.Completions, Not(Contains(AllOf(qualifier("Foo::"), named("foo"))))); // Private members are not shown. EXPECT_THAT(Results.Completions, Not(Contains(AllOf(qualifier(""), named("foo"))))); } // https://github.com/clangd/clangd/issues/1451 TEST(CompletionTest, QualificationWithInlineNamespace) { auto Results = completions(R"cpp( namespace a { inline namespace b {} } using namespace a::b; void f() { Foo^ } )cpp", {cls("a::Foo")}); EXPECT_THAT(Results.Completions, UnorderedElementsAre(AllOf(qualifier("a::"), named("Foo")))); } TEST(CompletionTest, InjectedTypename) { // These are suppressed when accessed as a member... EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").Completions, Not(has("X"))); EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").Completions, Not(has("X"))); // ...but accessible in other, more useful cases. EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").Completions, has("X")); EXPECT_THAT( completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").Completions, has("Y")); EXPECT_THAT( completions( "template struct Y{}; struct X:Y{ void foo(){ ^ } };") .Completions, has("Y")); // This case is marginal (`using X::X` is useful), we allow it for now. EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").Completions, has("X")); } TEST(CompletionTest, SkipInjectedWhenUnqualified) { EXPECT_THAT(completions("struct X { void f() { X^ }};").Completions, ElementsAre(named("X"), named("~X"))); } TEST(CompletionTest, Snippets) { clangd::CodeCompleteOptions Opts; auto Results = completions( R"cpp( struct fake { int a; int f(int i, const float f) const; }; int main() { fake f; f.^ } )cpp", /*IndexSymbols=*/{}, Opts); EXPECT_THAT( Results.Completions, HasSubsequence(named("a"), snippetSuffix("(${1:int i}, ${2:const float f})"))); } TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) { clangd::CodeCompleteOptions Opts; Opts.EnableSnippets = true; Annotations Code(R"cpp( struct Foo { static int staticMethod(int); int method(int) const; template T generic(U, V); template static T staticGeneric(); Foo() { this->$canBeCall^ $canBeCall^ Foo::$canBeCall^ } }; struct Derived : Foo { using Foo::method; using Foo::generic; Derived() { Foo::$canBeCall^ } }; struct OtherClass { OtherClass() { Foo f; Derived d; f.$canBeCall^ ; // Prevent parsing as 'f.f' f.Foo::$canBeCall^ &Foo::$canNotBeCall^ ; d.Foo::$canBeCall^ ; d.Derived::$canBeCall^ } }; int main() { Foo f; Derived d; f.$canBeCall^ ; // Prevent parsing as 'f.f' f.Foo::$canBeCall^ &Foo::$canNotBeCall^ ; d.Foo::$canBeCall^ ; d.Derived::$canBeCall^ } )cpp"); auto TU = TestTU::withCode(Code.code()); for (const auto &P : Code.points("canNotBeCall")) { auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); EXPECT_THAT(Results.Completions, Contains(AllOf(named("method"), signature("(int) const"), snippetSuffix("")))); // We don't have any arguments to deduce against if this isn't a call. // Thus, we should emit these deducible template arguments explicitly. EXPECT_THAT( Results.Completions, Contains(AllOf(named("generic"), signature("(U, V)"), snippetSuffix("<${1:typename T}, ${2:typename U}>")))); } for (const auto &P : Code.points("canBeCall")) { auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); EXPECT_THAT(Results.Completions, Contains(AllOf(named("method"), signature("(int) const"), snippetSuffix("(${1:int})")))); EXPECT_THAT( Results.Completions, Contains(AllOf(named("generic"), signature("(U, V)"), snippetSuffix("<${1:typename T}>(${2:U}, ${3:V})")))); } // static method will always keep the snippet for (const auto &P : Code.points()) { auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); EXPECT_THAT(Results.Completions, Contains(AllOf(named("staticMethod"), signature("(int)"), snippetSuffix("(${1:int})")))); EXPECT_THAT(Results.Completions, Contains(AllOf( named("staticGeneric"), signature("()"), snippetSuffix("<${1:typename T}, ${2:int U}>()")))); } } TEST(CompletionTest, NoSnippetsInUsings) { clangd::CodeCompleteOptions Opts; Opts.EnableSnippets = true; auto Results = completions( R"cpp( namespace ns { int func(int a, int b); } using ns::^; )cpp", /*IndexSymbols=*/{}, Opts); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("func"), labeled("func(int a, int b)"), snippetSuffix("")))); // Check index completions too. auto Func = func("ns::func"); Func.CompletionSnippetSuffix = "(${1:int a}, ${2: int b})"; Func.Signature = "(int a, int b)"; Func.ReturnType = "void"; Results = completions(R"cpp( namespace ns {} using ns::^; )cpp", /*IndexSymbols=*/{Func}, Opts); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("func"), labeled("func(int a, int b)"), snippetSuffix("")))); // Check all-scopes completions too. Opts.AllScopes = true; Results = completions(R"cpp( using ^; )cpp", /*IndexSymbols=*/{Func}, Opts); EXPECT_THAT(Results.Completions, Contains(AllOf(named("func"), labeled("ns::func(int a, int b)"), snippetSuffix("")))); } TEST(CompletionTest, Kinds) { auto Results = completions( R"cpp( int variable; struct Struct {}; int function(); // make sure MACRO is not included in preamble. #define MACRO 10 int X = ^ )cpp", {func("indexFunction"), var("indexVariable"), cls("indexClass")}); EXPECT_THAT(Results.Completions, AllOf(has("function", CompletionItemKind::Function), has("variable", CompletionItemKind::Variable), has("int", CompletionItemKind::Keyword), has("Struct", CompletionItemKind::Struct), has("MACRO", CompletionItemKind::Constant), has("indexFunction", CompletionItemKind::Function), has("indexVariable", CompletionItemKind::Variable), has("indexClass", CompletionItemKind::Class))); Results = completions("nam^"); EXPECT_THAT(Results.Completions, has("namespace", CompletionItemKind::Snippet)); // Members of anonymous unions are of kind 'field'. Results = completions( R"cpp( struct X{ union { void *a; }; }; auto u = X().^ )cpp"); EXPECT_THAT( Results.Completions, UnorderedElementsAre(AllOf(named("a"), kind(CompletionItemKind::Field)))); // Completion kinds for templates should not be unknown. Results = completions( R"cpp( template struct complete_class {}; template void complete_function(); template using complete_type_alias = int; template int complete_variable = 10; struct X { template static int complete_static_member = 10; static auto x = complete_^ } )cpp"); EXPECT_THAT( Results.Completions, UnorderedElementsAre( AllOf(named("complete_class"), kind(CompletionItemKind::Class)), AllOf(named("complete_function"), kind(CompletionItemKind::Function)), AllOf(named("complete_type_alias"), kind(CompletionItemKind::Interface)), AllOf(named("complete_variable"), kind(CompletionItemKind::Variable)), AllOf(named("complete_static_member"), kind(CompletionItemKind::Property)))); Results = completions( R"cpp( enum Color { Red }; Color u = ^ )cpp"); EXPECT_THAT( Results.Completions, Contains(AllOf(named("Red"), kind(CompletionItemKind::EnumMember)))); } TEST(CompletionTest, NoDuplicates) { auto Results = completions( R"cpp( class Adapter { }; void f() { Adapter^ } )cpp", {cls("Adapter")}); // Make sure there are no duplicate entries of 'Adapter'. EXPECT_THAT(Results.Completions, ElementsAre(named("Adapter"))); } TEST(CompletionTest, ScopedNoIndex) { auto Results = completions( R"cpp( namespace fake { int BigBang, Babble, Box; }; int main() { fake::ba^ } ")cpp"); // Babble is a better match than BigBang. Box doesn't match at all. EXPECT_THAT(Results.Completions, ElementsAre(named("Babble"), named("BigBang"))); } TEST(CompletionTest, Scoped) { auto Results = completions( R"cpp( namespace fake { int Babble, Box; }; int main() { fake::ba^ } ")cpp", {var("fake::BigBang")}); EXPECT_THAT(Results.Completions, ElementsAre(named("Babble"), named("BigBang"))); } TEST(CompletionTest, ScopedWithFilter) { auto Results = completions( R"cpp( void f() { ns::x^ } )cpp", {cls("ns::XYZ"), func("ns::foo")}); EXPECT_THAT(Results.Completions, UnorderedElementsAre(named("XYZ"))); } TEST(CompletionTest, ReferencesAffectRanking) { EXPECT_THAT(completions("int main() { abs^ }", {func("absA"), func("absB")}) .Completions, HasSubsequence(named("absA"), named("absB"))); EXPECT_THAT(completions("int main() { abs^ }", {func("absA"), withReferences(1000, func("absB"))}) .Completions, HasSubsequence(named("absB"), named("absA"))); } TEST(CompletionTest, ContextWords) { auto Results = completions(R"cpp( enum class Color { RED, YELLOW, BLUE }; // (blank lines so the definition above isn't "context") // "It was a yellow car," he said. "Big yellow car, new." auto Finish = Color::^ )cpp"); // Yellow would normally sort last (alphabetic). // But the recent mention should bump it up. ASSERT_THAT(Results.Completions, HasSubsequence(named("YELLOW"), named("BLUE"))); } TEST(CompletionTest, GlobalQualified) { auto Results = completions( R"cpp( void f() { ::^ } )cpp", {cls("XYZ")}); EXPECT_THAT(Results.Completions, AllOf(has("XYZ", CompletionItemKind::Class), has("f", CompletionItemKind::Function))); } TEST(CompletionTest, FullyQualified) { auto Results = completions( R"cpp( namespace ns { void bar(); } void f() { ::ns::^ } )cpp", {cls("ns::XYZ")}); EXPECT_THAT(Results.Completions, AllOf(has("XYZ", CompletionItemKind::Class), has("bar", CompletionItemKind::Function))); } TEST(CompletionTest, SemaIndexMerge) { auto Results = completions( R"cpp( namespace ns { int local; void both(); } void f() { ::ns::^ } )cpp", {func("ns::both"), cls("ns::Index")}); // We get results from both index and sema, with no duplicates. EXPECT_THAT(Results.Completions, UnorderedElementsAre( AllOf(named("local"), origin(SymbolOrigin::AST)), AllOf(named("Index"), origin(SymbolOrigin::Static)), AllOf(named("both"), origin(SymbolOrigin::AST | SymbolOrigin::Static)))); } TEST(CompletionTest, SemaIndexMergeWithLimit) { clangd::CodeCompleteOptions Opts; Opts.Limit = 1; auto Results = completions( R"cpp( namespace ns { int local; void both(); } void f() { ::ns::^ } )cpp", {func("ns::both"), cls("ns::Index")}, Opts); EXPECT_EQ(Results.Completions.size(), Opts.Limit); EXPECT_TRUE(Results.HasMore); } TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) { TestTU TU; TU.ExtraArgs.push_back("-I" + testPath("sub")); TU.AdditionalFiles["sub/bar.h"] = ""; auto BarURI = URI::create(testPath("sub/bar.h")).toString(); Symbol Sym = cls("ns::X"); Sym.CanonicalDeclaration.FileURI = BarURI.c_str(); Sym.IncludeHeaders.emplace_back(BarURI, 1, Symbol::Include); // Shorten include path based on search directory and insert. Annotations Test("int main() { ns::^ }"); TU.Code = Test.code().str(); auto Results = completions(TU, Test.point(), {Sym}); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("X"), insertInclude("\"bar.h\"")))); // Can be disabled via option. CodeCompleteOptions NoInsertion; NoInsertion.InsertIncludes = CodeCompleteOptions::NeverInsert; Results = completions(TU, Test.point(), {Sym}, NoInsertion); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("X"), Not(insertInclude())))); // Duplicate based on inclusions in preamble. Test = Annotations(R"cpp( #include "sub/bar.h" // not shortest, so should only match resolved. int main() { ns::^ } )cpp"); TU.Code = Test.code().str(); Results = completions(TU, Test.point(), {Sym}); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("X"), labeled("X"), Not(insertInclude())))); } TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) { Symbol SymX = cls("ns::X"); Symbol SymY = cls("ns::Y"); std::string BarHeader = testPath("bar.h"); auto BarURI = URI::create(BarHeader).toString(); SymX.CanonicalDeclaration.FileURI = BarURI.c_str(); SymY.CanonicalDeclaration.FileURI = BarURI.c_str(); SymX.IncludeHeaders.emplace_back("", 1, Symbol::Include); SymY.IncludeHeaders.emplace_back("", 1, Symbol::Include); // Shorten include path based on search directory and insert. auto Results = completions(R"cpp( namespace ns { class X; class Y {}; } int main() { ns::^ } )cpp", {SymX, SymY}); EXPECT_THAT(Results.Completions, ElementsAre(AllOf(named("X"), Not(insertInclude())), AllOf(named("Y"), Not(insertInclude())))); } TEST(CompletionTest, IndexSuppressesPreambleCompletions) { Annotations Test(R"cpp( #include "bar.h" namespace ns { int local; } void f() { ns::^; } void f2() { ns::preamble().$2^; } )cpp"); auto TU = TestTU::withCode(Test.code()); TU.AdditionalFiles["bar.h"] = R"cpp(namespace ns { struct preamble { int member; }; })cpp"; clangd::CodeCompleteOptions Opts = {}; auto I = memIndex({var("ns::index")}); Opts.Index = I.get(); auto WithIndex = completions(TU, Test.point(), {}, Opts); EXPECT_THAT(WithIndex.Completions, UnorderedElementsAre(named("local"), named("index"))); auto ClassFromPreamble = completions(TU, Test.point("2"), {}, Opts); EXPECT_THAT(ClassFromPreamble.Completions, Contains(named("member"))); Opts.Index = nullptr; auto WithoutIndex = completions(TU, Test.point(), {}, Opts); EXPECT_THAT(WithoutIndex.Completions, UnorderedElementsAre(named("local"), named("preamble"))); } // This verifies that we get normal preprocessor completions in the preamble. // This is a regression test for an old bug: if we override the preamble and // try to complete inside it, clang kicks our completion point just outside the // preamble, resulting in always getting top-level completions. TEST(CompletionTest, CompletionInPreamble) { auto Results = completions(R"cpp( #ifnd^ef FOO_H_ #define BAR_H_ #include int foo() {} #endif )cpp") .Completions; EXPECT_THAT(Results, ElementsAre(named("ifndef"))); } TEST(CompletionTest, CompletionRecoveryASTType) { auto Results = completions(R"cpp( struct S { int member; }; S overloaded(int); void foo() { // No overload matches, but we have recovery-expr with the correct type. overloaded().^ })cpp") .Completions; EXPECT_THAT(Results, ElementsAre(named("member"))); } TEST(CompletionTest, DynamicIndexIncludeInsertion) { MockFS FS; MockCompilationDatabase CDB; ClangdServer::Options Opts = ClangdServer::optsForTest(); Opts.BuildDynamicSymbolIndex = true; ClangdServer Server(CDB, FS, Opts); FS.Files[testPath("foo_header.h")] = R"cpp( #pragma once struct Foo { // Member doc int foo(); }; )cpp"; const std::string FileContent(R"cpp( #include "foo_header.h" int Foo::foo() { return 42; } )cpp"); Server.addDocument(testPath("foo_impl.cpp"), FileContent); // Wait for the dynamic index being built. ASSERT_TRUE(Server.blockUntilIdleForTest()); auto File = testPath("foo.cpp"); Annotations Test("Foo^ foo;"); runAddDocument(Server, File, Test.code()); auto CompletionList = llvm::cantFail(runCodeComplete(Server, File, Test.point(), {})); EXPECT_THAT(CompletionList.Completions, ElementsAre(AllOf(named("Foo"), hasInclude("\"foo_header.h\""), insertInclude()))); } TEST(CompletionTest, DynamicIndexMultiFile) { MockFS FS; MockCompilationDatabase CDB; auto Opts = ClangdServer::optsForTest(); Opts.BuildDynamicSymbolIndex = true; ClangdServer Server(CDB, FS, Opts); FS.Files[testPath("foo.h")] = R"cpp( namespace ns { class XYZ {}; void foo(int x) {} } )cpp"; runAddDocument(Server, testPath("foo.cpp"), R"cpp( #include "foo.h" )cpp"); auto File = testPath("bar.cpp"); Annotations Test(R"cpp( namespace ns { class XXX {}; /// Doooc void fooooo() {} } void f() { ns::^ } )cpp"); runAddDocument(Server, File, Test.code()); auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); // "XYZ" and "foo" are not included in the file being completed but are still // visible through the index. EXPECT_THAT(Results.Completions, has("XYZ", CompletionItemKind::Class)); EXPECT_THAT(Results.Completions, has("foo", CompletionItemKind::Function)); EXPECT_THAT(Results.Completions, has("XXX", CompletionItemKind::Class)); EXPECT_THAT(Results.Completions, Contains((named("fooooo"), kind(CompletionItemKind::Function), doc("Doooc"), returnType("void")))); } TEST(CompletionTest, Documentation) { auto Results = completions( R"cpp( // Non-doxygen comment. __attribute__((annotate("custom_annotation"))) int foo(); /// Doxygen comment. /// \param int a int bar(int a); /* Multi-line block comment */ int baz(); int x = ^ )cpp"); EXPECT_THAT(Results.Completions, Contains(AllOf( named("foo"), doc("Annotation: custom_annotation\nNon-doxygen comment.")))); EXPECT_THAT( Results.Completions, Contains(AllOf(named("bar"), doc("Doxygen comment.\n\\param int a")))); EXPECT_THAT(Results.Completions, Contains(AllOf(named("baz"), doc("Multi-line block comment")))); } TEST(CompletionTest, CommentsFromSystemHeaders) { MockFS FS; MockCompilationDatabase CDB; auto Opts = ClangdServer::optsForTest(); Opts.BuildDynamicSymbolIndex = true; ClangdServer Server(CDB, FS, Opts); FS.Files[testPath("foo.h")] = R"cpp( #pragma GCC system_header // This comment should be retained! int foo(); )cpp"; auto File = testPath("foo.cpp"); Annotations Test(R"cpp( #include "foo.h" int x = foo^ )cpp"); runAddDocument(Server, File, Test.code()); auto CompletionList = llvm::cantFail(runCodeComplete(Server, File, Test.point(), {})); EXPECT_THAT( CompletionList.Completions, Contains(AllOf(named("foo"), doc("This comment should be retained!")))); } TEST(CompletionTest, GlobalCompletionFiltering) { Symbol Class = cls("XYZ"); Class.Flags = static_cast( Class.Flags & ~(Symbol::IndexedForCodeCompletion)); Symbol Func = func("XYZ::foooo"); Func.Flags = static_cast( Func.Flags & ~(Symbol::IndexedForCodeCompletion)); auto Results = completions(R"(// void f() { XYZ::foooo^ })", {Class, Func}); EXPECT_THAT(Results.Completions, IsEmpty()); } TEST(CodeCompleteTest, DisableTypoCorrection) { auto Results = completions(R"cpp( namespace clang { int v; } void f() { clangd::^ )cpp"); EXPECT_TRUE(Results.Completions.empty()); } TEST(CodeCompleteTest, NoColonColonAtTheEnd) { auto Results = completions(R"cpp( namespace clang { } void f() { clan^ } )cpp"); EXPECT_THAT(Results.Completions, Contains(labeled("clang"))); EXPECT_THAT(Results.Completions, Not(Contains(labeled("clang::")))); } TEST(CompletionTests, EmptySnippetDoesNotCrash) { // See https://github.com/clangd/clangd/issues/1216 auto Results = completions(R"cpp( int main() { auto w = [&](auto &&f) { return f(f); }; auto f = w([&](auto &&f) { return [&](auto &&n) { if (n == 0) { return 1; } return n * ^(f)(n - 1); }; })(10); } )cpp"); } TEST(CompletionTest, Issue1427Crash) { // Need to provide main file signals to ensure that the branch in // SymbolRelevanceSignals::computeASTSignals() that tries to // compute a symbol ID is taken. ASTSignals MainFileSignals; CodeCompleteOptions Opts; Opts.MainFileSignals = &MainFileSignals; completions(R"cpp( auto f = []() { 1.0_^ }; )cpp", {}, Opts); } TEST(CompletionTest, BacktrackCrashes) { // Sema calls code completion callbacks twice in these cases. auto Results = completions(R"cpp( namespace ns { struct FooBarBaz {}; } // namespace ns int foo(ns::FooBar^ )cpp"); EXPECT_THAT(Results.Completions, ElementsAre(labeled("FooBarBaz"))); // Check we don't crash in that case too. completions(R"cpp( struct FooBarBaz {}; void test() { if (FooBarBaz * x^) {} } )cpp"); } TEST(CompletionTest, CompleteInMacroWithStringification) { auto Results = completions(R"cpp( void f(const char *, int x); #define F(x) f(#x, x) namespace ns { int X; int Y; } // namespace ns int f(int input_num) { F(ns::^) } )cpp"); EXPECT_THAT(Results.Completions, UnorderedElementsAre(named("X"), named("Y"))); } TEST(CompletionTest, CompleteInMacroAndNamespaceWithStringification) { auto Results = completions(R"cpp( void f(const char *, int x); #define F(x) f(#x, x) namespace ns { int X; int f(int input_num) { F(^) } } // namespace ns )cpp"); EXPECT_THAT(Results.Completions, Contains(named("X"))); } TEST(CompletionTest, IgnoreCompleteInExcludedPPBranchWithRecoveryContext) { auto Results = completions(R"cpp( int bar(int param_in_bar) { } int foo(int param_in_foo) { #if 0 // In recovery mode, "param_in_foo" will also be suggested among many other // unrelated symbols; however, this is really a special case where this works. // If the #if block is outside of the function, "param_in_foo" is still // suggested, but "bar" and "foo" are missing. So the recovery mode doesn't // really provide useful results in excluded branches. par^ #endif } )cpp"); EXPECT_TRUE(Results.Completions.empty()); } TEST(CompletionTest, DefaultArgs) { clangd::CodeCompleteOptions Opts; std::string Context = R"cpp( int X(int A = 0); int Y(int A, int B = 0); int Z(int A, int B = 0, int C = 0, int D = 0); )cpp"; EXPECT_THAT(completions(Context + "int y = X^", {}, Opts).Completions, UnorderedElementsAre(labeled("X(int A = 0)"))); EXPECT_THAT(completions(Context + "int y = Y^", {}, Opts).Completions, UnorderedElementsAre(AllOf(labeled("Y(int A, int B = 0)"), snippetSuffix("(${1:int A})")))); EXPECT_THAT(completions(Context + "int y = Z^", {}, Opts).Completions, UnorderedElementsAre( AllOf(labeled("Z(int A, int B = 0, int C = 0, int D = 0)"), snippetSuffix("(${1:int A})")))); } TEST(CompletionTest, NoCrashWithTemplateParamsAndPreferredTypes) { auto Completions = completions(R"cpp( template