//===-- 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 "Annotations.h" #include "ClangdServer.h" #include "CodeComplete.h" #include "Compiler.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 "support/Threading.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Testing/Support/Annotations.h" #include "llvm/Testing/Support/Error.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(NameStartsWith, Prefix, "") { return llvm::StringRef(arg.Name).startswith(Prefix); } 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(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; } // 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.hasValue(); } 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; } 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"))); } 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_IFF(Opts.IncludeComments, 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_IFF(Opts.IncludeMacros, 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_IFF(Opts.IncludeComments, 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::IncludeMacros, &clangd::CodeCompleteOptions::IncludeComments, &clangd::CodeCompleteOptions::IncludeCodePatterns, &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")))); } 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"))))); } 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, 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::Text), 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) { auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")}); EXPECT_THAT(Results.Completions, HasSubsequence(Named("absb"), Named("absl"))); Results = completions("int main() { abs^ }", {withReferences(10000, ns("absl")), func("absb")}); EXPECT_THAT(Results.Completions, HasSubsequence(Named("absl"), Named("absb"))); } 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); // 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); SymY.IncludeHeaders.emplace_back("", 1); // 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. 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("Non-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(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