aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam McCall <sam.mccall@gmail.com>2017-11-06 15:40:30 +0000
committerSam McCall <sam.mccall@gmail.com>2017-11-06 15:40:30 +0000
commit445a3f5e0c081457ecaa43c76f16f320b3407459 (patch)
treef5dd4cb6c9e6e48c909d1d3c052f961fcdaddcf6
parent38b98640c3820151aa4aba16c5b7797ec8c8b3e7 (diff)
Adds a json::Expr type to represent intermediate JSON expressions.
Summary: This form can be created with a nice clang-format-friendly literal syntax, and gets escaping right. It knows how to call unparse() on our Protocol types. All the places where we pass around JSON internally now use this type. Object properties are sorted (stored as std::map) and so serialization is canonicalized, with optional prettyprinting (triggered by a -pretty flag). This makes the lit tests much nicer to read and somewhat nicer to debug. (Unfortunately the completion tests use CHECK-DAG, which only has line-granularity, so pretty-printing is disabled there. In future we could make completion ordering deterministic, or switch to unittests). Compared to the current approach, it has some efficiencies like avoiding copies of string literals used as object keys, but is probably slower overall. I think the code/test quality benefits are worth it. This patch doesn't attempt to do anything about JSON *parsing*. It takes direction from the proposal in this doc[1], but is limited in scope and visibility, for now. I am of half a mind just to use Expr as the target of a parser, and maybe do a little string deduplication, but not bother with clever memory allocation. That would be simple, and fast enough for clangd... [1] https://docs.google.com/document/d/1OEF9IauWwNuSigZzvvbjc1cVS1uGHRyGTXaoy3DjqM4/edit +cc d0k so he can tell me not to use std::map. Reviewers: ioeric, malaperle Subscribers: bkramer, ilya-biryukov, mgorny, klimek Differential Revision: https://reviews.llvm.org/D39435 git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@317486 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--clangd/CMakeLists.txt1
-rw-r--r--clangd/ClangdLSPServer.cpp137
-rw-r--r--clangd/JSONExpr.cpp216
-rw-r--r--clangd/JSONExpr.h247
-rw-r--r--clangd/JSONRPCDispatcher.cpp71
-rw-r--r--clangd/JSONRPCDispatcher.h19
-rw-r--r--clangd/Protocol.cpp197
-rw-r--r--clangd/Protocol.h26
-rw-r--r--clangd/tool/ClangdMain.cpp7
-rw-r--r--test/clangd/authority-less-uri.test30
-rw-r--r--test/clangd/completion-items-kinds.test17
-rw-r--r--test/clangd/completion-priorities.test21
-rw-r--r--test/clangd/completion-qualifiers.test11
-rw-r--r--test/clangd/completion-snippet.test41
-rw-r--r--test/clangd/completion.test39
-rw-r--r--test/clangd/definitions.test322
-rw-r--r--test/clangd/diagnostics-preamble.test10
-rw-r--r--test/clangd/diagnostics.test41
-rw-r--r--test/clangd/did-change-watch-files.test13
-rw-r--r--test/clangd/execute-command.test53
-rw-r--r--test/clangd/extra-flags.test75
-rw-r--r--test/clangd/fixits.test242
-rw-r--r--test/clangd/formatting.test188
-rw-r--r--test/clangd/initialize-params-invalid.test48
-rw-r--r--test/clangd/initialize-params.test51
-rw-r--r--test/clangd/input-mirror.test3
-rw-r--r--test/clangd/protocol.test57
-rw-r--r--test/clangd/signature-help.test11
-rw-r--r--test/clangd/unsupported-method.test10
-rw-r--r--unittests/clangd/CMakeLists.txt1
-rw-r--r--unittests/clangd/JSONExprTests.cpp112
31 files changed, 1816 insertions, 501 deletions
diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt
index 1825d110..a81da615 100644
--- a/clangd/CMakeLists.txt
+++ b/clangd/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_library(clangDaemon
DraftStore.cpp
GlobalCompilationDatabase.cpp
JSONRPCDispatcher.cpp
+ JSONExpr.cpp
Logger.cpp
Protocol.cpp
ProtocolHandlers.cpp
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index 4f70acb8..c916d537 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -20,35 +20,46 @@ namespace {
std::vector<TextEdit>
replacementsToEdits(StringRef Code,
const std::vector<tooling::Replacement> &Replacements) {
- std::vector<TextEdit> Edits;
// Turn the replacements into the format specified by the Language Server
- // Protocol.
+ // Protocol. Fuse them into one big JSON array.
+ std::vector<TextEdit> 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::onInitialize(Ctx C, InitializeParams &Params) {
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 @@ void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
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 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
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<clang::tooling::Replacement> 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 @@ void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
// 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 @@ void ClangdLSPServer::onSignatureHelp(Ctx C,
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 @@ void ClangdLSPServer::onGoToDefinition(Ctx C,
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<Path> 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 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
void ClangdLSPServer::onDiagnosticsReady(
PathRef File, Tagged<std::vector<DiagWithFixIts>> 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 @@ void ClangdLSPServer::onDiagnosticsReady(
}
// 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)},
+ }},
+ });
}
diff --git a/clangd/JSONExpr.cpp b/clangd/JSONExpr.cpp
new file mode 100644
index 00000000..2fd82af9
--- /dev/null
+++ b/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<StringRef>(M.as<StringRef>());
+ break;
+ case T_String:
+ create<std::string>(M.as<std::string>());
+ break;
+ case T_Object:
+ create<Object>(M.as<Object>());
+ break;
+ case T_Array:
+ create<Array>(M.as<Array>());
+ 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<StringRef>(M.as<StringRef>());
+ break;
+ case T_String:
+ create<std::string>(std::move(M.as<std::string>()));
+ M.Type = T_Null;
+ break;
+ case T_Object:
+ create<Object>(std::move(M.as<Object>()));
+ M.Type = T_Null;
+ break;
+ case T_Array:
+ create<Array>(std::move(M.as<Array>()));
+ 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>().~StringRef();
+ break;
+ case T_String:
+ as<std::string>().~basic_string();
+ break;
+ case T_Object:
+ as<Object>().~Object();
+ break;
+ case T_Array:
+ as<Array>().~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 <typename Indenter>
+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<bool>() ? "true" : "false");
+ break;
+ case T_Number:
+ OS << format("%g", as<double>());
+ break;
+ case T_StringRef:
+ quote(OS, as<StringRef>());
+ break;
+ case T_String:
+ quote(OS, as<std::string>());
+ break;
+ case T_Object: {
+ bool Comma = false;
+ OS << '{';
+ I(Indent);
+ for (const auto &P : as<Expr::Object>()) {
+ 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<Expr::Array>()) {
+ 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<clang::clangd::json::Expr>::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;
+ };
+ });
+}
diff --git a/clangd/JSONExpr.h b/clangd/JSONExpr.h
new file mode 100644
index 00000000..90ff31ab
--- /dev/null
+++ b/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 <map>
+
+#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<StringExpr, Expr>
+// - json::ary is a type like vector<Expr>
+// 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<Expr> Elements) : Expr(Array(Elements)) {}
+ Expr(Array &&Elements) : Type(T_Array) { create<Array>(std::move(Elements)); }
+ Expr(Object &&Properties) : Type(T_Object) {
+ create<Object>(std::move(Properties));
+ }
+ // Strings: types with value semantics.
+ Expr(std::string &&V) : Type(T_String) { create<std::string>(std::move(V)); }
+ Expr(const std::string &V) : Type(T_String) { create<std::string>(V); }
+ Expr(const llvm::SmallVectorImpl<char> &V) : Type(T_String) {
+ create<std::string>(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<llvm::StringRef>(V); }
+ Expr(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
+ Expr(std::nullptr_t) : Type(T_Null) {}
+ // Prevent implicit conversions to boolean.
+ template <typename T, typename = typename std::enable_if<
+ std::is_same<T, bool>::value>::type>
+ Expr(T B) : Type(T_Boolean) {
+ create<bool>(B);
+ }
+ // Numbers: arithmetic types that are not boolean.
+ template <
+ typename T,
+ typename = typename std::enable_if<std::is_arithmetic<T>::value>::type,
+ typename = typename std::enable_if<std::integral_constant<
+ bool, !std::is_same<T, bool>::value>::value>::type>
+ Expr(T D) : Type(T_Number) {
+ create<double>(D);
+ }
+ // Types with a static T::unparse function returning an Expr.
+ // FIXME: should this be a free unparse() function found by ADL?
+ template <typename T,
+ typename = typename std::enable_if<std::is_same<
+ Expr, decltype(T::unparse(*(const T *)nullptr))>::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<T> is a container of const T).
+ void moveFrom(const Expr &&M);
+
+ template <typename T, typename... U> void create(U &&... V) {
+ new (&as<T>()) T(std::forward<U>(V)...);
+ }
+ template <typename T> T &as() const {
+ return *reinterpret_cast<T *>(Union.buffer);
+ }
+
+ template <typename Indenter>
+ void print(llvm::raw_ostream &, const Indenter &) const;
+ friend struct llvm::format_provider<clang::clangd::json::Expr>;
+
+ 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<char> &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<std::string> Owned; // mutable for cheating.
+ llvm::StringRef Data;
+ };
+
+ class Object : public std::map<ObjectKey, Expr> {
+ public:
+ explicit Object() {}
+ // Use a custom struct for list-init, because pair forces extra copies.
+ struct KV;
+ explicit Object(std::initializer_list<KV> 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<Expr> {
+ public:
+ explicit Array() {}
+ explicit Array(std::initializer_list<Expr> Elements) {
+ reserve(Elements.size());
+ for (const Expr &V : Elements)
+ emplace_back(std::move(V));
+ };
+ template <typename Collection> explicit Array(const Collection &C) {
+ for (const auto &V : C)
+ emplace_back(V);
+ }
+ };
+
+private:
+ mutable llvm::AlignedCharArrayUnion<bool, double, llvm::StringRef,
+ std::string, Array, Object>
+ Union;
+};
+
+struct Expr::Object::KV {
+ ObjectKey K;
+ Expr V;
+};
+
+inline Expr::Object::Object(std::initializer_list<KV> 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<clang::clangd::json::Expr> {
+ static void format(const clang::clangd::json::Expr &, raw_ostream &,
+ StringRef);
+};
+} // namespace llvm
+
+#endif
diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp
index 74d1dc81..4abe7b7d 100644
--- a/clangd/JSONRPCDispatcher.cpp
+++ b/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<std::mutex> 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 @@ void JSONOutput::mirrorInput(const Twine &Message) {
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 @@ void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
static void
callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
- llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
+ llvm::yaml::ScalarNode *Method, llvm::Optional<json::Expr> ID,
llvm::yaml::MappingNode *Params,
const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) {
llvm::SmallString<64> MethodStorage;
@@ -88,7 +102,7 @@ callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
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 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
llvm::yaml::ScalarNode *Version = nullptr;
llvm::yaml::ScalarNode *Method = nullptr;
llvm::yaml::MappingNode *Params = nullptr;
- llvm::yaml::ScalarNode *Id = nullptr;
+ llvm::Optional<json::Expr> ID;
for (auto &NextKeyValue : *Object) {
auto *KeyString =
dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
@@ -127,7 +141,18 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
} else if (KeyValue == "method") {
Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
} else if (KeyValue == "id") {
- Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
+ // ID may be either a string or a number.
+ if (auto *IdNode = dyn_cast<llvm::yaml::ScalarNode>(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 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
// 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<llvm::yaml::MappingNode>(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 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
// leftovers.
if (!Method)
return false;
- callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out);
+ callHandler(Handlers, Method, std::move(ID), nullptr, UnknownHandler, Out);
return true;
}
diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h
index 9a682147..39ff7b27 100644
--- a/clangd/JSONRPCDispatcher.h
+++ b/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 @@ namespace clangd {
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 @@ private:
llvm::raw_ostream &Outs;
llvm::raw_ostream &Logs;
llvm::raw_ostream *InputMirror;
+ bool Pretty;
std::mutex StreamMutex;
};
@@ -52,18 +54,19 @@ private:
/// 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<json::Expr> 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<json::Expr> ID;
};
/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the
diff --git a/clangd/Protocol.cpp b/clangd/Protocol.cpp
index 698c9416..254a95fb 100644
--- a/clangd/Protocol.cpp
+++ b/clangd/Protocol.cpp
@@ -63,7 +63,7 @@ URI URI::parse(llvm::yaml::ScalarNode *Param) {
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>
TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params,
@@ -125,11 +125,11 @@ llvm::Optional<Position> Position::parse(llvm::yaml::MappingNode *Params,
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> Range::parse(llvm::yaml::MappingNode *Params,
@@ -165,20 +165,18 @@ llvm::Optional<Range> Range::parse(llvm::yaml::MappingNode *Params,
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<TextDocumentItem>
@@ -279,25 +277,11 @@ llvm::Optional<TextEdit> TextEdit::parse(llvm::yaml::MappingNode *Params,
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<TextEdit> &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 @@ FormattingOptions::parse(llvm::yaml::MappingNode *Params,
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<DocumentRangeFormattingParams>
@@ -982,28 +966,18 @@ ExecuteCommandParams::parse(llvm::yaml::MappingNode *Params,
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<TextDocumentPositionParams>
@@ -1040,96 +1014,57 @@ TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params,
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<int>(CI.kind) << R"(,)";
+ Result["kind"] = static_cast<int>(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<int>(CI.insertTextFormat)
- << R"(,)";
- }
+ Result["insertText"] = CI.insertText;
+ if (CI.insertTextFormat != InsertTextFormat::Missing)
+ Result["insertTextFormat"] = static_cast<int>(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)},
+ };
}
diff --git a/clangd/Protocol.h b/clangd/Protocol.h
index 4a7c8bf0..fcde23b2 100644
--- a/clangd/Protocol.h
+++ b/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 <string>
@@ -39,7 +40,7 @@ struct URI {
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 @@ struct Position {
static llvm::Optional<Position> 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 @@ struct Range {
static llvm::Optional<Range> 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 @@ struct Location {
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 @@ struct TextEdit {
static llvm::Optional<TextEdit> parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger);
- static std::string unparse(const TextEdit &P);
- static std::string unparse(const std::vector<TextEdit> &TextEdits);
+ static json::Expr unparse(const TextEdit &P);
};
struct TextDocumentItem {
@@ -283,7 +283,7 @@ struct FormattingOptions {
static llvm::Optional<FormattingOptions>
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 @@ struct WorkspaceEdit {
static llvm::Optional<WorkspaceEdit> 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 ExecuteCommandParams {
struct ApplyWorkspaceEditParams {
WorkspaceEdit edit;
- static std::string unparse(const ApplyWorkspaceEditParams &Params);
+ static json::Expr unparse(const ApplyWorkspaceEditParams &Params);
};
struct TextDocumentPositionParams {
@@ -527,7 +527,7 @@ struct CompletionItem {
//
// 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 @@ struct ParameterInformation {
/// 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 @@ struct SignatureInformation {
/// The parameters of this signature.
std::vector<ParameterInformation> parameters;
- static std::string unparse(const SignatureInformation &);
+ static json::Expr unparse(const SignatureInformation &);
};
/// Represents the signature of a callable.
@@ -569,7 +569,7 @@ struct SignatureHelp {
/// The active parameter of the active signature.
int activeParameter = 0;
- static std::string unparse(const SignatureHelp &);
+ static json::Expr unparse(const SignatureHelp &);
};
} // namespace clangd
diff --git a/clangd/tool/ClangdMain.cpp b/clangd/tool/ClangdMain.cpp
index 2808d055..dc8969aa 100644
--- a/clangd/tool/ClangdMain.cpp
+++ b/clangd/tool/ClangdMain.cpp
@@ -41,6 +41,10 @@ static llvm::cl::opt<bool> EnableSnippets(
"Present snippet completions instead of plaintext completions"),
llvm::cl::init(false));
+static llvm::cl::opt<bool>
+ PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"),
+ llvm::cl::init(false));
+
static llvm::cl::opt<bool> RunSynchronously(
"run-synchronously",
llvm::cl::desc("Parse on main thread. If set, -j is ignored"),
@@ -104,7 +108,8 @@ int main(int argc, char *argv[]) {
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.
diff --git a/test/clangd/authority-less-uri.test b/test/clangd/authority-less-uri.test
index 494f6914..d33a1c27 100644
--- a/test/clangd/authority-less-uri.test
+++ b/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 @@ Content-Length: 146
{"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"}
diff --git a/test/clangd/completion-items-kinds.test b/test/clangd/completion-items-kinds.test
index 8ad7cfa7..83d2c7b9 100644
--- a/test/clangd/completion-items-kinds.test
+++ b/test/clangd/completion-items-kinds.test
@@ -11,27 +11,26 @@ Content-Length: 148
{"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<type>(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<type>(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}
diff --git a/test/clangd/completion-priorities.test b/test/clangd/completion-priorities.test
index 35ecd61b..3c05187d 100644
--- a/test/clangd/completion-priorities.test
+++ b/test/clangd/completion-priorities.test
@@ -16,25 +16,24 @@ Content-Length: 151
# 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"}
diff --git a/test/clangd/completion-qualifiers.test b/test/clangd/completion-qualifiers.test
index 97cc002d..0dd49b77 100644
--- a/test/clangd/completion-qualifiers.test
+++ b/test/clangd/completion-qualifiers.test
@@ -8,15 +8,14 @@ Content-Length: 297
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"}
diff --git a/test/clangd/completion-snippet.test b/test/clangd/completion-snippet.test
index fdf797d6..c43302a8 100644
--- a/test/clangd/completion-snippet.test
+++ b/test/clangd/completion-snippet.test
@@ -15,27 +15,27 @@ Content-Length: 148
# 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: 226
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"}
diff --git a/test/clangd/completion.test b/test/clangd/completion.test
index bc2b5302..a88b2410 100644
--- a/test/clangd/completion.test
+++ b/test/clangd/completion.test
@@ -15,27 +15,27 @@ Content-Length: 148
# 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 @@ 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"}
diff --git a/test/clangd/definitions.test b/test/clangd/definitions.test
index c52e5dec..efe5b028 100644
--- a/test/clangd/definitions.test
+++ b/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 @@ Content-Length: 148
{"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 @@ Content-Length: 149
{"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 @@ Content-Length: 149
{"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 @@ Content-Length: 148
{"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 @@ Content-Length: 148
{"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 @@ Content-Length: 148
{"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 @@ Content-Length: 148
{"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 @@ Content-Length: 148
{"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 @@ Content-Length: 149
{"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 <typename MyTemplateParam>\nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}}
@@ -109,8 +259,9 @@ Content-Length: 149
{"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 @@ Content-Length: 148
{"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: 265
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 @@ Content-Length: 148
{"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 @@ Content-Length: 148
{"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"}
diff --git a/test/clangd/diagnostics-preamble.test b/test/clangd/diagnostics-preamble.test
index c4adbe3c..a17f15f5 100644
--- a/test/clangd/diagnostics-preamble.test
+++ b/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,14 @@ Content-Length: 125
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}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/diagnostics.test b/test/clangd/diagnostics.test
index 41838c4c..99c222d8 100644
--- a/test/clangd/diagnostics.test
+++ b/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,43 @@ Content-Length: 125
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}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/did-change-watch-files.test b/test/clangd/did-change-watch-files.test
index d29184d7..717a6dd5 100644
--- a/test/clangd/did-change-watch-files.test
+++ b/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}]}}
diff --git a/test/clangd/execute-command.test b/test/clangd/execute-command.test
index 7e326d7a..833690d6 100644
--- a/test/clangd/execute-command.test
+++ b/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: 125
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":{}}
diff --git a/test/clangd/extra-flags.test b/test/clangd/extra-flags.test
index fe703796..8defbefa 100644
--- a/test/clangd/extra-flags.test
+++ b/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,80 @@ Content-Length: 125
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}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/fixits.test b/test/clangd/fixits.test
index 38e086cf..2a16c8c7 100644
--- a/test/clangd/fixits.test
+++ b/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: 125
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"}
diff --git a/test/clangd/formatting.test b/test/clangd/formatting.test
index 09068fc0..87181e21 100644
--- a/test/clangd/formatting.test
+++ b/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: 197
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: 190
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: 193
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"}
diff --git a/test/clangd/initialize-params-invalid.test b/test/clangd/initialize-params-invalid.test
index 8c1f442c..535dc2c0 100644
--- a/test/clangd/initialize-params-invalid.test
+++ b/test/clangd/initialize-params-invalid.test
@@ -1,27 +1,45 @@
-# 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: }
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"}
diff --git a/test/clangd/initialize-params.test b/test/clangd/initialize-params.test
index f2d185e2..af9b3d4e 100644
--- a/test/clangd/initialize-params.test
+++ b/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"}
diff --git a/test/clangd/input-mirror.test b/test/clangd/input-mirror.test
index db54bcf5..5b07cb53 100644
--- a/test/clangd/input-mirror.test
+++ b/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,6 @@ Content-Length: 148
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"}
diff --git a/test/clangd/protocol.test b/test/clangd/protocol.test
index 61fa90c8..7837410d 100644
--- a/test/clangd/protocol.test
+++ b/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 @@ Content-Type: application/vscode-jsonrpc; charset-utf-8
{"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 @@ Content-Length: 146
{"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 @@ Content-Length: 146
{"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 @@ Content-Length: 146
{"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}}}
diff --git a/test/clangd/signature-help.test b/test/clangd/signature-help.test
index c28a309f..d19422b0 100644
--- a/test/clangd/signature-help.test
+++ b/test/clangd/signature-help.test
@@ -15,12 +15,12 @@ Content-Length: 333
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: 333
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"}
diff --git a/test/clangd/unsupported-method.test b/test/clangd/unsupported-method.test
index cccbb5cc..0ce22bbc 100644
--- a/test/clangd/unsupported-method.test
+++ b/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,16 @@ Content-Length: 143
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}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/unittests/clangd/CMakeLists.txt b/unittests/clangd/CMakeLists.txt
index f45bc712..5be935c9 100644
--- a/unittests/clangd/CMakeLists.txt
+++ b/unittests/clangd/CMakeLists.txt
@@ -10,6 +10,7 @@ include_directories(
add_extra_unittest(ClangdTests
ClangdTests.cpp
+ JSONExprTests.cpp
TraceTests.cpp
)
diff --git a/unittests/clangd/JSONExprTests.cpp b/unittests/clangd/JSONExprTests.cpp
new file mode 100644
index 00000000..b6a2c562
--- /dev/null
+++ b/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<const char *>(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