Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -10,6 +10,7 @@ DraftStore.cpp GlobalCompilationDatabase.cpp JSONRPCDispatcher.cpp + JSONExpr.cpp Logger.cpp Protocol.cpp ProtocolHandlers.cpp Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -20,35 +20,46 @@ std::vector replacementsToEdits(StringRef Code, const std::vector &Replacements) { - std::vector Edits; // Turn the replacements into the format specified by the Language Server - // Protocol. + // Protocol. Fuse them into one big JSON array. + std::vector Edits; for (auto &R : Replacements) { Range ReplacementRange = { offsetToPosition(Code, R.getOffset()), offsetToPosition(Code, R.getOffset() + R.getLength())}; Edits.push_back({ReplacementRange, R.getReplacementText()}); } - return Edits; } } // namespace void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) { - C.reply( - R"({"capabilities":{ - "textDocumentSync": 1, - "documentFormattingProvider": true, - "documentRangeFormattingProvider": true, - "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, - "codeActionProvider": true, - "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, - "signatureHelpProvider": {"triggerCharacters": ["(",","]}, - "definitionProvider": true, - "executeCommandProvider": {"commands": [")" + - ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + R"("]} - }})"); + C.reply(json::obj{ + {"textDocumentSync", 1}, + {"documentFormattingProvider", true}, + {"documentRangeFormattingProvider", true}, + {"documentOnTypeFormattingProvider", + json::obj{ + {"firstTriggerCharacter", "}"}, + {"moreTriggerCharacter", {}}, + }}, + {"codeActionProvider", true}, + {"completionProvider", + json::obj{ + {"resolveProvider", false}, + {"triggerCharacters", {".", ">", ":"}}, + }}, + {"signatureHelpProvider", + json::obj{ + {"triggerCharacters", {"(", ","}}, + }}, + {"definitionProvider", true}, + {"executeCommandProvider", + json::obj{ + {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + }}, + }); if (Params.rootUri && !Params.rootUri->file.empty()) Server.setRootPath(Params.rootUri->file); else if (Params.rootPath && !Params.rootPath->empty()) @@ -58,7 +69,7 @@ void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) { // Do essentially nothing, just say we're ready to exit. ShutdownRequestReceived = true; - C.reply("null"); + C.reply(nullptr); } void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; } @@ -98,7 +109,7 @@ ApplyWorkspaceEditParams ApplyEdit; ApplyEdit.edit = *Params.workspaceEdit; - C.reply("\"Fix applied.\""); + C.reply("Fix applied."); // We don't need the response so id == 1 is OK. // Ideally, we would wait for the response and if there is no error, we // would reply success/failure to the original RPC. @@ -121,51 +132,45 @@ Ctx C, DocumentOnTypeFormattingParams &Params) { auto File = Params.textDocument.uri.file; std::string Code = Server.getDocument(File); - std::string Edits = TextEdit::unparse( - replacementsToEdits(Code, Server.formatOnType(File, Params.position))); - C.reply(Edits); + C.reply(json::ary( + replacementsToEdits(Code, Server.formatOnType(File, Params.position)))); } void ClangdLSPServer::onDocumentRangeFormatting( Ctx C, DocumentRangeFormattingParams &Params) { auto File = Params.textDocument.uri.file; std::string Code = Server.getDocument(File); - std::string Edits = TextEdit::unparse( - replacementsToEdits(Code, Server.formatRange(File, Params.range))); - C.reply(Edits); + C.reply(json::ary( + replacementsToEdits(Code, Server.formatRange(File, Params.range)))); } void ClangdLSPServer::onDocumentFormatting(Ctx C, DocumentFormattingParams &Params) { auto File = Params.textDocument.uri.file; std::string Code = Server.getDocument(File); - std::string Edits = - TextEdit::unparse(replacementsToEdits(Code, Server.formatFile(File))); - C.reply(Edits); + C.reply(json::ary(replacementsToEdits(Code, Server.formatFile(File)))); } void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. std::string Code = Server.getDocument(Params.textDocument.uri.file); - std::string Commands; + json::ary Commands; for (Diagnostic &D : Params.context.diagnostics) { std::vector Fixes = getFixIts(Params.textDocument.uri.file, D); auto Edits = replacementsToEdits(Code, Fixes); - WorkspaceEdit WE; - WE.changes = {{llvm::yaml::escape(Params.textDocument.uri.uri), Edits}}; - - if (!Edits.empty()) - Commands += - R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) + - R"('", "command": ")" + - ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + - R"(", "arguments": [)" + WorkspaceEdit::unparse(WE) + R"(]},)"; + if (!Edits.empty()) { + WorkspaceEdit WE; + WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}}; + Commands.push_back(json::obj{ + {"title", llvm::formatv("Apply FixIt {0}", D.message)}, + {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}, + {"arguments", {WE}}, + }); + } } - if (!Commands.empty()) - Commands.pop_back(); - C.reply("[" + Commands + "]"); + C.reply(std::move(Commands)); } void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) { @@ -177,15 +182,7 @@ // had an API that would allow to attach callbacks to // futures returned by ClangdServer. .Value; - - std::string Completions; - for (const auto &Item : Items) { - Completions += CompletionItem::unparse(Item); - Completions += ","; - } - if (!Completions.empty()) - Completions.pop_back(); - C.reply("[" + Completions + "]"); + C.reply(json::ary(Items)); } void ClangdLSPServer::onSignatureHelp(Ctx C, @@ -195,7 +192,7 @@ Position{Params.position.line, Params.position.character}); if (!SignatureHelp) return C.replyError(-32602, llvm::toString(SignatureHelp.takeError())); - C.reply(SignatureHelp::unparse(SignatureHelp->Value)); + C.reply(SignatureHelp->Value); } void ClangdLSPServer::onGoToDefinition(Ctx C, @@ -205,22 +202,14 @@ Position{Params.position.line, Params.position.character}); if (!Items) return C.replyError(-32602, llvm::toString(Items.takeError())); - - std::string Locations; - for (const auto &Item : Items->Value) { - Locations += Location::unparse(Item); - Locations += ","; - } - if (!Locations.empty()) - Locations.pop_back(); - C.reply("[" + Locations + "]"); + C.reply(json::ary(Items->Value)); } void ClangdLSPServer::onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) { llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); std::string ResultUri; - C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")"); + C.reply(Result ? URI::fromFile(*Result).uri : ""); } ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, @@ -270,17 +259,16 @@ void ClangdLSPServer::onDiagnosticsReady( PathRef File, Tagged> Diagnostics) { - std::string DiagnosticsJSON; + json::ary DiagnosticsJSON; DiagnosticToReplacementMap LocalFixIts; // Temporary storage for (auto &DiagWithFixes : Diagnostics.Value) { auto Diag = DiagWithFixes.Diag; - DiagnosticsJSON += - R"({"range":)" + Range::unparse(Diag.range) + - R"(,"severity":)" + std::to_string(Diag.severity) + - R"(,"message":")" + llvm::yaml::escape(Diag.message) + - R"("},)"; - + DiagnosticsJSON.push_back(json::obj{ + {"range", Diag.range}, + {"severity", Diag.severity}, + {"message", Diag.message}, + }); // We convert to Replacements to become independent of the SourceManager. auto &FixItsForDiagnostic = LocalFixIts[Diag]; std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(), @@ -295,10 +283,13 @@ } // Publish diagnostics. - if (!DiagnosticsJSON.empty()) - DiagnosticsJSON.pop_back(); // Drop trailing comma. - Out.writeMessage( - R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" + - URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON + - R"(]}})"); + Out.writeMessage(json::obj{ + {"jsonrpc", "2.0"}, + {"method", "textDocument/publishDiagnostics"}, + {"params", + json::obj{ + {"uri", URI::fromFile(File)}, + {"diagnostics", std::move(DiagnosticsJSON)}, + }}, + }); } Index: clangd/JSONExpr.h =================================================================== --- /dev/null +++ clangd/JSONExpr.h @@ -0,0 +1,247 @@ +//===--- JSONExpr.h - composable JSON expressions ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H + +#include + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +namespace json { + +// An Expr is an opaque temporary JSON structure used to compose documents. +// They can be copied, but should generally be moved. +// +// You can implicitly construct literals from: +// - strings: std::string, SmallString, formatv, StringRef, char* +// (char*, and StringRef are references, not copies!) +// - numbers +// - booleans +// - null: nullptr +// - arrays: {"foo", 42.0, false} +// - serializable things: any T with a T::unparse(const T&) -> Expr +// +// They can also be constructed from object/array helpers: +// - json::obj is a type like map +// - json::ary is a type like vector +// These can be list-initialized, or used to build up collections in a loop. +// json::ary(Collection) converts all items in a collection to Exprs. +// +// Exprs can be serialized to JSON: +// 1) raw_ostream << Expr // Basic formatting. +// 2) raw_ostream << formatv("{0}", Expr) // Basic formatting. +// 3) raw_ostream << formatv("{0:2}", Expr) // Pretty-print with indent 2. +class Expr { +public: + class Object; + class ObjectKey; + class Array; + + // It would be nice to have Expr() be null. But that would make {} null too... + Expr(const Expr &M) { copyFrom(M); } + Expr(Expr &&M) { moveFrom(std::move(M)); } + // "cheating" move-constructor for moving from initializer_list. + Expr(const Expr &&M) { moveFrom(std::move(M)); } + Expr(std::initializer_list Elements) : Expr(Array(Elements)) {} + Expr(Array &&Elements) : Type(T_Array) { create(std::move(Elements)); } + Expr(Object &&Properties) : Type(T_Object) { + create(std::move(Properties)); + } + // Strings: types with value semantics. + Expr(std::string &&V) : Type(T_String) { create(std::move(V)); } + Expr(const std::string &V) : Type(T_String) { create(V); } + Expr(const llvm::SmallVectorImpl &V) : Type(T_String) { + create(V.begin(), V.end()); + } + Expr(const llvm::formatv_object_base &V) : Expr(V.str()){}; + // Strings: types with reference semantics. + Expr(llvm::StringRef V) : Type(T_StringRef) { create(V); } + Expr(const char *V) : Type(T_StringRef) { create(V); } + Expr(std::nullptr_t) : Type(T_Null) {} + // Prevent implicit conversions to boolean. + template ::value>::type> + Expr(T B) : Type(T_Boolean) { + create(B); + } + // Numbers: arithmetic types that are not boolean. + template < + typename T, + typename = typename std::enable_if::value>::type, + typename = typename std::enable_if::value>::value>::type> + Expr(T D) : Type(T_Number) { + create(D); + } + // Types with a static T::unparse function returning an Expr. + // FIXME: should this be a free unparse() function found by ADL? + template ::value>> + Expr(const T &V) : Expr(T::unparse(V)) {} + + Expr &operator=(const Expr &M) { + destroy(); + copyFrom(M); + return *this; + } + Expr &operator=(Expr &&M) { + destroy(); + moveFrom(std::move(M)); + return *this; + } + ~Expr() { destroy(); } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Expr &); + +private: + void destroy(); + void copyFrom(const Expr &M); + // We allow moving from *const* Exprs, by marking all members as mutable! + // This hack is needed to support initializer-list syntax efficiently. + // (std::initializer_list is a container of const T). + void moveFrom(const Expr &&M); + + template void create(U &&... V) { + new (&as()) T(std::forward(V)...); + } + template T &as() const { + return *reinterpret_cast(Union.buffer); + } + + template + void print(llvm::raw_ostream &, const Indenter &) const; + friend struct llvm::format_provider; + + enum ExprType : char { + T_Null, + T_Boolean, + T_Number, + T_StringRef, + T_String, + T_Object, + T_Array, + }; + mutable ExprType Type; + +public: + // ObjectKey is a used to capture keys in Expr::Objects. It's like Expr but: + // - only strings are allowed + // - it's copyable (for std::map) + // - we're slightly more eager to copy, to allow efficient key compares + // - it's optimized for the string literal case (Owned == nullptr) + class ObjectKey { + public: + ObjectKey(const char *S) : Data(S) {} + ObjectKey(llvm::StringRef S) : Data(S) {} + ObjectKey(std::string &&V) + : Owned(new std::string(std::move(V))), Data(*Owned) {} + ObjectKey(const std::string &V) : Owned(new std::string(V)), Data(*Owned) {} + ObjectKey(const llvm::SmallVectorImpl &V) + : ObjectKey(std::string(V.begin(), V.end())) {} + ObjectKey(const llvm::formatv_object_base &V) : ObjectKey(V.str()) {} + + ObjectKey(const ObjectKey &C) { *this = C; } + ObjectKey(ObjectKey &&C) = default; + ObjectKey &operator=(const ObjectKey &C) { + if (C.Owned) { + Owned.reset(new std::string(*C.Owned)); + Data = *Owned; + } else { + Data = C.Data; + } + return *this; + } + ObjectKey &operator=(ObjectKey &&) = default; + + operator llvm::StringRef() const { return Data; } + + friend bool operator<(const ObjectKey &L, const ObjectKey &R) { + return L.Data < R.Data; + } + + // "cheating" move-constructor for moving from initializer_list. + ObjectKey(const ObjectKey &&V) { + Owned = std::move(V.Owned); + Data = V.Data; + } + + private: + mutable std::unique_ptr Owned; // mutable for cheating. + llvm::StringRef Data; + }; + + class Object : public std::map { + public: + explicit Object() {} + // Use a custom struct for list-init, because pair forces extra copies. + struct KV; + explicit Object(std::initializer_list Properties); + + // Allow [] as if Expr was default-constructible as null. + Expr &operator[](const ObjectKey &K) { + return emplace(K, Expr(nullptr)).first->second; + } + Expr &operator[](ObjectKey &&K) { + return emplace(std::move(K), Expr(nullptr)).first->second; + } + }; + + class Array : public std::vector { + public: + explicit Array() {} + explicit Array(std::initializer_list Elements) { + reserve(Elements.size()); + for (const Expr &V : Elements) + emplace_back(std::move(V)); + }; + template explicit Array(const Collection &C) { + for (const auto &V : C) + emplace_back(V); + } + }; + +private: + mutable llvm::AlignedCharArrayUnion + Union; +}; + +struct Expr::Object::KV { + ObjectKey K; + Expr V; +}; + +inline Expr::Object::Object(std::initializer_list Properties) { + for (const auto &P : Properties) + emplace(std::move(P.K), std::move(P.V)); +} + +// Give Expr::{Object,Array} more convenient names for literal use. +using obj = Expr::Object; +using ary = Expr::Array; + +} // namespace json +} // namespace clangd +} // namespace clang + +namespace llvm { +template <> struct format_provider { + static void format(const clang::clangd::json::Expr &, raw_ostream &, + StringRef); +}; +} // namespace llvm + +#endif Index: clangd/JSONExpr.cpp =================================================================== --- /dev/null +++ clangd/JSONExpr.cpp @@ -0,0 +1,216 @@ +#include "JSONExpr.h" + +#include "llvm/Support/Format.h" + +namespace clang { +namespace clangd { +namespace json { +using namespace llvm; + +void Expr::copyFrom(const Expr &M) { + Type = M.Type; + switch (Type) { + case T_Null: + case T_Boolean: + case T_Number: + memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer)); + break; + case T_StringRef: + create(M.as()); + break; + case T_String: + create(M.as()); + break; + case T_Object: + create(M.as()); + break; + case T_Array: + create(M.as()); + break; + } +} + +void Expr::moveFrom(const Expr &&M) { + Type = M.Type; + switch (Type) { + case T_Null: + case T_Boolean: + case T_Number: + memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer)); + break; + case T_StringRef: + create(M.as()); + break; + case T_String: + create(std::move(M.as())); + M.Type = T_Null; + break; + case T_Object: + create(std::move(M.as())); + M.Type = T_Null; + break; + case T_Array: + create(std::move(M.as())); + M.Type = T_Null; + break; + } +} + +void Expr::destroy() { + switch (Type) { + case T_Null: + case T_Boolean: + case T_Number: + break; + case T_StringRef: + as().~StringRef(); + break; + case T_String: + as().~basic_string(); + break; + case T_Object: + as().~Object(); + break; + case T_Array: + as().~Array(); + break; + } +} + +} // namespace json +} // namespace clangd +} // namespace clang + +namespace { +void quote(llvm::raw_ostream &OS, llvm::StringRef S) { + OS << '\"'; + for (unsigned char C : S) { + if (C == 0x22 || C == 0x5C) + OS << '\\'; + if (C >= 0x20) { + OS << C; + continue; + } + OS << '\\'; + switch (C) { + // A few characters are common enough to make short escapes worthwhile. + case '\t': + OS << 't'; + break; + case '\n': + OS << 'n'; + break; + case '\r': + OS << 'r'; + break; + default: + OS << 'u'; + llvm::write_hex(OS, C, llvm::HexPrintStyle::Lower, 4); + break; + } + } + OS << '\"'; +} + +enum IndenterAction { + Indent, + Outdent, + Newline, + Space, +}; +} // namespace + +// Prints JSON. The indenter can be used to control formatting. +template +void clang::clangd::json::Expr::print(raw_ostream &OS, + const Indenter &I) const { + switch (Type) { + case T_Null: + OS << "null"; + break; + case T_Boolean: + OS << (as() ? "true" : "false"); + break; + case T_Number: + OS << format("%g", as()); + break; + case T_StringRef: + quote(OS, as()); + break; + case T_String: + quote(OS, as()); + break; + case T_Object: { + bool Comma = false; + OS << '{'; + I(Indent); + for (const auto &P : as()) { + if (Comma) + OS << ','; + Comma = true; + I(Newline); + quote(OS, P.first); + OS << ':'; + I(Space); + P.second.print(OS, I); + } + I(Outdent); + if (Comma) + I(Newline); + OS << '}'; + break; + } + case T_Array: { + bool Comma = false; + OS << '['; + I(Indent); + for (const auto &E : as()) { + if (Comma) + OS << ','; + Comma = true; + I(Newline); + E.print(OS, I); + } + I(Outdent); + if (Comma) + I(Newline); + OS << ']'; + break; + } + } +} + +llvm::raw_ostream &clang::clangd::json::operator<<(raw_ostream &OS, + const Expr &E) { + E.print(OS, [](IndenterAction A) { /*ignore*/ }); + return OS; +} + +void llvm::format_provider::format( + const clang::clangd::json::Expr &E, raw_ostream &OS, StringRef Options) { + if (Options.empty()) { + OS << E; + return; + } + unsigned IndentAmount = 0; + if (Options.getAsInteger(/*Radix=*/10, IndentAmount)) + assert(false && "json::Expr format options should be an integer"); + unsigned IndentLevel = 0; + E.print(OS, [&](IndenterAction A) { + switch (A) { + case Newline: + OS << '\n'; + OS.indent(IndentLevel); + break; + case Space: + OS << ' '; + break; + case Indent: + IndentLevel += IndentAmount; + break; + case Outdent: + IndentLevel -= IndentAmount; + break; + }; + }); +} Index: clangd/JSONRPCDispatcher.h =================================================================== --- clangd/JSONRPCDispatcher.h +++ clangd/JSONRPCDispatcher.h @@ -10,6 +10,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H +#include "JSONExpr.h" #include "Logger.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" @@ -26,11 +27,11 @@ class JSONOutput : public Logger { public: JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs, - llvm::raw_ostream *InputMirror = nullptr) - : Outs(Outs), Logs(Logs), InputMirror(InputMirror) {} + llvm::raw_ostream *InputMirror = nullptr, bool Pretty = false) + : Outs(Outs), Logs(Logs), InputMirror(InputMirror), Pretty(Pretty) {} /// Emit a JSONRPC message. - void writeMessage(const Twine &Message); + void writeMessage(const json::Expr &Result); /// Write to the logging stream. /// No newline is implicitly added. (TODO: we should fix this!) @@ -45,6 +46,7 @@ llvm::raw_ostream &Outs; llvm::raw_ostream &Logs; llvm::raw_ostream *InputMirror; + bool Pretty; std::mutex StreamMutex; }; @@ -52,18 +54,19 @@ /// Context object passed to handlers to allow replies. class RequestContext { public: - RequestContext(JSONOutput &Out, StringRef ID) : Out(Out), ID(ID) {} + RequestContext(JSONOutput &Out, llvm::Optional ID) + : Out(Out), ID(std::move(ID)) {} - /// Sends a successful reply. Result should be well-formed JSON. - void reply(const Twine &Result); + /// Sends a successful reply. + void reply(json::Expr &&Result); /// Sends an error response to the client, and logs it. void replyError(int code, const llvm::StringRef &Message); /// Sends a request to the client. - void call(llvm::StringRef Method, StringRef Params); + void call(llvm::StringRef Method, json::Expr &&Params); private: JSONOutput &Out; - llvm::SmallString<64> ID; // Valid JSON, or empty for a notification. + llvm::Optional ID; }; /// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "JSONRPCDispatcher.h" +#include "JSONExpr.h" #include "ProtocolHandlers.h" #include "Trace.h" #include "llvm/ADT/SmallString.h" @@ -18,17 +19,22 @@ using namespace clang; using namespace clangd; -void JSONOutput::writeMessage(const Twine &Message) { - llvm::SmallString<128> Storage; - StringRef M = Message.toStringRef(Storage); +void JSONOutput::writeMessage(const json::Expr &Message) { + std::string S; + llvm::raw_string_ostream OS(S); + if (Pretty) + OS << llvm::formatv("{0:2}", Message); + else + OS << Message; + OS.flush(); std::lock_guard Guard(StreamMutex); // Log without headers. - Logs << "--> " << M << '\n'; + Logs << "--> " << S << '\n'; Logs.flush(); // Emit message with header. - Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M; + Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; Outs.flush(); } @@ -47,30 +53,38 @@ InputMirror->flush(); } -void RequestContext::reply(const llvm::Twine &Result) { - if (ID.empty()) { +void RequestContext::reply(json::Expr &&Result) { + if (!ID) { Out.log("Attempted to reply to a notification!\n"); return; } - Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID + - R"(,"result":)" + Result + "}"); + Out.writeMessage(json::obj{ + {"jsonrpc", "2.0"}, + {"id", *ID}, + {"result", std::move(Result)}, + }); } void RequestContext::replyError(int code, const llvm::StringRef &Message) { Out.log("Error " + llvm::Twine(code) + ": " + Message + "\n"); - if (!ID.empty()) { - Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID + - R"(,"error":{"code":)" + llvm::Twine(code) + - R"(,"message":")" + llvm::yaml::escape(Message) + - R"("}})"); + if (ID) { + Out.writeMessage(json::obj{ + {"jsonrpc", "2.0"}, + {"id", *ID}, + {"error", json::obj{{"code", code}, {"message", Message}}}, + }); } } -void RequestContext::call(StringRef Method, StringRef Params) { +void RequestContext::call(StringRef Method, json::Expr &&Params) { // FIXME: Generate/Increment IDs for every request so that we can get proper // replies once we need to. - Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":1,"method":")" + - Method + R"(","params":)" + Params + R"(})")); + Out.writeMessage(json::obj{ + {"jsonrpc", "2.0"}, + {"id", 1}, + {"method", Method}, + {"params", std::move(Params)}, + }); } void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) { @@ -80,7 +94,7 @@ static void callHandler(const llvm::StringMap &Handlers, - llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id, + llvm::yaml::ScalarNode *Method, llvm::Optional ID, llvm::yaml::MappingNode *Params, const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) { llvm::SmallString<64> MethodStorage; @@ -88,7 +102,7 @@ auto I = Handlers.find(MethodStr); auto &Handler = I != Handlers.end() ? I->second : UnknownHandler; trace::Span Tracer(MethodStr); - Handler(RequestContext(Out, Id ? Id->getRawValue() : ""), Params); + Handler(RequestContext(Out, std::move(ID)), Params); } bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const { @@ -106,7 +120,7 @@ llvm::yaml::ScalarNode *Version = nullptr; llvm::yaml::ScalarNode *Method = nullptr; llvm::yaml::MappingNode *Params = nullptr; - llvm::yaml::ScalarNode *Id = nullptr; + llvm::Optional ID; for (auto &NextKeyValue : *Object) { auto *KeyString = dyn_cast_or_null(NextKeyValue.getKey()); @@ -127,7 +141,18 @@ } else if (KeyValue == "method") { Method = dyn_cast(Value); } else if (KeyValue == "id") { - Id = dyn_cast(Value); + // ID may be either a string or a number. + if (auto *IdNode = dyn_cast(Value)) { + llvm::SmallString<32> S; + llvm::StringRef V = IdNode->getValue(S); + if (IdNode->getRawValue().startswith("\"")) { + ID.emplace(V.str()); + } else { + double D; + if (!V.getAsDouble(D)) + ID.emplace(D); + } + } } else if (KeyValue == "params") { if (!Method) return false; @@ -136,7 +161,7 @@ // because it will break clients that put the id after params. A possible // fix would be to split the parsing and execution phases. Params = dyn_cast(Value); - callHandler(Handlers, Method, Id, Params, UnknownHandler, Out); + callHandler(Handlers, Method, std::move(ID), Params, UnknownHandler, Out); return true; } else { return false; @@ -147,7 +172,7 @@ // leftovers. if (!Method) return false; - callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out); + callHandler(Handlers, Method, std::move(ID), nullptr, UnknownHandler, Out); return true; } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -21,6 +21,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H +#include "JSONExpr.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/YAMLParser.h" #include @@ -39,7 +40,7 @@ static URI fromFile(llvm::StringRef file); static URI parse(llvm::yaml::ScalarNode *Param); - static std::string unparse(const URI &U); + static json::Expr unparse(const URI &U); friend bool operator==(const URI &LHS, const URI &RHS) { return LHS.uri == RHS.uri; @@ -80,7 +81,7 @@ static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); - static std::string unparse(const Position &P); + static json::Expr unparse(const Position &P); }; struct Range { @@ -99,7 +100,7 @@ static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); - static std::string unparse(const Range &P); + static json::Expr unparse(const Range &P); }; struct Location { @@ -119,7 +120,7 @@ return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range); } - static std::string unparse(const Location &P); + static json::Expr unparse(const Location &P); }; struct Metadata { @@ -140,8 +141,7 @@ static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); - static std::string unparse(const TextEdit &P); - static std::string unparse(const std::vector &TextEdits); + static json::Expr unparse(const TextEdit &P); }; struct TextDocumentItem { @@ -283,7 +283,7 @@ static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); - static std::string unparse(const FormattingOptions &P); + static json::Expr unparse(const FormattingOptions &P); }; struct DocumentRangeFormattingParams { @@ -392,7 +392,7 @@ static llvm::Optional parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); - static std::string unparse(const WorkspaceEdit &WE); + static json::Expr unparse(const WorkspaceEdit &WE); }; /// Exact commands are not specified in the protocol so we define the @@ -420,7 +420,7 @@ struct ApplyWorkspaceEditParams { WorkspaceEdit edit; - static std::string unparse(const ApplyWorkspaceEditParams &Params); + static json::Expr unparse(const ApplyWorkspaceEditParams &Params); }; struct TextDocumentPositionParams { @@ -527,7 +527,7 @@ // // data?: any - A data entry field that is preserved on a completion item // between a completion and a completion resolve request. - static std::string unparse(const CompletionItem &P); + static json::Expr unparse(const CompletionItem &P); }; /// A single parameter of a particular signature. @@ -539,7 +539,7 @@ /// The documentation of this parameter. Optional. std::string documentation; - static std::string unparse(const ParameterInformation &); + static json::Expr unparse(const ParameterInformation &); }; /// Represents the signature of something callable. @@ -554,7 +554,7 @@ /// The parameters of this signature. std::vector parameters; - static std::string unparse(const SignatureInformation &); + static json::Expr unparse(const SignatureInformation &); }; /// Represents the signature of a callable. @@ -569,7 +569,7 @@ /// The active parameter of the active signature. int activeParameter = 0; - static std::string unparse(const SignatureHelp &); + static json::Expr unparse(const SignatureHelp &); }; } // namespace clangd Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -63,7 +63,7 @@ return URI::fromUri(Param->getValue(Storage)); } -std::string URI::unparse(const URI &U) { return "\"" + U.uri + "\""; } +json::Expr URI::unparse(const URI &U) { return U.uri; } llvm::Optional TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params, @@ -125,11 +125,11 @@ return Result; } -std::string Position::unparse(const Position &P) { - std::string Result; - llvm::raw_string_ostream(Result) - << llvm::format(R"({"line": %d, "character": %d})", P.line, P.character); - return Result; +json::Expr Position::unparse(const Position &P) { + return json::obj{ + {"line", P.line}, + {"character", P.character}, + }; } llvm::Optional Range::parse(llvm::yaml::MappingNode *Params, @@ -165,20 +165,18 @@ return Result; } -std::string Range::unparse(const Range &P) { - std::string Result; - llvm::raw_string_ostream(Result) << llvm::format( - R"({"start": %s, "end": %s})", Position::unparse(P.start).c_str(), - Position::unparse(P.end).c_str()); - return Result; +json::Expr Range::unparse(const Range &P) { + return json::obj{ + {"start", P.start}, + {"end", P.end}, + }; } -std::string Location::unparse(const Location &P) { - std::string Result; - llvm::raw_string_ostream(Result) << llvm::format( - R"({"uri": %s, "range": %s})", URI::unparse(P.uri).c_str(), - Range::unparse(P.range).c_str()); - return Result; +json::Expr Location::unparse(const Location &P) { + return json::obj{ + {"uri", P.uri}, + {"range", P.range}, + }; } llvm::Optional @@ -279,25 +277,11 @@ return Result; } -std::string TextEdit::unparse(const TextEdit &P) { - std::string Result; - llvm::raw_string_ostream(Result) << llvm::format( - R"({"range": %s, "newText": "%s"})", Range::unparse(P.range).c_str(), - llvm::yaml::escape(P.newText).c_str()); - return Result; -} - -std::string TextEdit::unparse(const std::vector &TextEdits) { - // Fuse all edits into one big JSON array. - std::string Edits; - for (auto &TE : TextEdits) { - Edits += TextEdit::unparse(TE); - Edits += ','; - } - if (!Edits.empty()) - Edits.pop_back(); - - return "[" + Edits + "]"; +json::Expr TextEdit::unparse(const TextEdit &P) { + return json::obj{ + {"range", P.range}, + {"newText", P.newText}, + }; } namespace { @@ -611,11 +595,11 @@ return Result; } -std::string FormattingOptions::unparse(const FormattingOptions &P) { - std::string Result; - llvm::raw_string_ostream(Result) << llvm::format( - R"({"tabSize": %d, "insertSpaces": %d})", P.tabSize, P.insertSpaces); - return Result; +json::Expr FormattingOptions::unparse(const FormattingOptions &P) { + return json::obj{ + {"tabSize", P.tabSize}, + {"insertSpaces", P.insertSpaces}, + }; } llvm::Optional @@ -982,28 +966,18 @@ return Result; } -std::string WorkspaceEdit::unparse(const WorkspaceEdit &WE) { - std::string Changes; - for (auto &Change : *WE.changes) { - Changes += llvm::formatv(R"("{0}": {1})", Change.first, - TextEdit::unparse(Change.second)); - Changes += ','; - } - if (!Changes.empty()) - Changes.pop_back(); - - std::string Result; - llvm::raw_string_ostream(Result) - << llvm::format(R"({"changes": {%s}})", Changes.c_str()); - return Result; +json::Expr WorkspaceEdit::unparse(const WorkspaceEdit &WE) { + if (!WE.changes) + return json::obj{}; + json::obj FileChanges; + for (auto &Change : *WE.changes) + FileChanges[Change.first] = json::ary(Change.second); + return json::obj{{"changes", std::move(FileChanges)}}; } -std::string +json::Expr ApplyWorkspaceEditParams::unparse(const ApplyWorkspaceEditParams &Params) { - std::string Result; - llvm::raw_string_ostream(Result) << llvm::format( - R"({"edit": %s})", WorkspaceEdit::unparse(Params.edit).c_str()); - return Result; + return json::obj{{"edit", Params.edit}}; } llvm::Optional @@ -1040,96 +1014,57 @@ return Result; } -std::string CompletionItem::unparse(const CompletionItem &CI) { - std::string Result = "{"; - llvm::raw_string_ostream Os(Result); +json::Expr CompletionItem::unparse(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); - Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)"; + json::obj Result{{"label", CI.label}}; if (CI.kind != CompletionItemKind::Missing) - Os << R"("kind":)" << static_cast(CI.kind) << R"(,)"; + Result["kind"] = static_cast(CI.kind); if (!CI.detail.empty()) - Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)"; + Result["detail"] = CI.detail; if (!CI.documentation.empty()) - Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation) - << R"(",)"; + Result["documentation"] = CI.documentation; if (!CI.sortText.empty()) - Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)"; + Result["sortText"] = CI.sortText; if (!CI.filterText.empty()) - Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)"; + Result["filterText"] = CI.filterText; if (!CI.insertText.empty()) - Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)"; - if (CI.insertTextFormat != InsertTextFormat::Missing) { - Os << R"("insertTextFormat":)" << static_cast(CI.insertTextFormat) - << R"(,)"; - } + Result["insertText"] = CI.insertText; + if (CI.insertTextFormat != InsertTextFormat::Missing) + Result["insertTextFormat"] = static_cast(CI.insertTextFormat); if (CI.textEdit) - Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ','; - if (!CI.additionalTextEdits.empty()) { - Os << R"("additionalTextEdits":[)"; - for (const auto &Edit : CI.additionalTextEdits) - Os << TextEdit::unparse(Edit) << ","; - Os.flush(); - // The list additionalTextEdits is guaranteed nonempty at this point. - // Replace the trailing comma with right brace. - Result.back() = ']'; - } - Os.flush(); - // Label is required, so Result is guaranteed to have a trailing comma. - Result.back() = '}'; - return Result; + Result["textEdit"] = *CI.textEdit; + if (!CI.additionalTextEdits.empty()) + Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits); + return std::move(Result); } -std::string ParameterInformation::unparse(const ParameterInformation &PI) { - std::string Result = "{"; - llvm::raw_string_ostream Os(Result); +json::Expr ParameterInformation::unparse(const ParameterInformation &PI) { assert(!PI.label.empty() && "parameter information label is required"); - Os << R"("label":")" << llvm::yaml::escape(PI.label) << '\"'; + json::obj Result{{"label", PI.label}}; if (!PI.documentation.empty()) - Os << R"(,"documentation":")" << llvm::yaml::escape(PI.documentation) - << '\"'; - Os << '}'; - Os.flush(); - return Result; + Result["documentation"] = PI.documentation; + return std::move(Result); } -std::string SignatureInformation::unparse(const SignatureInformation &SI) { - std::string Result = "{"; - llvm::raw_string_ostream Os(Result); +json::Expr SignatureInformation::unparse(const SignatureInformation &SI) { assert(!SI.label.empty() && "signature information label is required"); - Os << R"("label":")" << llvm::yaml::escape(SI.label) << '\"'; + json::obj Result{ + {"label", SI.label}, + {"parameters", json::ary(SI.parameters)}, + }; if (!SI.documentation.empty()) - Os << R"(,"documentation":")" << llvm::yaml::escape(SI.documentation) - << '\"'; - Os << R"(,"parameters":[)"; - for (const auto &Parameter : SI.parameters) { - Os << ParameterInformation::unparse(Parameter) << ','; - } - Os.flush(); - if (SI.parameters.empty()) - Result.push_back(']'); - else - Result.back() = ']'; // Replace the last `,` with an `]`. - Result.push_back('}'); - return Result; + Result["documentation"] = SI.documentation; + return std::move(Result); } -std::string SignatureHelp::unparse(const SignatureHelp &SH) { - std::string Result = "{"; - llvm::raw_string_ostream Os(Result); +json::Expr SignatureHelp::unparse(const SignatureHelp &SH) { assert(SH.activeSignature >= 0 && "Unexpected negative value for number of active signatures."); assert(SH.activeParameter >= 0 && "Unexpected negative value for active parameter index"); - Os << R"("activeSignature":)" << SH.activeSignature - << R"(,"activeParameter":)" << SH.activeParameter << R"(,"signatures":[)"; - for (const auto &Signature : SH.signatures) { - Os << SignatureInformation::unparse(Signature) << ','; - } - Os.flush(); - if (SH.signatures.empty()) - Result.push_back(']'); - else - Result.back() = ']'; // Replace the last `,` with an `]`. - Result.push_back('}'); - return Result; + return json::obj{ + {"activeSignature", SH.activeSignature}, + {"activeParameter", SH.activeParameter}, + {"signatures", json::ary(SH.signatures)}, + }; } Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -41,6 +41,10 @@ "Present snippet completions instead of plaintext completions"), llvm::cl::init(false)); +static llvm::cl::opt + PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), + llvm::cl::init(false)); + static llvm::cl::opt RunSynchronously( "run-synchronously", llvm::cl::desc("Parse on main thread. If set, -j is ignored"), @@ -104,7 +108,8 @@ llvm::raw_ostream &Outs = llvm::outs(); llvm::raw_ostream &Logs = llvm::errs(); JSONOutput Out(Outs, Logs, - InputMirrorStream ? InputMirrorStream.getPointer() : nullptr); + InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, + PrettyPrint); // If --compile-commands-dir arg was invoked, check value and override default // path. Index: test/clangd/authority-less-uri.test =================================================================== --- test/clangd/authority-less-uri.test +++ test/clangd/authority-less-uri.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # # Test authority-less URI @@ -15,22 +15,34 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}} # Test authority-less URI # -# CHECK: {"jsonrpc":"2.0","id":1,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK: ]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK: "filterText": "fake", +# CHECK-NEXT: "insertText": "fake", +# CHECK-NEXT: "insertTextFormat": 1, +# CHECK-NEXT: "kind": 7, +# CHECK-NEXT: "label": "fake::", +# CHECK-NEXT: "sortText": "000075fake" +# CHECK: ] Content-Length: 172 {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"uri":"file:///main.cpp","position":{"line":3,"character":5}}} # Test params parsing in the presence of a 1.x-compatible client (inlined "uri") # -# CHECK: {"jsonrpc":"2.0","id":2,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK: ]} +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK: "filterText": "fake", +# CHECK-NEXT: "insertText": "fake", +# CHECK-NEXT: "insertTextFormat": 1, +# CHECK-NEXT: "kind": 7, +# CHECK-NEXT: "label": "fake::", +# CHECK-NEXT: "sortText": "000075fake" +# CHECK: ] Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":3,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion-items-kinds.test =================================================================== --- test/clangd/completion-items-kinds.test +++ test/clangd/completion-items-kinds.test @@ -11,27 +11,26 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}} Content-Length: 58 -# CHECK: {"jsonrpc":"2.0","id":1,"result":[ +# CHECK: {"id":1,"jsonrpc":"2.0","result":[ # # Keyword -# CHECK-DAG: {"label":"int","kind":14,"sortText":"000050int","filterText":"int","insertText":"int","insertTextFormat":1} +# CHECK-DAG: {"filterText":"int","insertText":"int","insertTextFormat":1,"kind":14,"label":"int","sortText":"000050int"} # # Code pattern -# CHECK-DAG: {"label":"static_cast(expression)","kind":15,"sortText":"000040static_cast","filterText":"static_cast","insertText":"static_cast<${1:type}>(${2:expression})","insertTextFormat":2} +# CHECK-DAG: {"filterText":"static_cast","insertText":"static_cast<${1:type}>(${2:expression})","insertTextFormat":2,"kind":15,"label":"static_cast(expression)","sortText":"000040static_cast"} # # Struct -# CHECK-DAG: {"label":"Struct","kind":7,"sortText":"000050Struct","filterText":"Struct","insertText":"Struct","insertTextFormat":1} +# CHECK-DAG: {"filterText":"Struct","insertText":"Struct","insertTextFormat":1,"kind":7,"label":"Struct","sortText":"000050Struct"} # # Macro -# CHECK-DAG: {"label":"MACRO","kind":1,"sortText":"000070MACRO","filterText":"MACRO","insertText":"MACRO","insertTextFormat":1} +# CHECK-DAG: {"filterText":"MACRO","insertText":"MACRO","insertTextFormat":1,"kind":1,"label":"MACRO","sortText":"000070MACRO"} # # Variable -# CHECK-DAG: {"label":"variable","kind":6,"detail":"int","sortText":"000012variable","filterText":"variable","insertText":"variable","insertTextFormat":1} +# CHECK-DAG: {"detail":"int","filterText":"variable","insertText":"variable","insertTextFormat":1,"kind":6,"label":"variable","sortText":"000012variable"} # # Function -# CHECK-DAG: {"label":"function()","kind":3,"detail":"int","sortText":"000012function","filterText":"function","insertText":"function()","insertTextFormat":1} +# CHECK-DAG: {"detail":"int","filterText":"function","insertText":"function()","insertTextFormat":1,"kind":3,"label":"function()","sortText":"000012function"} # -# -# CHECK: ]} +# CHECK-SAME: ]} {"jsonrpc":"2.0","id":3,"method":"shutdown","params":null} Index: test/clangd/completion-priorities.test =================================================================== --- test/clangd/completion-priorities.test +++ test/clangd/completion-priorities.test @@ -16,25 +16,24 @@ # The order of results returned by codeComplete seems to be # nondeterministic, so we check regardless of order. # -# CHECK: {"jsonrpc":"2.0","id":2,"result":[ -# CHECK-DAG: {"label":"pub()","kind":2,"detail":"void","sortText":"000034pub","filterText":"pub","insertText":"pub","insertTextFormat":1} -# CHECK-DAG: {"label":"prot()","kind":2,"detail":"void","sortText":"000034prot","filterText":"prot","insertText":"prot","insertTextFormat":1} -# CHECK-DAG: {"label":"priv()","kind":2,"detail":"void","sortText":"000034priv","filterText":"priv","insertText":"priv","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":2,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"void","filterText":"pub","insertText":"pub","insertTextFormat":1,"kind":2,"label":"pub()","sortText":"000034pub"} +# CHECK-DAG: {"detail":"void","filterText":"prot","insertText":"prot","insertTextFormat":1,"kind":2,"label":"prot()","sortText":"000034prot"} +# CHECK-DAG: {"detail":"void","filterText":"priv","insertText":"priv","insertTextFormat":1,"kind":2,"label":"priv()","sortText":"000034priv"} +# CHECK-SAME: ]} Content-Length: 151 {"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":4}}} -# CHECK: {"jsonrpc":"2.0","id":3,"result":[ -# CHECK-DAG: {"label":"pub()","kind":2,"detail":"void","sortText":"000034pub","filterText":"pub","insertText":"pub","insertTextFormat":1} -# CHECK-DAG: {"label":"prot()","kind":2,"detail":"void","sortText":"200034prot","filterText":"prot","insertText":"prot","insertTextFormat":1} -# CHECK-DAG: {"label":"priv()","kind":2,"detail":"void","sortText":"200034priv","filterText":"priv","insertText":"priv","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":3,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"void","filterText":"pub","insertText":"pub","insertTextFormat":1,"kind":2,"label":"pub()","sortText":"000034pub"} +# CHECK-DAG: {"detail":"void","filterText":"prot","insertText":"prot","insertTextFormat":1,"kind":2,"label":"prot()","sortText":"200034prot"} +# CHECK-DAG: {"detail":"void","filterText":"priv","insertText":"priv","insertTextFormat":1,"kind":2,"label":"priv()","sortText":"200034priv"} +# CHECK-SAME: ]} Content-Length: 58 {"jsonrpc":"2.0","id":4,"method":"shutdown","params":null} -# CHECK: {"jsonrpc":"2.0","id":4,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion-qualifiers.test =================================================================== --- test/clangd/completion-qualifiers.test +++ test/clangd/completion-qualifiers.test @@ -8,15 +8,14 @@ Content-Length: 151 {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":11,"character":8}}} -# CHECK: {"jsonrpc":"2.0","id":2,"result":[ -# CHECK-DAG: {"label":"foo() const","kind":2,"detail":"int","sortText":"200035foo","filterText":"foo","insertText":"foo","insertTextFormat":1} -# CHECK-DAG: {"label":"bar() const","kind":2,"detail":"int","sortText":"000037bar","filterText":"bar","insertText":"bar","insertTextFormat":1} -# CHECK-DAG: {"label":"Foo::foo() const","kind":2,"detail":"int","sortText":"000037foo","filterText":"foo","insertText":"foo","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":2,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int","filterText":"foo","insertText":"foo","insertTextFormat":1,"kind":2,"label":"foo() const","sortText":"200035foo"} +# CHECK-DAG: {"detail":"int","filterText":"bar","insertText":"bar","insertTextFormat":1,"kind":2,"label":"bar() const","sortText":"000037bar"} +# CHECK-DAG: {"detail":"int","filterText":"foo","insertText":"foo","insertTextFormat":1,"kind":2,"label":"Foo::foo() const","sortText":"000037foo"} +# CHECK-SAME: ]} Content-Length: 44 {"jsonrpc":"2.0","id":4,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":4,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion-snippet.test =================================================================== --- test/clangd/completion-snippet.test +++ test/clangd/completion-snippet.test @@ -15,27 +15,27 @@ # The order of results returned by codeComplete seems to be # nondeterministic, so we check regardless of order. # -# CHECK: {"jsonrpc":"2.0","id":1,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1} -# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1} -# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2} -# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1} -# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2} -# CHECK: ]} +# CHECK: {"id":1,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int","filterText":"a","insertText":"a","insertTextFormat":1,"kind":5,"label":"a","sortText":"000035a"} +# CHECK-DAG: {"detail":"int","filterText":"bb","insertText":"bb","insertTextFormat":1,"kind":5,"label":"bb","sortText":"000035bb"} +# CHECK-DAG: {"detail":"int","filterText":"ccc","insertText":"ccc","insertTextFormat":1,"kind":5,"label":"ccc","sortText":"000035ccc"} +# CHECK-DAG: {"detail":"fake &","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2,"kind":2,"label":"operator=(const fake &)","sortText":"000079operator="} +# CHECK-DAG: {"detail":"void","filterText":"~fake","insertText":"~fake()","insertTextFormat":1,"kind":4,"label":"~fake()","sortText":"000079~fake"} +# CHECK-DAG: {"detail":"int","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2,"kind":2,"label":"f(int i, const float f) const","sortText":"000035f"} +# CHECK-SAME: ]} Content-Length: 148 {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} # Repeat the completion request, expect the same results. # -# CHECK: {"jsonrpc":"2.0","id":2,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1} -# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1} -# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2} -# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1} -# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2} -# CHECK: ]} +# CHECK: {"id":2,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int","filterText":"a","insertText":"a","insertTextFormat":1,"kind":5,"label":"a","sortText":"000035a"} +# CHECK-DAG: {"detail":"int","filterText":"bb","insertText":"bb","insertTextFormat":1,"kind":5,"label":"bb","sortText":"000035bb"} +# CHECK-DAG: {"detail":"int","filterText":"ccc","insertText":"ccc","insertTextFormat":1,"kind":5,"label":"ccc","sortText":"000035ccc"} +# CHECK-DAG: {"detail":"fake &","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2,"kind":2,"label":"operator=(const fake &)","sortText":"000079operator="} +# CHECK-DAG: {"detail":"void","filterText":"~fake","insertText":"~fake()","insertTextFormat":1,"kind":4,"label":"~fake()","sortText":"000079~fake"} +# CHECK-DAG: {"detail":"int","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2,"kind":2,"label":"f(int i, const float f) const","sortText":"000035f"} +# CHECK-SAME: ]} # Update the source file and check for completions again. Content-Length: 226 @@ -44,15 +44,12 @@ Content-Length: 148 {"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} -# Repeat the completion request, expect the same results. -# -# CHECK: {"jsonrpc":"2.0","id":3,"result":[ -# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"000034func","filterText":"func","insertText":"func()","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":3,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int (*)(int, int)","filterText":"func","insertText":"func()","insertTextFormat":1,"kind":2,"label":"func()","sortText":"000034func"} +# CHECK-SAME: ]} Content-Length: 44 {"jsonrpc":"2.0","id":4,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":4,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/completion.test =================================================================== --- test/clangd/completion.test +++ test/clangd/completion.test @@ -15,27 +15,27 @@ # The order of results returned by codeComplete seems to be # nondeterministic, so we check regardless of order. # -# CHECK: {"jsonrpc":"2.0","id":1,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1} -# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1} -# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1} -# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1} -# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":1,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int","filterText":"a","insertText":"a","insertTextFormat":1,"kind":5,"label":"a","sortText":"000035a"} +# CHECK-DAG: {"detail":"int","filterText":"bb","insertText":"bb","insertTextFormat":1,"kind":5,"label":"bb","sortText":"000035bb"} +# CHECK-DAG: {"detail":"int","filterText":"ccc","insertText":"ccc","insertTextFormat":1,"kind":5,"label":"ccc","sortText":"000035ccc"} +# CHECK-DAG: {"detail":"fake &","filterText":"operator=","insertText":"operator=","insertTextFormat":1,"kind":2,"label":"operator=(const fake &)","sortText":"000079operator="} +# CHECK-DAG: {"detail":"void","filterText":"~fake","insertText":"~fake","insertTextFormat":1,"kind":4,"label":"~fake()","sortText":"000079~fake"} +# CHECK-DAG: {"detail":"int","filterText":"f","insertText":"f","insertTextFormat":1,"kind":2,"label":"f(int i, const float f) const","sortText":"000035f"} +# CHECK-SAME: ]} Content-Length: 148 {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} # Repeat the completion request, expect the same results. # -# CHECK: {"jsonrpc":"2.0","id":2,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1} -# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1} -# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1} -# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1} -# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":2,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int","filterText":"a","insertText":"a","insertTextFormat":1,"kind":5,"label":"a","sortText":"000035a"} +# CHECK-DAG: {"detail":"int","filterText":"bb","insertText":"bb","insertTextFormat":1,"kind":5,"label":"bb","sortText":"000035bb"} +# CHECK-DAG: {"detail":"int","filterText":"ccc","insertText":"ccc","insertTextFormat":1,"kind":5,"label":"ccc","sortText":"000035ccc"} +# CHECK-DAG: {"detail":"fake &","filterText":"operator=","insertText":"operator=","insertTextFormat":1,"kind":2,"label":"operator=(const fake &)","sortText":"000079operator="} +# CHECK-DAG: {"detail":"void","filterText":"~fake","insertText":"~fake","insertTextFormat":1,"kind":4,"label":"~fake()","sortText":"000079~fake"} +# CHECK-DAG: {"detail":"int","filterText":"f","insertText":"f","insertTextFormat":1,"kind":2,"label":"f(int i, const float f) const","sortText":"000035f"} +# CHECK-SAME: ]} # Update the source file and check for completions again. Content-Length: 226 @@ -46,13 +46,12 @@ {"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}} # Repeat the completion request, expect the same results. # -# CHECK: {"jsonrpc":"2.0","id":3,"result":[ -# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"000034func","filterText":"func","insertText":"func","insertTextFormat":1} -# CHECK: ]} +# CHECK: {"id":3,"jsonrpc":"2.0","result":[ +# CHECK-DAG: {"detail":"int (*)(int, int)","filterText":"func","insertText":"func","insertTextFormat":1,"kind":2,"label":"func()","sortText":"000034func"} +# CHECK-SAME: ]} Content-Length: 44 {"jsonrpc":"2.0","id":4,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":4,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/definitions.test =================================================================== --- test/clangd/definitions.test +++ test/clangd/definitions.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -13,14 +13,44 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":0}}} # Go to local variable -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 148 {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":1}}} # Go to local variable, end of token -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 214 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo bar = { x : 1 };\n}\n"}]}} @@ -29,8 +59,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":14}}} # Go to field, GNU old-style field designator -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 215 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":3},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo baz = { .x = 2 };\n}\n"}]}} @@ -39,8 +84,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":15}}} # Go to field, field designator -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 5, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 187 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":4},"contentChanges":[{"text":"int main() {\n main();\n return 0;\n}"}]}} @@ -49,8 +109,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}} # Go to function declaration, function call -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 208 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"struct Foo {\n};\nint main() {\n Foo bar;\n return 0;\n}\n"}]}} @@ -59,8 +134,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":3}}} # Go to struct declaration, new struct instance -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 1}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 231 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"namespace n1 {\nstruct Foo {\n};\n}\nint main() {\n n1::Foo bar;\n return 0;\n}\n"}]}} @@ -69,8 +159,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":4}}} # Go to struct declaration, new struct instance, qualified name -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 215 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":6},"contentChanges":[{"text":"struct Foo {\n int x;\n};\nint main() {\n Foo bar;\n bar.x;\n}\n"}]}} @@ -79,8 +184,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}} # Go to field declaration, field reference -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 7}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 2, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 220 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n void x();\n};\nint main() {\n Foo bar;\n bar.x();\n}\n"}]}} @@ -89,8 +209,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}} # Go to method declaration, method call -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 10}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 2, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 240 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n};\ntypedef Foo TypedefFoo;\nint main() {\n TypedefFoo bar;\n return 0;\n}\n"}]}} @@ -99,8 +234,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":10}}} # Go to typedef -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 22}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 22, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 254 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"template \nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}} @@ -109,8 +259,9 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":13}}} # Go to template type parameter. Fails until clangIndex is modified to handle those. -# no-CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 10}, "end": {"line": 0, "character": 34}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [] Content-Length: 256 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\nstatic void bar() {}\n};\n}\nint main() {\n ns::Foo::bar();\n return 0;\n}\n"}]}} @@ -119,8 +270,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":6,"character":4}}} # Go to namespace, static method call -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 4, "character": 1}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 4 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 265 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\n int field;\n Foo(int param) : field(param) {}\n};\n}\nint main() {\n return 0;\n}\n"}]}} @@ -128,9 +294,24 @@ Content-Length: 149 {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":21}}} -# Go to field, member initializer -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 2}, "end": {"line": 2, "character": 11}}}]} - +# Go to field, member initializer +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 11, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 2, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 204 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define MY_MACRO 0\nint main() {\n return MY_MACRO;\n}\n"}]}} @@ -139,8 +320,23 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":9}}} # Go to macro. -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///{{([A-Za-z]:/)?}}main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 18}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 18, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 217 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define FOO 1\nint a = FOO;\n#define FOO 2\nint b = FOO;\n#undef FOO\n"}]}} @@ -149,29 +345,77 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}} # Go to macro, re-defined later -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///{{([A-Za-z]:/)?}}main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 13}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 148 {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":8}}} # Go to macro, undefined later -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 148 {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}} # Go to macro, being undefined -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]} - +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 156 {"jsonrpc":"2.0","id":2,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///doesnotexist.cpp"},"position":{"line":4,"character":7}}} -# CHECK: {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"findDefinitions called on non-added file"}} - +# CHECK: "error": { +# CHECK-NEXT: "code": -32602, +# CHECK-NEXT: "message": "findDefinitions called on non-added file" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0" Content-Length: 48 {"jsonrpc":"2.0","id":10000,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":10000,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/diagnostics-preamble.test =================================================================== --- test/clangd/diagnostics-preamble.test +++ test/clangd/diagnostics-preamble.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -8,12 +8,17 @@ Content-Length: 206 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#ifndef FOO\n#define FOO\nint a;\n#else\nint a = b;#endif\n\n\n"}}} -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///main.cpp","diagnostics":[]}} - +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [], +# CHECK-NEXT: "uri": "file:///main.cpp" +# CHECK-NEXT: } Content-Length: 58 {"jsonrpc":"2.0","id":2,"method":"shutdown","params":null} -# CHECK: {"jsonrpc":"2.0","id":2,"result":null} +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/diagnostics.test =================================================================== --- test/clangd/diagnostics.test +++ test/clangd/diagnostics.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -8,14 +8,46 @@ Content-Length: 152 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} -# -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":2,"message":"return type of 'main' is not 'int'"},{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":3,"message":"change return type to 'int'"}]}} -# -# +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "message": "return type of 'main' is not 'int'", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "change return type to 'int'", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 1, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "uri": "file:///foo.c" +# CHECK-NEXT: } Content-Length: 44 {"jsonrpc":"2.0","id":5,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":5,"result":null} +# CHECK: "id": 5, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/did-change-watch-files.test =================================================================== --- test/clangd/did-change-watch-files.test +++ test/clangd/did-change-watch-files.test @@ -5,18 +5,7 @@ Content-Length: 143 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 466 -# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ -# CHECK: "textDocumentSync": 1, -# CHECK: "documentFormattingProvider": true, -# CHECK: "documentRangeFormattingProvider": true, -# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, -# CHECK: "codeActionProvider": true, -# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, -# CHECK: "definitionProvider": true -# CHECK: }}} -# -#Normal case +# Normal case. Content-Length: 217 {"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file.cpp","type":1},{"uri":"file:///path/to/file2.cpp","type":2},{"uri":"file:///path/to/file3.cpp","type":3}]}} Index: test/clangd/execute-command.test =================================================================== --- test/clangd/execute-command.test +++ test/clangd/execute-command.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -8,9 +8,54 @@ Content-Length: 180 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} -# -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}} -# +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "uri": "file:///foo.c" +# CHECK-NEXT: } Content-Length: 72 {"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{}} Index: test/clangd/extra-flags.test =================================================================== --- test/clangd/extra-flags.test +++ test/clangd/extra-flags.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -8,17 +8,83 @@ Content-Length: 205 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"},"metadata":{"extraFlags":["-Wall"]}}} -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 28}, "end": {"line": 0, "character": 28}},"severity":2,"message":"variable 'i' is uninitialized when used here"},{"range":{"start": {"line": 0, "character": 19}, "end": {"line": 0, "character": 19}},"severity":3,"message":"initialize the variable 'i' to silence this warning"}]}} -# +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "uri": "file:///foo.c" +# CHECK-NEXT: } Content-Length: 175 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":2},"contentChanges":[{"text":"int main() { int i; return i; }"}]}} -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 28}, "end": {"line": 0, "character": 28}},"severity":2,"message":"variable 'i' is uninitialized when used here"},{"range":{"start": {"line": 0, "character": 19}, "end": {"line": 0, "character": 19}},"severity":3,"message":"initialize the variable 'i' to silence this warning"}]}} -# +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 28, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "uri": "file:///foo.c" +# CHECK-NEXT: } Content-Length: 44 {"jsonrpc":"2.0","id":5,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":5,"result":null} +# CHECK: "id": 5, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/fixits.test =================================================================== --- test/clangd/fixits.test +++ test/clangd/fixits.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -8,30 +8,242 @@ Content-Length: 180 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} -# -# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}} -# +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "severity": 3 +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "uri": "file:///foo.c" +# CHECK-NEXT: } Content-Length: 746 {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} -# -# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]}}]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]}}]}]} -# +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "arguments": [ +# CHECK-NEXT: { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///foo.c": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "(", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": ")", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "command": "clangd.applyFix", +# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning" +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "arguments": [ +# CHECK-NEXT: { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///foo.c": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "==", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 34, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "command": "clangd.applyFix", +# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 771 -{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} +{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} # Make sure unused "code" and "source" fields ignored gracefully -# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]}}]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": [{"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]}}]}]} -# +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "arguments": [ +# CHECK-NEXT: { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///foo.c": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "(", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": ")", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "command": "clangd.applyFix", +# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning" +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "arguments": [ +# CHECK-NEXT: { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///foo.c": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "==", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 35, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 34, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "command": "clangd.applyFix", +# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison" +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 329 -{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}} -# CHECK: {"jsonrpc":"2.0","id":3,"result":"Fix applied."} -# CHECK: {"jsonrpc":"2.0","id":1,"method":"workspace/applyEdit","params":{"edit": {"changes": {"file:///foo.c": [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]}}}} +{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}} +# CHECK: "id": 4, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": "Fix applied." +# +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "method": "workspace/applyEdit", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "edit": { +# CHECK-NEXT: "changes": { +# CHECK-NEXT: "file:///foo.c": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "(", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 32, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": ")", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 37, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } Content-Length: 44 -{"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":3,"result":null} +{"jsonrpc":"2.0","id":4,"method":"shutdown"} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/formatting.test =================================================================== --- test/clangd/formatting.test +++ test/clangd/formatting.test @@ -1,30 +1,71 @@ -# RUN: clangd < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %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"}} -# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ -# CHECK: "textDocumentSync": 1, -# CHECK: "documentFormattingProvider": true, -# CHECK: "documentRangeFormattingProvider": true, -# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, -# CHECK: "codeActionProvider": true, -# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, -# CHECK: "definitionProvider": true -# CHECK: }}} -# Content-Length: 193 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int foo ( int x ) {\n x = x+1;\n return x;\n }"}}} -# -# Content-Length: 233 {"jsonrpc":"2.0","id":1,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 0, "character": 19}, "end": {"line": 1, "character": 4}}, "newText": "\n "},{"range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 9}}, "newText": " "},{"range": {"start": {"line": 1, "character": 10}, "end": {"line": 1, "character": 10}}, "newText": " "},{"range": {"start": {"line": 1, "character": 12}, "end": {"line": 2, "character": 4}}, "newText": "\n "}]} -# -# +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "\n ", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 4, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 19, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": " ", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": " ", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "\n ", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 4, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 12, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 197 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n }"}]}} @@ -33,14 +74,68 @@ Content-Length: 233 {"jsonrpc":"2.0","id":2,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":2},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: {"jsonrpc":"2.0","id":2,"result":[]} -# +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [] Content-Length: 153 {"jsonrpc":"2.0","id":3,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: {"jsonrpc":"2.0","id":3,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""},{"range": {"start": {"line": 2, "character": 11}, "end": {"line": 3, "character": 4}}, "newText": "\n"}]} -# -# +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 16, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 15, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "\n", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 4, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 11, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 190 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n x = x + 1;\n return x;\n}"}]}} @@ -49,8 +144,9 @@ Content-Length: 153 {"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: {"jsonrpc":"2.0","id":4,"result":[]} -# +# CHECK: "id": 4, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [] Content-Length: 193 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n}"}]}} @@ -59,13 +155,53 @@ Content-Length: 204 {"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: {"jsonrpc":"2.0","id":5,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""}]} -# +# CHECK: "id": 5, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 8, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 10, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 9, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: { +# CHECK-NEXT: "newText": "", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 16, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 15, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: } +# CHECK-NEXT: ] Content-Length: 44 {"jsonrpc":"2.0","id":6,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":6,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -1,27 +1,50 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # # Test with invalid initialize request parameters Content-Length: 142 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 606 -# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ -# CHECK: "textDocumentSync": 1, -# CHECK: "documentFormattingProvider": true, -# CHECK: "documentRangeFormattingProvider": true, -# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, -# CHECK: "codeActionProvider": true, -# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, -# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true, -# CHECK: "executeCommandProvider": {"commands": ["clangd.applyFix"]} -# CHECK: }}} +# CHECK: "id": 0, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "codeActionProvider": true, +# CHECK-NEXT: "completionProvider": { +# CHECK-NEXT: "resolveProvider": false, +# CHECK-NEXT: "triggerCharacters": [ +# CHECK-NEXT: ".", +# CHECK-NEXT: ">", +# CHECK-NEXT: ":" +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "definitionProvider": true, +# CHECK-NEXT: "documentFormattingProvider": true, +# CHECK-NEXT: "documentOnTypeFormattingProvider": { +# CHECK-NEXT: "firstTriggerCharacter": "}", +# CHECK-NEXT: "moreTriggerCharacter": [] +# CHECK-NEXT: }, +# CHECK-NEXT: "documentRangeFormattingProvider": true, +# CHECK-NEXT: "executeCommandProvider": { +# CHECK-NEXT: "commands": [ +# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "signatureHelpProvider": { +# CHECK-NEXT: "triggerCharacters": [ +# CHECK-NEXT: "(", +# CHECK-NEXT: "," +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "textDocumentSync": 1 +# CHECK-NEXT: } +>>>>>>> Adds a json::Expr type to represent intermediate JSON expressions. # Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":3,"result":null} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -1,27 +1,48 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # # Test initialize request parameters with rootUri Content-Length: 143 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 606 -# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ -# CHECK: "textDocumentSync": 1, -# CHECK: "documentFormattingProvider": true, -# CHECK: "documentRangeFormattingProvider": true, -# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, -# CHECK: "codeActionProvider": true, -# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, -# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true, -# CHECK: "executeCommandProvider": {"commands": ["clangd.applyFix"]} -# CHECK: }}} -# +# CHECK: "id": 0, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "codeActionProvider": true, +# CHECK-NEXT: "completionProvider": { +# CHECK-NEXT: "resolveProvider": false, +# CHECK-NEXT: "triggerCharacters": [ +# CHECK-NEXT: ".", +# CHECK-NEXT: ">", +# CHECK-NEXT: ":" +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "definitionProvider": true, +# CHECK-NEXT: "documentFormattingProvider": true, +# CHECK-NEXT: "documentOnTypeFormattingProvider": { +# CHECK-NEXT: "firstTriggerCharacter": "}", +# CHECK-NEXT: "moreTriggerCharacter": [] +# CHECK-NEXT: }, +# CHECK-NEXT: "documentRangeFormattingProvider": true, +# CHECK-NEXT: "executeCommandProvider": { +# CHECK-NEXT: "commands": [ +# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "signatureHelpProvider": { +# CHECK-NEXT: "triggerCharacters": [ +# CHECK-NEXT: "(", +# CHECK-NEXT: "," +# CHECK-NEXT: ] +# CHECK-NEXT: }, +# CHECK-NEXT: "textDocumentSync": 1 +# CHECK-NEXT: } Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":3,"result":null} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/input-mirror.test =================================================================== --- test/clangd/input-mirror.test +++ test/clangd/input-mirror.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously -input-mirror-file %t < %s +# RUN: clangd -pretty -run-synchronously -input-mirror-file %t < %s # Note that we have to use '-b' as -input-mirror-file does not have a newline at the end of file. # RUN: diff -b %t %s # It is absolutely vital that this file has CRLF line endings. @@ -152,7 +152,9 @@ Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":3,"result":null} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/protocol.test =================================================================== --- test/clangd/protocol.test +++ test/clangd/protocol.test @@ -1,5 +1,5 @@ -# RUN: not clangd -run-synchronously < %s | FileCheck %s -# RUN: not clangd -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s +# RUN: not clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s +# RUN: not clangd -pretty -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s # vim: fileformat=dos # It is absolutely vital that this file has CRLF line endings. # @@ -12,16 +12,9 @@ {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} # Test message with Content-Type after Content-Length # -# CHECK: "jsonrpc":"2.0","id":0,"result":{"capabilities":{ -# CHECK-DAG: "textDocumentSync": 1, -# CHECK-DAG: "documentFormattingProvider": true, -# CHECK-DAG: "documentRangeFormattingProvider": true, -# CHECK-DAG: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, -# CHECK-DAG: "codeActionProvider": true, -# CHECK-DAG: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, -# CHECK-DAG: "definitionProvider": true -# CHECK: }} - +# CHECK: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK: } 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"}}} @@ -36,9 +29,16 @@ {"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}} # Test message with Content-Type before Content-Length # -# CHECK: {"jsonrpc":"2.0","id":1,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK: ]} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK: "filterText": "fake", +# CHECK-NEXT: "insertText": "fake", +# CHECK-NEXT: "insertTextFormat": 1, +# CHECK-NEXT: "kind": 7, +# CHECK-NEXT: "label": "fake::", +# CHECK-NEXT: "sortText": "000075fake" +# CHECK: ] X-Test: Testing Content-Type: application/vscode-jsonrpc; charset-utf-8 @@ -55,9 +55,16 @@ {"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}} # Test message with duplicate Content-Length headers # -# CHECK: {"jsonrpc":"2.0","id":3,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK: ]} +# CHECK: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK: "filterText": "fake", +# CHECK-NEXT: "insertText": "fake", +# CHECK-NEXT: "insertTextFormat": 1, +# CHECK-NEXT: "kind": 7, +# CHECK-NEXT: "label": "fake::", +# CHECK-NEXT: "sortText": "000075fake" +# CHECK: ] # STDERR: Warning: Duplicate Content-Length header received. The previous value for this message (10) was ignored. Content-Type: application/vscode-jsonrpc; charset-utf-8 @@ -74,10 +81,16 @@ {"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}} # Test message with Content-Type before Content-Length # -# CHECK: {"jsonrpc":"2.0","id":5,"result":[ -# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1} -# CHECK: ]} - +# CHECK: "id": 5, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK: "filterText": "fake", +# CHECK-NEXT: "insertText": "fake", +# CHECK-NEXT: "insertTextFormat": 1, +# CHECK-NEXT: "kind": 7, +# CHECK-NEXT: "label": "fake::", +# CHECK-NEXT: "sortText": "000075fake" +# CHECK: ] Content-Length: 1024 {"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}} Index: test/clangd/signature-help.test =================================================================== --- test/clangd/signature-help.test +++ test/clangd/signature-help.test @@ -15,12 +15,12 @@ Content-Length: 151 {"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}} -# CHECK: {"jsonrpc":"2.0","id":1,"result":{"activeSignature":0,"activeParameter":0,"signatures":[ +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"activeParameter":0,"activeSignature":0,"signatures":[ # CHECK-DAG: {"label":"foo(float x, float y) -> void","parameters":[{"label":"float x"},{"label":"float y"}]} # CHECK-DAG: {"label":"foo(float x, int y) -> void","parameters":[{"label":"float x"},{"label":"int y"}]} # CHECK-DAG: {"label":"foo(int x, float y) -> void","parameters":[{"label":"int x"},{"label":"float y"}]} # CHECK-DAG: {"label":"foo(int x, int y) -> void","parameters":[{"label":"int x"},{"label":"int y"}]} -# CHECK: ]} +# CHECK-SAME: ]} # Modify the document Content-Length: 333 @@ -31,21 +31,20 @@ Content-Length: 151 {"jsonrpc":"2.0","id":2,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}} -# CHECK: {"jsonrpc":"2.0","id":2,"result":{"activeSignature":0,"activeParameter":0,"signatures":[ +# CHECK: {"id":2,"jsonrpc":"2.0","result":{"activeParameter":0,"activeSignature":0,"signatures":[ # CHECK-DAG: {"label":"bar(int x, int y = 0) -> void","parameters":[{"label":"int x"},{"label":"int y = 0"}]} # CHECK-DAG: {"label":"bar(float x = 0, int y = 42) -> void","parameters":[{"label":"float x = 0"},{"label":"int y = 42"}]} -# CHECK: ]} +# CHECK-SAME: ]} Content-Length: 159 {"jsonrpc":"2.0","id":3,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///doesnotexist.cpp"},"position":{"line":8,"character":9}}} -# CHECK: {"jsonrpc":"2.0","id":3,"error":{"code":-32602,"message":"signatureHelp is called for non-added document"}} +# CHECK: {"error":{"code":-32602,"message":"signatureHelp is called for non-added document"},"id":3,"jsonrpc":"2.0"} # Shutdown. Content-Length: 49 {"jsonrpc":"2.0","id":100000,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":100000,"result":null} Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: test/clangd/unsupported-method.test =================================================================== --- test/clangd/unsupported-method.test +++ test/clangd/unsupported-method.test @@ -1,4 +1,4 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s # It is absolutely vital that this file has CRLF line endings. # Content-Length: 125 @@ -12,12 +12,19 @@ Content-Length: 92 {"jsonrpc":"2.0","id":1,"method":"textDocument/jumpInTheAirLikeYouJustDontCare","params":{}} -# CHECK: {"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"method not found"}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32601, +# CHECK-NEXT: "message": "method not found" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0" Content-Length: 44 {"jsonrpc":"2.0","id":2,"method":"shutdown"} -# CHECK: {"jsonrpc":"2.0","id":2,"result":null} +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": null Content-Length: 33 {"jsonrpc":"2.0":"method":"exit"} Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -10,6 +10,7 @@ add_extra_unittest(ClangdTests ClangdTests.cpp + JSONExprTests.cpp TraceTests.cpp ) Index: unittests/clangd/JSONExprTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/JSONExprTests.cpp @@ -0,0 +1,112 @@ +//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "JSONExpr.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace json { +namespace { + +std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); } +std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); } + +TEST(JSONExprTests, Types) { + EXPECT_EQ("true", s(true)); + EXPECT_EQ("null", s(nullptr)); + EXPECT_EQ("2.5", s(2.5)); + EXPECT_EQ(R"("foo")", s("foo")); + EXPECT_EQ("[1,2,3]", s({1, 2, 3})); + EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}})); +} + +TEST(JSONExprTests, Constructors) { + // Lots of edge cases around empty and singleton init lists. + EXPECT_EQ("[[[3]]]", s({{{3}}})); + EXPECT_EQ("[[[]]]", s({{{}}})); + EXPECT_EQ("[[{}]]", s({{obj{}}})); + EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}})); + EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})", + s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}})); +} + +TEST(JSONExprTests, StringOwnership) { + char X[] = "Hello"; + Expr Alias = static_cast(X); + X[1] = 'a'; + EXPECT_EQ(R"("Hallo")", s(Alias)); + + std::string Y = "Hello"; + Expr Copy = Y; + Y[1] = 'a'; + EXPECT_EQ(R"("Hello")", s(Copy)); +} + +TEST(JSONExprTests, CanonicalOutput) { + // Objects are sorted (but arrays aren't)! + EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}})); + EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"})); + EXPECT_EQ("3", s(3.0)); +} + +TEST(JSONExprTests, Escaping) { + std::string test = { + 0, // Strings may contain nulls. + '\b', '\f', // Have mnemonics, but we escape numerically. + '\r', '\n', '\t', // Escaped with mnemonics. + 'S', '\"', '\\', // Printable ASCII characters. + '\x7f', // Delete is not escaped. + '\xce', '\x94', // Non-ASCII UTF-8 is not escaped. + }; + EXPECT_EQ(R"("\u0000\u0008\u000c\r\n\tS\"\\)" + u8"\x7fΔ\"", + s(test)); + + EXPECT_EQ(R"({"object keys are\nescaped":true})", + s(obj{{"object keys are\nescaped", true}})); +} + +TEST(JSONExprTests, PrettyPrinting) { + EXPECT_EQ( + R"({ + "empty_array": [], + "empty_object": {}, + "full_array": [ + 1, + null + ], + "full_object": { + "nested_array": [ + { + "property": "value" + } + ] + } +})", + sp(obj{ + {"empty_object", obj{}}, + {"empty_array", {}}, + {"full_array", {1, nullptr}}, + {"full_object", + obj{ + {"nested_array", + {obj{ + {"property", "value"}, + }}}, + }}, + })); +} + +} // namespace +} // namespace json +} // namespace clangd +} // namespace clang