summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang-tools-extra/clangd/CMakeLists.txt1
-rw-r--r--clang-tools-extra/clangd/ClangdLSPServer.cpp137
-rw-r--r--clang-tools-extra/clangd/JSONExpr.cpp216
-rw-r--r--clang-tools-extra/clangd/JSONExpr.h247
-rw-r--r--clang-tools-extra/clangd/JSONRPCDispatcher.cpp71
-rw-r--r--clang-tools-extra/clangd/JSONRPCDispatcher.h19
-rw-r--r--clang-tools-extra/clangd/Protocol.cpp197
-rw-r--r--clang-tools-extra/clangd/Protocol.h26
-rw-r--r--clang-tools-extra/clangd/tool/ClangdMain.cpp7
-rw-r--r--clang-tools-extra/test/clangd/authority-less-uri.test30
-rw-r--r--clang-tools-extra/test/clangd/completion-items-kinds.test17
-rw-r--r--clang-tools-extra/test/clangd/completion-priorities.test21
-rw-r--r--clang-tools-extra/test/clangd/completion-qualifiers.test11
-rw-r--r--clang-tools-extra/test/clangd/completion-snippet.test41
-rw-r--r--clang-tools-extra/test/clangd/completion.test39
-rw-r--r--clang-tools-extra/test/clangd/definitions.test322
-rw-r--r--clang-tools-extra/test/clangd/diagnostics-preamble.test10
-rw-r--r--clang-tools-extra/test/clangd/diagnostics.test41
-rw-r--r--clang-tools-extra/test/clangd/did-change-watch-files.test13
-rw-r--r--clang-tools-extra/test/clangd/execute-command.test53
-rw-r--r--clang-tools-extra/test/clangd/extra-flags.test75
-rw-r--r--clang-tools-extra/test/clangd/fixits.test242
-rw-r--r--clang-tools-extra/test/clangd/formatting.test188
-rw-r--r--clang-tools-extra/test/clangd/initialize-params-invalid.test48
-rw-r--r--clang-tools-extra/test/clangd/initialize-params.test51
-rw-r--r--clang-tools-extra/test/clangd/input-mirror.test3
-rw-r--r--clang-tools-extra/test/clangd/protocol.test57
-rw-r--r--clang-tools-extra/test/clangd/signature-help.test11
-rw-r--r--clang-tools-extra/test/clangd/unsupported-method.test10
-rw-r--r--clang-tools-extra/unittests/clangd/CMakeLists.txt1
-rw-r--r--clang-tools-extra/unittests/clangd/JSONExprTests.cpp112
31 files changed, 1816 insertions, 501 deletions
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 1825d1103f2..a81da615a36 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 4f70acb8692..c916d5371cb 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/JSONExpr.cpp b/clang-tools-extra/clangd/JSONExpr.cpp
new file mode 100644
index 00000000000..2fd82af9771
--- /dev/null
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/JSONExpr.h b/clang-tools-extra/clangd/JSONExpr.h
new file mode 100644
index 00000000000..90ff31abee6
--- /dev/null
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/JSONRPCDispatcher.cpp b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
index 74d1dc81fb3..4abe7b7d69b 100644
--- a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/JSONRPCDispatcher.h b/clang-tools-extra/clangd/JSONRPCDispatcher.h
index 9a682147411..39ff7b271e5 100644
--- a/clang-tools-extra/clangd/JSONRPCDispatcher.h
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 698c9416468..254a95fbd27 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 4a7c8bf04d8..fcde23b2f11 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/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/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 2808d0555c2..dc8969aa96b 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/authority-less-uri.test b/clang-tools-extra/test/clangd/authority-less-uri.test
index 494f6914cff..d33a1c278f0 100644
--- a/clang-tools-extra/test/clangd/authority-less-uri.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/completion-items-kinds.test b/clang-tools-extra/test/clangd/completion-items-kinds.test
index 8ad7cfa7ad4..83d2c7b96fa 100644
--- a/clang-tools-extra/test/clangd/completion-items-kinds.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/completion-priorities.test b/clang-tools-extra/test/clangd/completion-priorities.test
index 35ecd61bfb9..3c05187df1a 100644
--- a/clang-tools-extra/test/clangd/completion-priorities.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/completion-qualifiers.test b/clang-tools-extra/test/clangd/completion-qualifiers.test
index 97cc002d0c1..0dd49b77fe9 100644
--- a/clang-tools-extra/test/clangd/completion-qualifiers.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/completion-snippet.test b/clang-tools-extra/test/clangd/completion-snippet.test
index fdf797d6abd..c43302a8151 100644
--- a/clang-tools-extra/test/clangd/completion-snippet.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/completion.test b/clang-tools-extra/test/clangd/completion.test
index bc2b5302d27..a88b241089e 100644
--- a/clang-tools-extra/test/clangd/completion.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/definitions.test b/clang-tools-extra/test/clangd/definitions.test
index c52e5dec2e3..efe5b0289c7 100644
--- a/clang-tools-extra/test/clangd/definitions.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/diagnostics-preamble.test b/clang-tools-extra/test/clangd/diagnostics-preamble.test
index c4adbe3c6f4..a17f15f585e 100644
--- a/clang-tools-extra/test/clangd/diagnostics-preamble.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/diagnostics.test b/clang-tools-extra/test/clangd/diagnostics.test
index 41838c4c984..99c222d8907 100644
--- a/clang-tools-extra/test/clangd/diagnostics.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/did-change-watch-files.test b/clang-tools-extra/test/clangd/did-change-watch-files.test
index d29184d709f..717a6dd5d53 100644
--- a/clang-tools-extra/test/clangd/did-change-watch-files.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/execute-command.test b/clang-tools-extra/test/clangd/execute-command.test
index 7e326d7a49c..833690d6b2f 100644
--- a/clang-tools-extra/test/clangd/execute-command.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/extra-flags.test b/clang-tools-extra/test/clangd/extra-flags.test
index fe703796b07..8defbefa659 100644
--- a/clang-tools-extra/test/clangd/extra-flags.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/fixits.test b/clang-tools-extra/test/clangd/fixits.test
index 38e086cf9b4..2a16c8c789a 100644
--- a/clang-tools-extra/test/clangd/fixits.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/formatting.test b/clang-tools-extra/test/clangd/formatting.test
index 09068fc0d69..87181e214ca 100644
--- a/clang-tools-extra/test/clangd/formatting.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/initialize-params-invalid.test b/clang-tools-extra/test/clangd/initialize-params-invalid.test
index 8c1f442c7d8..535dc2c0a98 100644
--- a/clang-tools-extra/test/clangd/initialize-params-invalid.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/initialize-params.test b/clang-tools-extra/test/clangd/initialize-params.test
index f2d185e263f..af9b3d4ea33 100644
--- a/clang-tools-extra/test/clangd/initialize-params.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/input-mirror.test b/clang-tools-extra/test/clangd/input-mirror.test
index db54bcf5411..5b07cb53f55 100644
--- a/clang-tools-extra/test/clangd/input-mirror.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/protocol.test b/clang-tools-extra/test/clangd/protocol.test
index 61fa90c86d1..7837410de8a 100644
--- a/clang-tools-extra/test/clangd/protocol.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/signature-help.test b/clang-tools-extra/test/clangd/signature-help.test
index c28a309fb60..d19422b06e3 100644
--- a/clang-tools-extra/test/clangd/signature-help.test
+++ b/clang-tools-extra/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/clang-tools-extra/test/clangd/unsupported-method.test b/clang-tools-extra/test/clangd/unsupported-method.test
index cccbb5cc0ff..0ce22bbc8c3 100644
--- a/clang-tools-extra/test/clangd/unsupported-method.test
+++ b/clang-tools-extra/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/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt
index f45bc712381..5be935c97ac 100644
--- a/clang-tools-extra/unittests/clangd/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clangd/CMakeLists.txt
@@ -10,6 +10,7 @@ include_directories(
add_extra_unittest(ClangdTests
ClangdTests.cpp
+ JSONExprTests.cpp
TraceTests.cpp
)
diff --git a/clang-tools-extra/unittests/clangd/JSONExprTests.cpp b/clang-tools-extra/unittests/clangd/JSONExprTests.cpp
new file mode 100644
index 00000000000..b6a2c562206
--- /dev/null
+++ b/clang-tools-extra/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
OpenPOWER on IntegriCloud