Index: test/clangd/completion-items-kinds.test =================================================================== --- test/clangd/completion-items-kinds.test +++ /dev/null @@ -1,42 +0,0 @@ -# RUN: clangd -enable-snippets -run-synchronously < %s | FileCheck %s -# It is absolutely vital that this file has CRLF line endings. -# -Content-Length: 125 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -Content-Length: 220 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO X\nint variable;\nstruct Struct {};\n int function();\nint X = "}}} -Content-Length: 148 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}} -# CHECK: {"id":1,"jsonrpc":"2.0","result":{"isIncomplete":false,"items": -# -# Function -# CHECK: {"detail":"int","filterText":"function","insertText":"function()","insertTextFormat":1,"kind":3,"label":"function()","sortText":"{{.*}}function"} -# -# Variable -# CHECK: {"detail":"int","filterText":"variable","insertText":"variable","insertTextFormat":1,"kind":6,"label":"variable","sortText":"{{.*}}variable"} -# -# Keyword -# CHECK: {"filterText":"int","insertText":"int","insertTextFormat":1,"kind":14,"label":"int","sortText":"{{.*}}int"} -# -# Struct -# CHECK: {"filterText":"Struct","insertText":"Struct","insertTextFormat":1,"kind":7,"label":"Struct","sortText":"{{.*}}Struct"} -# -# Macro -# CHECK: {"filterText":"MACRO","insertText":"MACRO","insertTextFormat":1,"kind":1,"label":"MACRO","sortText":"{{.*}}MACRO"} -# -# CHECK-SAME: ]}} -Content-Length: 146 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"nam"}}} -Content-Length: 148 - -{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}} -# Code pattern (unfortunately there are none in expression context) -# CHECK-DAG: {"filterText":"namespace","insertText":"namespace ${1:identifier}{${2:declarations}\n}","insertTextFormat":2,"kind":15,"label":"namespace identifier{declarations}","sortText":"{{.*}}namespace"} -# -Content-Length: 58 - -{"jsonrpc":"2.0","id":3,"method":"shutdown","params":null} Index: test/clangd/completion-priorities.test =================================================================== --- test/clangd/completion-priorities.test +++ /dev/null @@ -1,73 +0,0 @@ -# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s -# It is absolutely vital that this file has CRLF line endings. -# - -Content-Length: 127 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} - -Content-Length: 312 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"class Foo {\npublic:\n void pub();\n\nprotected:\n void prot();\n\nprivate:\n void priv();\n};\n\nvoid Foo::pub() {\n this->\n}\n\nvoid test() {\n Foo f;\n f.\n}"}}} - -Content-Length: 151 - -{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":12,"character":8}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "void", -# CHECK-NEXT: "filterText": "priv", -# CHECK-NEXT: "insertText": "priv", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "priv()", -# CHECK-NEXT: "sortText": "{{.*}}priv" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "void", -# CHECK-NEXT: "filterText": "prot", -# CHECK-NEXT: "insertText": "prot", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "prot()", -# CHECK-NEXT: "sortText": "{{.*}}prot" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "void", -# CHECK-NEXT: "filterText": "pub", -# CHECK-NEXT: "insertText": "pub", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "pub()", -# CHECK-NEXT: "sortText": "{{.*}}pub" -# CHECK-NEXT: }, -Content-Length: 151 - -{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":4}}} -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "void", -# CHECK-NEXT: "filterText": "pub", -# CHECK-NEXT: "insertText": "pub", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "pub()", -# CHECK-NEXT: "sortText": "{{.*}}pub" -# CHECK-NEXT: } -# CHECK-NOT: "label": "priv()", -# CHECK-NOT: "label": "prot()", -# CHECK: ] -Content-Length: 58 - -{"jsonrpc":"2.0","id":4,"method":"shutdown","params":null} -Content-Length: 33 - -{"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion-qualifiers.test =================================================================== --- test/clangd/completion-qualifiers.test +++ /dev/null @@ -1,43 +0,0 @@ -# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s -Content-Length: 125 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -Content-Length: 297 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"class Foo {\n public:\n int foo() const;\n int bar() const;\n};\n\nclass Bar : public Foo {\n int foo() const;\n};\n\nvoid test() {\n Bar().\n}"}}} -Content-Length: 151 - -{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":11,"character":8}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# Eligible functions are at the top of the list. -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "bar", -# CHECK-NEXT: "insertText": "bar", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "bar() const", -# CHECK-NEXT: "sortText": "{{.*}}bar" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "foo", -# CHECK-NEXT: "insertText": "foo", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "Foo::foo() const", -# CHECK-NEXT: "sortText": "{{.*}}foo" -# CHECK-NEXT: }, -# Ineligible private functions are not present. -# CHECK-NOT: "label": "foo() const", -# CHECK: ] -Content-Length: 44 - -{"jsonrpc":"2.0","id":4,"method":"shutdown"} -Content-Length: 33 - -{"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion-snippet.test =================================================================== --- test/clangd/completion-snippet.test +++ /dev/null @@ -1,102 +0,0 @@ -# RUN: clangd -pretty -run-synchronously -enable-snippets < %s | FileCheck -strict-whitespace %s -# It is absolutely vital that this file has CRLF line endings. -# -Content-Length: 125 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} - -Content-Length: 246 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"struct fake { int a, bb, ccc; int f(int i, const float f) const; };\nint main() {\n fake f;\n f.\n}\n"}}} - -Content-Length: 148 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "a", -# CHECK-NEXT: "insertText": "a", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": "a", -# CHECK-NEXT: "sortText": "{{.*}}a" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "bb", -# CHECK-NEXT: "insertText": "bb", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": "bb", -# CHECK-NEXT: "sortText": "{{.*}}bb" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "ccc", -# CHECK-NEXT: "insertText": "ccc", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": "ccc", -# CHECK-NEXT: "sortText": "{{.*}}ccc" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "f", -# CHECK-NEXT: "insertText": "f(${1:int i}, ${2:const float f})", -# CHECK-NEXT: "insertTextFormat": 2, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "f(int i, const float f) const", -# CHECK-NEXT: "sortText": "{{.*}}f" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "filterText": "fake", -# CHECK-NEXT: "insertText": "fake::", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 7, -# CHECK-NEXT: "label": "fake::", -# CHECK-NEXT: "sortText": "{{.*}}fake" -# CHECK-NEXT: }, -# CHECK: { -# CHECK: "detail": "void", -# CHECK-NEXT: "filterText": "~fake", -# CHECK-NEXT: "insertText": "~fake()", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 4, -# CHECK-NEXT: "label": "~fake()", -# CHECK-NEXT: "sortText": "{{.*}}~fake" -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# Update the source file and check for completions again. -Content-Length: 226 - -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct fancy { int (*func())(int, int); };\nint main() {\n fancy f;\n f.\n}\n"}]}} - -Content-Length: 148 - -{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int (*)(int, int)", -# CHECK-NEXT: "filterText": "func", -# CHECK-NEXT: "insertText": "func()", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "label": "func()", -# CHECK-NEXT: "sortText": "{{.*}}func" -# CHECK-NEXT: }, -Content-Length: 44 - -{"jsonrpc":"2.0","id":4,"method":"shutdown"} -Content-Length: 33 - -{"jsonrpc":"2.0":"method":"exit"} Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "ClangdServer.h" #include "Compiler.h" +#include "Matchers.h" #include "Protocol.h" #include "TestFS.h" #include "gmock/gmock.h" @@ -18,7 +19,16 @@ // Let GMock print completion items. void PrintTo(const CompletionItem &I, std::ostream *O) { llvm::raw_os_ostream OS(*O); - OS << toJSON(I); + OS << I.label << " - " << toJSON(I); +} +void PrintTo(const std::vector &V, std::ostream *O) { + *O << "{\n"; + for (const auto &I : V) { + *O << "\t"; + PrintTo(I, O); + *O << "\n"; + } + *O << "}"; } namespace { @@ -26,7 +36,6 @@ using ::testing::AllOf; using ::testing::Contains; using ::testing::ElementsAre; -using ::testing::Matcher; using ::testing::Not; class IgnoreDiagnostics : public DiagnosticsConsumer { @@ -57,22 +66,25 @@ // GMock helpers for matching completion items. MATCHER_P(Named, Name, "") { return arg.insertText == Name; } +MATCHER_P(Labeled, Label, "") { return arg.label == Label; } +MATCHER_P(Kind, K, "") { return arg.kind == K; } +MATCHER_P(PlainText, Text, "") { + return arg.insertTextFormat == clangd::InsertTextFormat::PlainText && + arg.insertText == Text; +} +MATCHER_P(Snippet, Text, "") { + return arg.insertTextFormat == clangd::InsertTextFormat::Snippet && + arg.insertText == Text; +} // Shorthand for Contains(Named(Name)). Matcher &> Has(std::string Name) { return Contains(Named(std::move(Name))); } -MATCHER(IsDocumented, "") { return !arg.documentation.empty(); } -MATCHER(IsSnippet, "") { - return arg.kind == clangd::CompletionItemKind::Snippet; +Matcher &> Has(std::string Name, + CompletionItemKind K) { + return Contains(AllOf(Named(std::move(Name)), Kind(K))); } -// This is hard to write as a function, because matchers may be polymorphic. -#define EXPECT_IFF(condition, value, matcher) \ - do { \ - if (condition) \ - EXPECT_THAT(value, matcher); \ - else \ - EXPECT_THAT(value, ::testing::Not(matcher)); \ - } while (0) +MATCHER(IsDocumented, "") { return !arg.documentation.empty(); } CompletionList completions(StringRef Text, clangd::CodeCompleteOptions Opts = {}) { @@ -133,93 +145,97 @@ } void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions(R"cpp( -#define MACRO X + auto Results = completions( + R"cpp( + #define MACRO X -int global_var; + int global_var; -int global_func(); + int global_func(); -struct GlobalClass {}; + struct GlobalClass {}; -struct ClassWithMembers { - /// Doc for method. - int method(); + struct ClassWithMembers { + /// Doc for method. + int method(); - int field; -private: - int private_field; -}; + int field; + private: + int private_field; + }; -int test() { - struct LocalClass {}; + int test() { + struct LocalClass {}; - /// Doc for local_var. - int local_var; + /// Doc for local_var. + int local_var; - ClassWithMembers().^ -} -)cpp", - Opts) - .items; + ClassWithMembers().^ + } + )cpp", + Opts); // Class members. The only items that must be present in after-dot // completion. - EXPECT_THAT(Results, AllOf(Has(Opts.EnableSnippets ? "method()" : "method"), - Has("field"))); - EXPECT_IFF(Opts.IncludeIneligibleResults, Results, Has("private_field")); + EXPECT_THAT( + Results.items, + AllOf(Has(Opts.EnableSnippets ? "method()" : "method"), Has("field"))); + EXPECT_IFF(Opts.IncludeIneligibleResults, Results.items, + Has("private_field")); // Global items. - EXPECT_THAT(Results, Not(AnyOf(Has("global_var"), Has("global_func"), - Has("global_func()"), Has("GlobalClass"), - Has("MACRO"), Has("LocalClass")))); + EXPECT_THAT(Results.items, Not(AnyOf(Has("global_var"), Has("global_func"), + Has("global_func()"), Has("GlobalClass"), + 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, Not(Contains(IsSnippet()))); + EXPECT_THAT(Results.items, Not(Contains(Kind(CompletionItemKind::Snippet)))); // Check documentation. - EXPECT_IFF(Opts.IncludeBriefComments, Results, Contains(IsDocumented())); + EXPECT_IFF(Opts.IncludeBriefComments, Results.items, + Contains(IsDocumented())); } void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions(R"cpp( -#define MACRO X + auto Results = completions( + R"cpp( + #define MACRO X -int global_var; -int global_func(); + int global_var; + int global_func(); -struct GlobalClass {}; + struct GlobalClass {}; -struct ClassWithMembers { - /// Doc for method. - int method(); -}; + struct ClassWithMembers { + /// Doc for method. + int method(); + }; -int test() { - struct LocalClass {}; + int test() { + struct LocalClass {}; - /// Doc for local_var. - int local_var; + /// Doc for local_var. + int local_var; - ^ -} -)cpp", - Opts) - .items; + ^ + } + )cpp", + Opts); // Class members. Should never be present in global completions. - EXPECT_THAT(Results, + EXPECT_THAT(Results.items, Not(AnyOf(Has("method"), Has("method()"), Has("field")))); // Global items. - EXPECT_IFF(Opts.IncludeGlobals, Results, + EXPECT_IFF(Opts.IncludeGlobals, Results.items, AllOf(Has("global_var"), Has(Opts.EnableSnippets ? "global_func()" : "global_func"), Has("GlobalClass"))); // A macro. - EXPECT_IFF(Opts.IncludeMacros, Results, Has("MACRO")); + EXPECT_IFF(Opts.IncludeMacros, Results.items, Has("MACRO")); // Local items. Must be present always. - EXPECT_THAT(Results, AllOf(Has("local_var"), Has("LocalClass"), - Contains(IsSnippet()))); + EXPECT_THAT(Results.items, AllOf(Has("local_var"), Has("LocalClass"), + Contains(Kind(CompletionItemKind::Snippet)))); // Check documentation. - EXPECT_IFF(Opts.IncludeBriefComments, Results, Contains(IsDocumented())); + EXPECT_IFF(Opts.IncludeBriefComments, Results.items, + Contains(IsDocumented())); } TEST(CompletionTest, CompletionOptions) { @@ -267,6 +283,90 @@ EXPECT_THAT(Results.items, Contains(Named("cbc"))); } +TEST(CompletionTest, Priorities) { + auto Internal = completions(R"cpp( + class Foo { + public: void pub(); + protected: void prot(); + private: void priv(); + }; + void Foo::pub() { this->^ } + )cpp"); + EXPECT_THAT(Internal.items, + HasSubsequence(Named("priv"), Named("prot"), Named("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.items, + 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.items, HasSubsequence(Labeled("bar() const"), + Labeled("Foo::foo() const"))); + EXPECT_THAT(Results.items, Not(Contains(Labeled("foo() const")))); // private +} + +TEST(CompletionTest, Snippets) { + clangd::CodeCompleteOptions Opts; + Opts.EnableSnippets = true; + auto Results = completions( + R"cpp( + struct fake { + int a; + int f(int i, const float f) const; + }; + int main() { + fake f; + f.^ + } + )cpp", + Opts); + EXPECT_THAT(Results.items, + HasSubsequence(PlainText("a"), + Snippet("f(${1:int i}, ${2:const float f})"))); +} + +TEST(CompletionTest, Kinds) { + auto Results = completions(R"cpp( + #define MACRO X + int variable; + struct Struct {}; + int function(); + int X = ^ + )cpp"); + EXPECT_THAT(Results.items, Has("function", CompletionItemKind::Function)); + EXPECT_THAT(Results.items, Has("variable", CompletionItemKind::Variable)); + EXPECT_THAT(Results.items, Has("int", CompletionItemKind::Keyword)); + EXPECT_THAT(Results.items, Has("Struct", CompletionItemKind::Class)); + EXPECT_THAT(Results.items, Has("MACRO", CompletionItemKind::Text)); + + clangd::CodeCompleteOptions Opts; + Opts.EnableSnippets = true; // Needed for code patterns. + + Results = completions("nam^"); + EXPECT_THAT(Results.items, Has("namespace", CompletionItemKind::Snippet)); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/Matchers.h =================================================================== --- /dev/null +++ unittests/clangd/Matchers.h @@ -0,0 +1,112 @@ +//===-- Matchers.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// GMock matchers that aren't specific to particular tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H +#include "gmock/gmock.h" + +namespace clang { +namespace clangd { +using ::testing::Matcher; + +// EXPECT_IFF expects matcher if condition is true, and Not(matcher) if false. +// This is hard to write as a function, because matchers may be polymorphic. +#define EXPECT_IFF(condition, value, matcher) \ + do { \ + if (condition) \ + EXPECT_THAT(value, matcher); \ + else \ + EXPECT_THAT(value, ::testing::Not(matcher)); \ + } while (0) + +// HasSubsequence(m1, m2, ...) matches a vector containing elements that match +// m1, m2 ... in that order. +// +// SubsequenceMatcher implements this once the type of vector is known. +template +class SubsequenceMatcher + : public ::testing::MatcherInterface &> { + std::vector> Matchers; + +public: + SubsequenceMatcher(std::vector> M) : Matchers(M) {} + + void DescribeTo(std::ostream *OS) const override { + *OS << "Contains the subsequence ["; + const char *Sep = ""; + for (const auto &M : Matchers) { + *OS << Sep; + M.DescribeTo(OS); + Sep = ", "; + } + *OS << "]"; + } + + bool MatchAndExplain(const std::vector &V, + ::testing::MatchResultListener *L) const override { + std::vector Matches(Matchers.size()); + size_t I = 0; + for (size_t J = 0; I < Matchers.size() && J < V.size(); ++J) + if (Matchers[I].Matches(V[J])) + Matches[I++] = J; + if (I == Matchers.size()) // We exhausted all matchers. + return true; + if (L->IsInterested()) { + *L << "\n Matched:"; + for (size_t K = 0; K < I; ++K) { + *L << "\n\t"; + Matchers[K].DescribeTo(L->stream()); + *L << " ==> " << ::testing::PrintToString(V[Matches[K]]); + } + *L << "\n\t"; + Matchers[I].DescribeTo(L->stream()); + *L << " ==> no subsequent match"; + } + return false; + } +}; + +// PolySubsequenceMatcher implements a "polymorphic" SubsequenceMatcher. +// It captures the types of the element matchers, and can be converted to +// Matcher> if each matcher can be converted to Matcher. +// This allows HasSubsequence() to accept polymorphic matchers like Not(). +template class PolySubsequenceMatcher { + std::tuple Matchers; + +public: + PolySubsequenceMatcher(M &&... Args) + : Matchers(std::make_tuple(std::forward(Args)...)) {} + + template operator Matcher &>() const { + return ::testing::MakeMatcher(new SubsequenceMatcher( + TypedMatchers(llvm::index_sequence_for{}))); + } + +private: + template + std::vector> TypedMatchers(llvm::index_sequence) const { + return {std::get(Matchers)...}; + } +}; + +// HasSubsequence(m1, m2, ...) matches a vector containing elements that match +// m1, m2 ... in that order. +// The real implementation is in SubsequenceMatcher. +template +PolySubsequenceMatcher HasSubsequence(Args &&... M) { + return PolySubsequenceMatcher(std::forward(M)...); +} + +} // namespace clangd +} // namespace clang +#endif