//===-- CodeCompleteTests.cpp -----------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "ClangdServer.h" #include "Compiler.h" #include "Protocol.h" #include "TestFS.h" #include "gtest/gtest.h" namespace clang { namespace clangd { namespace { using namespace llvm; class IgnoreDiagnostics : public DiagnosticsConsumer { void onDiagnosticsReady( PathRef File, Tagged> Diagnostics) override {} }; struct StringWithPos { std::string Text; clangd::Position MarkerPos; }; /// Returns location of "{mark}" substring in \p Text and removes it from \p /// Text. Note that \p Text must contain exactly one occurence of "{mark}". /// /// Marker name can be configured using \p MarkerName parameter. StringWithPos parseTextMarker(StringRef Text, StringRef MarkerName = "mark") { SmallString<16> Marker; Twine("{" + MarkerName + "}").toVector(/*ref*/ Marker); std::size_t MarkerOffset = Text.find(Marker); assert(MarkerOffset != StringRef::npos && "{mark} wasn't found in Text."); std::string WithoutMarker; WithoutMarker += Text.take_front(MarkerOffset); WithoutMarker += Text.drop_front(MarkerOffset + Marker.size()); assert(StringRef(WithoutMarker).find(Marker) == StringRef::npos && "There were multiple occurences of {mark} inside Text"); clangd::Position MarkerPos = clangd::offsetToPosition(WithoutMarker, MarkerOffset); return {std::move(WithoutMarker), MarkerPos}; } class ClangdCompletionTest : public ::testing::Test { protected: template bool ContainsItemPred(CompletionList const &Items, Predicate Pred) { for (const auto &Item : Items.items) { if (Pred(Item)) return true; } return false; } bool ContainsItem(CompletionList const &Items, StringRef Name) { return ContainsItemPred(Items, [Name](clangd::CompletionItem Item) { return Item.insertText == Name; }); return false; } }; TEST_F(ClangdCompletionTest, CheckContentsOverride) { MockFSProvider FS; IgnoreDiagnostics DiagConsumer; MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, clangd::CodeCompleteOptions(), EmptyLogger::getInstance()); auto FooCpp = getVirtualTestFilePath("foo.cpp"); const auto SourceContents = R"cpp( int aba; int b = ; )cpp"; const auto OverridenSourceContents = R"cpp( int cbc; int b = ; )cpp"; // Complete after '=' sign. We need to be careful to keep the SourceContents' // size the same. // We complete on the 3rd line (2nd in zero-based numbering), because raw // string literal of the SourceContents starts with a newline(it's easy to // miss). Position CompletePos = {2, 8}; FS.Files[FooCpp] = SourceContents; FS.ExpectedFile = FooCpp; // No need to sync reparses here as there are no asserts on diagnostics (or // other async operations). Server.addDocument(FooCpp, SourceContents); { auto CodeCompletionResults1 = Server.codeComplete(FooCpp, CompletePos, None).get().Value; EXPECT_TRUE(ContainsItem(CodeCompletionResults1, "aba")); EXPECT_FALSE(ContainsItem(CodeCompletionResults1, "cbc")); } { auto CodeCompletionResultsOverriden = Server .codeComplete(FooCpp, CompletePos, StringRef(OverridenSourceContents)) .get() .Value; EXPECT_TRUE(ContainsItem(CodeCompletionResultsOverriden, "cbc")); EXPECT_FALSE(ContainsItem(CodeCompletionResultsOverriden, "aba")); } { auto CodeCompletionResults2 = Server.codeComplete(FooCpp, CompletePos, None).get().Value; EXPECT_TRUE(ContainsItem(CodeCompletionResults2, "aba")); EXPECT_FALSE(ContainsItem(CodeCompletionResults2, "cbc")); } } TEST_F(ClangdCompletionTest, Limit) { MockFSProvider FS; MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); CDB.ExtraClangFlags.push_back("-xc++"); IgnoreDiagnostics DiagConsumer; clangd::CodeCompleteOptions Opts; Opts.Limit = 2; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, Opts, EmptyLogger::getInstance()); auto FooCpp = getVirtualTestFilePath("foo.cpp"); FS.Files[FooCpp] = ""; FS.ExpectedFile = FooCpp; StringWithPos Completion = parseTextMarker(R"cpp( struct ClassWithMembers { int AAA(); int BBB(); int CCC(); } int main() { ClassWithMembers().{complete} } )cpp", "complete"); Server.addDocument(FooCpp, Completion.Text); /// For after-dot completion we must always get consistent results. auto Results = Server .codeComplete(FooCpp, Completion.MarkerPos, StringRef(Completion.Text)) .get() .Value; EXPECT_TRUE(Results.isIncomplete); EXPECT_EQ(Opts.Limit, Results.items.size()); EXPECT_TRUE(ContainsItem(Results, "AAA")); EXPECT_TRUE(ContainsItem(Results, "BBB")); EXPECT_FALSE(ContainsItem(Results, "CCC")); } TEST_F(ClangdCompletionTest, Filter) { MockFSProvider FS; MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); CDB.ExtraClangFlags.push_back("-xc++"); IgnoreDiagnostics DiagConsumer; clangd::CodeCompleteOptions Opts; ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, Opts, EmptyLogger::getInstance()); auto FooCpp = getVirtualTestFilePath("foo.cpp"); FS.Files[FooCpp] = ""; FS.ExpectedFile = FooCpp; const char *Body = R"cpp( int Abracadabra; int Alakazam; struct S { int FooBar; int FooBaz; int Qux; }; )cpp"; auto Complete = [&](StringRef Query) { StringWithPos Completion = parseTextMarker( formatv("{0} int main() { {1}{{complete}} }", Body, Query).str(), "complete"); Server.addDocument(FooCpp, Completion.Text); return Server .codeComplete(FooCpp, Completion.MarkerPos, StringRef(Completion.Text)) .get() .Value; }; auto Foba = Complete("S().Foba"); EXPECT_TRUE(ContainsItem(Foba, "FooBar")); EXPECT_TRUE(ContainsItem(Foba, "FooBaz")); EXPECT_FALSE(ContainsItem(Foba, "Qux")); auto FR = Complete("S().FR"); EXPECT_TRUE(ContainsItem(FR, "FooBar")); EXPECT_FALSE(ContainsItem(FR, "FooBaz")); EXPECT_FALSE(ContainsItem(FR, "Qux")); auto Op = Complete("S().opr"); EXPECT_TRUE(ContainsItem(Op, "operator=")); auto Aaa = Complete("aaa"); EXPECT_TRUE(ContainsItem(Aaa, "Abracadabra")); EXPECT_TRUE(ContainsItem(Aaa, "Alakazam")); auto UA = Complete("_a"); EXPECT_TRUE(ContainsItem(UA, "static_cast")); EXPECT_FALSE(ContainsItem(UA, "Abracadabra")); } TEST_F(ClangdCompletionTest, CompletionOptions) { MockFSProvider FS; IgnoreDiagnostics DiagConsumer; MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); CDB.ExtraClangFlags.push_back("-xc++"); auto FooCpp = getVirtualTestFilePath("foo.cpp"); FS.Files[FooCpp] = ""; FS.ExpectedFile = FooCpp; const auto GlobalCompletionSourceTemplate = R"cpp( #define MACRO X int global_var; int global_func(); struct GlobalClass {}; struct ClassWithMembers { /// Doc for method. int method(); }; int test() { struct LocalClass {}; /// Doc for local_var. int local_var; {complete} } )cpp"; const auto MemberCompletionSourceTemplate = R"cpp( #define MACRO X int global_var; int global_func(); 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().{complete} } )cpp"; StringWithPos GlobalCompletion = parseTextMarker(GlobalCompletionSourceTemplate, "complete"); StringWithPos MemberCompletion = parseTextMarker(MemberCompletionSourceTemplate, "complete"); auto TestWithOpts = [&](clangd::CodeCompleteOptions Opts) { ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true, Opts, EmptyLogger::getInstance()); // No need to sync reparses here as there are no asserts on diagnostics (or // other async operations). Server.addDocument(FooCpp, GlobalCompletion.Text); StringRef MethodItemText = Opts.EnableSnippets ? "method()" : "method"; StringRef GlobalFuncItemText = Opts.EnableSnippets ? "global_func()" : "global_func"; /// For after-dot completion we must always get consistent results. { auto Results = Server .codeComplete(FooCpp, MemberCompletion.MarkerPos, StringRef(MemberCompletion.Text)) .get() .Value; // Class members. The only items that must be present in after-dor // completion. EXPECT_TRUE(ContainsItem(Results, MethodItemText)); EXPECT_TRUE(ContainsItem(Results, MethodItemText)); EXPECT_TRUE(ContainsItem(Results, "field")); EXPECT_EQ(Opts.IncludeIneligibleResults, ContainsItem(Results, "private_field")); // Global items. EXPECT_FALSE(ContainsItem(Results, "global_var")); EXPECT_FALSE(ContainsItem(Results, GlobalFuncItemText)); EXPECT_FALSE(ContainsItem(Results, "GlobalClass")); // A macro. EXPECT_FALSE(ContainsItem(Results, "MACRO")); // Local items. EXPECT_FALSE(ContainsItem(Results, "LocalClass")); // There should be no code patterns (aka snippets) in after-dot // completion. At least there aren't any we're aware of. EXPECT_FALSE( ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { return Item.kind == clangd::CompletionItemKind::Snippet; })); // Check documentation. EXPECT_EQ( Opts.IncludeBriefComments, ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { return !Item.documentation.empty(); })); } // Global completion differs based on the Opts that were passed. { auto Results = Server .codeComplete(FooCpp, GlobalCompletion.MarkerPos, StringRef(GlobalCompletion.Text)) .get() .Value; // Class members. Should never be present in global completions. EXPECT_FALSE(ContainsItem(Results, MethodItemText)); EXPECT_FALSE(ContainsItem(Results, "field")); // Global items. EXPECT_EQ(ContainsItem(Results, "global_var"), Opts.IncludeGlobals); EXPECT_EQ(ContainsItem(Results, GlobalFuncItemText), Opts.IncludeGlobals); EXPECT_EQ(ContainsItem(Results, "GlobalClass"), Opts.IncludeGlobals); // A macro. EXPECT_EQ(ContainsItem(Results, "MACRO"), Opts.IncludeMacros); // Local items. Must be present always. EXPECT_TRUE(ContainsItem(Results, "local_var")); EXPECT_TRUE(ContainsItem(Results, "LocalClass")); // FIXME(ibiryukov): snippets have wrong Item.kind now. Reenable this // check after https://reviews.llvm.org/D38720 makes it in. // // Code patterns (aka snippets). // EXPECT_EQ( // Opts.IncludeCodePatterns && Opts.EnableSnippets, // ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { // return Item.kind == clangd::CompletionItemKind::Snippet; // })); // Check documentation. EXPECT_EQ( Opts.IncludeBriefComments, ContainsItemPred(Results, [](clangd::CompletionItem const &Item) { return !Item.documentation.empty(); })); } }; clangd::CodeCompleteOptions CCOpts; for (bool IncludeMacros : {true, false}) { CCOpts.IncludeMacros = IncludeMacros; for (bool IncludeGlobals : {true, false}) { CCOpts.IncludeGlobals = IncludeGlobals; for (bool IncludeBriefComments : {true, false}) { CCOpts.IncludeBriefComments = IncludeBriefComments; for (bool EnableSnippets : {true, false}) { CCOpts.EnableSnippets = EnableSnippets; for (bool IncludeCodePatterns : {true, false}) { CCOpts.IncludeCodePatterns = IncludeCodePatterns; for (bool IncludeIneligibleResults : {true, false}) { CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; TestWithOpts(CCOpts); } } } } } } } } // namespace } // namespace clangd } // namespace clang