Files
Nathan Ridge 28d564bcc9 [Index] Reflect in SymbolSubKind whether a typedef points to a struct or a class (#181967)
Typedefs don't have their own symbol kind in the Language Server
Protocol, the choices are Struct or Class. For clangd to be able to
represent typedefs accurately in response to requests such as
`workspace/symbol`, it needs this information surfaced in
index::SymbolInfo.

Fixes https://github.com/clangd/clangd/issues/2253
2026-02-19 03:31:38 -05:00

1779 lines
58 KiB
C++

//===--- Protocol.cpp - Language Server Protocol Implementation -----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains the serialization code for the LSP structs.
//
//===----------------------------------------------------------------------===//
#include "Protocol.h"
#include "URI.h"
#include "support/Logger.h"
#include "clang/Basic/LLVM.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace clangd {
namespace {
// Helper that doesn't treat `null` and absent fields as failures.
template <typename T>
bool mapOptOrNull(const llvm::json::Value &Params, llvm::StringLiteral Prop,
T &Out, llvm::json::Path P) {
auto *O = Params.getAsObject();
assert(O);
auto *V = O->get(Prop);
// Field is missing or null.
if (!V || V->getAsNull())
return true;
return fromJSON(*V, Out, P.field(Prop));
}
} // namespace
char LSPError::ID;
URIForFile URIForFile::canonicalize(llvm::StringRef AbsPath,
llvm::StringRef TUPath) {
assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative");
auto Resolved = URI::resolvePath(AbsPath, TUPath);
if (!Resolved) {
elog("URIForFile: failed to resolve path {0} with TU path {1}: "
"{2}.\nUsing unresolved path.",
AbsPath, TUPath, Resolved.takeError());
return URIForFile(std::string(AbsPath));
}
return URIForFile(std::move(*Resolved));
}
llvm::Expected<URIForFile> URIForFile::fromURI(const URI &U,
llvm::StringRef HintPath) {
auto Resolved = URI::resolve(U, HintPath);
if (!Resolved)
return Resolved.takeError();
return URIForFile(std::move(*Resolved));
}
bool fromJSON(const llvm::json::Value &E, URIForFile &R, llvm::json::Path P) {
if (auto S = E.getAsString()) {
auto Parsed = URI::parse(*S);
if (!Parsed) {
consumeError(Parsed.takeError());
P.report("failed to parse URI");
return false;
}
if (Parsed->scheme() != "file" && Parsed->scheme() != "test") {
P.report("clangd only supports 'file' URI scheme for workspace files");
return false;
}
// "file" and "test" schemes do not require hint path.
auto U = URIForFile::fromURI(*Parsed, /*HintPath=*/"");
if (!U) {
P.report("unresolvable URI");
consumeError(U.takeError());
return false;
}
R = std::move(*U);
return true;
}
return false;
}
llvm::json::Value toJSON(const URIForFile &U) { return U.uri(); }
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) {
return OS << U.uri();
}
llvm::json::Value toJSON(const TextDocumentIdentifier &R) {
return llvm::json::Object{{"uri", R.uri}};
}
bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("uri", R.uri);
}
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) {
auto Result = toJSON(static_cast<const TextDocumentIdentifier &>(R));
Result.getAsObject()->try_emplace("version", R.version);
return Result;
}
bool fromJSON(const llvm::json::Value &Params,
VersionedTextDocumentIdentifier &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return fromJSON(Params, static_cast<TextDocumentIdentifier &>(R), P) && O &&
O.map("version", R.version);
}
bool fromJSON(const llvm::json::Value &Params, Position &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("line", R.line) && O.map("character", R.character);
}
llvm::json::Value toJSON(const Position &P) {
return llvm::json::Object{
{"line", P.line},
{"character", P.character},
};
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Position &P) {
return OS << P.line << ':' << P.character;
}
bool fromJSON(const llvm::json::Value &Params, Range &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("start", R.start) && O.map("end", R.end);
}
llvm::json::Value toJSON(const Range &P) {
return llvm::json::Object{
{"start", P.start},
{"end", P.end},
};
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Range &R) {
return OS << R.start << '-' << R.end;
}
llvm::json::Value toJSON(const Location &P) {
return llvm::json::Object{
{"uri", P.uri},
{"range", P.range},
};
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Location &L) {
return OS << L.range << '@' << L.uri;
}
llvm::json::Value toJSON(const ReferenceLocation &P) {
llvm::json::Object Result{
{"uri", P.uri},
{"range", P.range},
};
if (P.containerName)
Result.insert({"containerName", P.containerName});
return Result;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const ReferenceLocation &L) {
return OS << L.range << '@' << L.uri << " (container: " << L.containerName
<< ")";
}
bool fromJSON(const llvm::json::Value &Params, TextDocumentItem &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) &&
O.map("version", R.version) && O.map("text", R.text);
}
bool fromJSON(const llvm::json::Value &Params, TextEdit &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("range", R.range) && O.map("newText", R.newText) &&
O.mapOptional("annotationId", R.annotationId);
}
llvm::json::Value toJSON(const TextEdit &P) {
llvm::json::Object Result{
{"range", P.range},
{"newText", P.newText},
};
if (!P.annotationId.empty())
Result["annotationId"] = P.annotationId;
return Result;
}
bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("label", R.label) &&
O.map("needsConfirmation", R.needsConfirmation) &&
O.mapOptional("description", R.description);
}
llvm::json::Value toJSON(const ChangeAnnotation & CA) {
llvm::json::Object Result{{"label", CA.label}};
if (CA.needsConfirmation)
Result["needsConfirmation"] = *CA.needsConfirmation;
if (!CA.description.empty())
Result["description"] = CA.description;
return Result;
}
bool fromJSON(const llvm::json::Value &Params, TextDocumentEdit &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) && O.map("edits", R.edits);
}
llvm::json::Value toJSON(const TextDocumentEdit &P) {
llvm::json::Object Result{{"textDocument", P.textDocument},
{"edits", P.edits}};
return Result;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TextEdit &TE) {
OS << TE.range << " => \"";
llvm::printEscapedString(TE.newText, OS);
return OS << '"';
}
bool fromJSON(const llvm::json::Value &E, TraceLevel &Out, llvm::json::Path P) {
if (auto S = E.getAsString()) {
if (*S == "off") {
Out = TraceLevel::Off;
return true;
}
if (*S == "messages") {
Out = TraceLevel::Messages;
return true;
}
if (*S == "verbose") {
Out = TraceLevel::Verbose;
return true;
}
}
return false;
}
bool fromJSON(const llvm::json::Value &E, SymbolKind &Out, llvm::json::Path P) {
if (auto T = E.getAsInteger()) {
if (*T < static_cast<int>(SymbolKind::File) ||
*T > static_cast<int>(SymbolKind::TypeParameter))
return false;
Out = static_cast<SymbolKind>(*T);
return true;
}
return false;
}
bool fromJSON(const llvm::json::Value &E, SymbolKindBitset &Out,
llvm::json::Path P) {
if (auto *A = E.getAsArray()) {
for (size_t I = 0; I < A->size(); ++I) {
SymbolKind KindOut;
if (fromJSON((*A)[I], KindOut, P.index(I)))
Out.set(size_t(KindOut));
}
return true;
}
return false;
}
SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKindBitset &SupportedSymbolKinds) {
auto KindVal = static_cast<size_t>(Kind);
if (KindVal >= SymbolKindMin && KindVal <= SupportedSymbolKinds.size() &&
SupportedSymbolKinds[KindVal])
return Kind;
switch (Kind) {
// Provide some fall backs for common kinds that are close enough.
case SymbolKind::Struct:
return SymbolKind::Class;
case SymbolKind::EnumMember:
return SymbolKind::Enum;
default:
return SymbolKind::String;
}
}
SymbolKind indexSymbolKindToSymbolKind(const index::SymbolInfo &Info) {
switch (Info.Kind) {
// FIXME: for backwards compatibility, the include directive kind is treated
// the same as Unknown
case index::SymbolKind::IncludeDirective:
case index::SymbolKind::Unknown:
return SymbolKind::Variable;
case index::SymbolKind::Module:
return SymbolKind::Module;
case index::SymbolKind::Namespace:
return SymbolKind::Namespace;
case index::SymbolKind::NamespaceAlias:
return SymbolKind::Namespace;
case index::SymbolKind::Macro:
return SymbolKind::String;
case index::SymbolKind::Enum:
return SymbolKind::Enum;
case index::SymbolKind::Struct:
return SymbolKind::Struct;
case index::SymbolKind::Class:
return SymbolKind::Class;
case index::SymbolKind::Protocol:
return SymbolKind::Interface;
case index::SymbolKind::Extension:
return SymbolKind::Interface;
case index::SymbolKind::Union:
return SymbolKind::Class;
case index::SymbolKind::TypeAlias: {
switch (Info.SubKind) {
case index::SymbolSubKind::UsingStruct:
return SymbolKind::Struct;
case index::SymbolSubKind::UsingClass:
return SymbolKind::Class;
default:
return SymbolKind::Class;
}
}
case index::SymbolKind::Function:
return SymbolKind::Function;
case index::SymbolKind::Variable:
return SymbolKind::Variable;
case index::SymbolKind::Field:
return SymbolKind::Field;
case index::SymbolKind::EnumConstant:
return SymbolKind::EnumMember;
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::ClassMethod:
case index::SymbolKind::StaticMethod:
return SymbolKind::Method;
case index::SymbolKind::InstanceProperty:
case index::SymbolKind::ClassProperty:
case index::SymbolKind::StaticProperty:
return SymbolKind::Property;
case index::SymbolKind::Constructor:
case index::SymbolKind::Destructor:
return SymbolKind::Constructor;
case index::SymbolKind::ConversionFunction:
return SymbolKind::Function;
case index::SymbolKind::Parameter:
case index::SymbolKind::NonTypeTemplateParm:
return SymbolKind::Variable;
case index::SymbolKind::Using:
return SymbolKind::Namespace;
case index::SymbolKind::TemplateTemplateParm:
case index::SymbolKind::TemplateTypeParm:
return SymbolKind::TypeParameter;
case index::SymbolKind::Concept:
return SymbolKind::Interface;
}
llvm_unreachable("invalid symbol kind");
}
bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
llvm::json::Path P) {
const llvm::json::Object *O = Params.getAsObject();
if (!O) {
P.report("expected object");
return false;
}
if (auto *TextDocument = O->getObject("textDocument")) {
if (auto *SemanticHighlighting =
TextDocument->getObject("semanticHighlightingCapabilities")) {
if (auto SemanticHighlightingSupport =
SemanticHighlighting->getBoolean("semanticHighlighting"))
R.TheiaSemanticHighlighting = *SemanticHighlightingSupport;
}
if (auto *InactiveRegions =
TextDocument->getObject("inactiveRegionsCapabilities")) {
if (auto InactiveRegionsSupport =
InactiveRegions->getBoolean("inactiveRegions")) {
R.InactiveRegions = *InactiveRegionsSupport;
}
}
if (TextDocument->getObject("semanticTokens"))
R.SemanticTokens = true;
if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) {
if (auto CategorySupport = Diagnostics->getBoolean("categorySupport"))
R.DiagnosticCategory = *CategorySupport;
if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline"))
R.DiagnosticFixes = *CodeActions;
if (auto RelatedInfo = Diagnostics->getBoolean("relatedInformation"))
R.DiagnosticRelatedInformation = *RelatedInfo;
}
if (auto *References = TextDocument->getObject("references"))
if (auto ContainerSupport = References->getBoolean("container"))
R.ReferenceContainer = *ContainerSupport;
if (auto *Completion = TextDocument->getObject("completion")) {
if (auto *Item = Completion->getObject("completionItem")) {
if (auto SnippetSupport = Item->getBoolean("snippetSupport"))
R.CompletionSnippets = *SnippetSupport;
if (auto LabelDetailsSupport = Item->getBoolean("labelDetailsSupport"))
R.CompletionLabelDetail = *LabelDetailsSupport;
if (const auto *DocumentationFormat =
Item->getArray("documentationFormat")) {
for (const auto &Format : *DocumentationFormat) {
if (fromJSON(Format, R.CompletionDocumentationFormat, P))
break;
}
}
}
if (auto *ItemKind = Completion->getObject("completionItemKind")) {
if (auto *ValueSet = ItemKind->get("valueSet")) {
R.CompletionItemKinds.emplace();
if (!fromJSON(*ValueSet, *R.CompletionItemKinds,
P.field("textDocument")
.field("completion")
.field("completionItemKind")
.field("valueSet")))
return false;
}
}
if (auto EditsNearCursor = Completion->getBoolean("editsNearCursor"))
R.CompletionFixes = *EditsNearCursor;
}
if (auto *CodeAction = TextDocument->getObject("codeAction")) {
if (CodeAction->getObject("codeActionLiteralSupport"))
R.CodeActionStructure = true;
}
if (auto *DocumentSymbol = TextDocument->getObject("documentSymbol")) {
if (auto HierarchicalSupport =
DocumentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
R.HierarchicalDocumentSymbol = *HierarchicalSupport;
}
if (auto *Hover = TextDocument->getObject("hover")) {
if (auto *ContentFormat = Hover->getArray("contentFormat")) {
for (const auto &Format : *ContentFormat) {
if (fromJSON(Format, R.HoverContentFormat, P))
break;
}
}
}
if (auto *Help = TextDocument->getObject("signatureHelp")) {
R.HasSignatureHelp = true;
if (auto *Info = Help->getObject("signatureInformation")) {
if (auto *Parameter = Info->getObject("parameterInformation")) {
if (auto OffsetSupport = Parameter->getBoolean("labelOffsetSupport"))
R.OffsetsInSignatureHelp = *OffsetSupport;
}
if (const auto *DocumentationFormat =
Info->getArray("documentationFormat")) {
for (const auto &Format : *DocumentationFormat) {
if (fromJSON(Format, R.SignatureHelpDocumentationFormat, P))
break;
}
}
}
}
if (auto *Folding = TextDocument->getObject("foldingRange")) {
if (auto LineFolding = Folding->getBoolean("lineFoldingOnly"))
R.LineFoldingOnly = *LineFolding;
}
if (auto *Rename = TextDocument->getObject("rename")) {
if (auto RenameSupport = Rename->getBoolean("prepareSupport"))
R.RenamePrepareSupport = *RenameSupport;
}
}
if (auto *Workspace = O->getObject("workspace")) {
if (auto *Symbol = Workspace->getObject("symbol")) {
if (auto *SymbolKind = Symbol->getObject("symbolKind")) {
if (auto *ValueSet = SymbolKind->get("valueSet")) {
R.WorkspaceSymbolKinds.emplace();
if (!fromJSON(*ValueSet, *R.WorkspaceSymbolKinds,
P.field("workspace")
.field("symbol")
.field("symbolKind")
.field("valueSet")))
return false;
}
}
}
if (auto *SemanticTokens = Workspace->getObject("semanticTokens")) {
if (auto RefreshSupport = SemanticTokens->getBoolean("refreshSupport"))
R.SemanticTokenRefreshSupport = *RefreshSupport;
}
if (auto *WorkspaceEdit = Workspace->getObject("workspaceEdit")) {
if (auto DocumentChanges = WorkspaceEdit->getBoolean("documentChanges"))
R.DocumentChanges = *DocumentChanges;
if (WorkspaceEdit->getObject("changeAnnotationSupport")) {
R.ChangeAnnotation = true;
}
}
}
if (auto *Window = O->getObject("window")) {
if (auto WorkDoneProgress = Window->getBoolean("workDoneProgress"))
R.WorkDoneProgress = *WorkDoneProgress;
if (auto Implicit = Window->getBoolean("implicitWorkDoneProgressCreate"))
R.ImplicitProgressCreation = *Implicit;
}
if (auto *General = O->getObject("general")) {
if (auto *StaleRequestSupport = General->getObject("staleRequestSupport")) {
if (auto Cancel = StaleRequestSupport->getBoolean("cancel"))
R.CancelsStaleRequests = *Cancel;
}
if (auto *PositionEncodings = General->get("positionEncodings")) {
R.PositionEncodings.emplace();
if (!fromJSON(*PositionEncodings, *R.PositionEncodings,
P.field("general").field("positionEncodings")))
return false;
}
}
if (auto *OffsetEncoding = O->get("offsetEncoding")) {
R.PositionEncodings.emplace();
elog("offsetEncoding capability is a deprecated clangd extension that'll "
"go away with clangd 23. Migrate to standard positionEncodings "
"capability introduced by LSP 3.17");
if (!fromJSON(*OffsetEncoding, *R.PositionEncodings,
P.field("offsetEncoding")))
return false;
}
if (auto *Experimental = O->getObject("experimental")) {
if (auto *TextDocument = Experimental->getObject("textDocument")) {
if (auto *Completion = TextDocument->getObject("completion")) {
if (auto EditsNearCursor = Completion->getBoolean("editsNearCursor"))
R.CompletionFixes |= *EditsNearCursor;
}
if (auto *References = TextDocument->getObject("references")) {
if (auto ContainerSupport = References->getBoolean("container")) {
R.ReferenceContainer |= *ContainerSupport;
}
}
if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) {
if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline")) {
R.DiagnosticFixes |= *CodeActions;
}
}
if (auto *InactiveRegions =
TextDocument->getObject("inactiveRegionsCapabilities")) {
if (auto InactiveRegionsSupport =
InactiveRegions->getBoolean("inactiveRegions")) {
R.InactiveRegions |= *InactiveRegionsSupport;
}
}
}
if (auto *Window = Experimental->getObject("window")) {
if (auto Implicit =
Window->getBoolean("implicitWorkDoneProgressCreate")) {
R.ImplicitProgressCreation |= *Implicit;
}
}
if (auto *OffsetEncoding = Experimental->get("offsetEncoding")) {
R.PositionEncodings.emplace();
elog("offsetEncoding capability is a deprecated clangd extension that'll "
"go away with clangd 23. Migrate to standard positionEncodings "
"capability introduced by LSP 3.17");
if (!fromJSON(*OffsetEncoding, *R.PositionEncodings,
P.field("offsetEncoding")))
return false;
}
}
return true;
}
bool fromJSON(const llvm::json::Value &Params, InitializeParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O)
return false;
// We deliberately don't fail if we can't parse individual fields.
// Failing to handle a slightly malformed initialize would be a disaster.
O.map("processId", R.processId);
O.map("rootUri", R.rootUri);
O.map("rootPath", R.rootPath);
O.map("capabilities", R.capabilities);
if (auto *RawCaps = Params.getAsObject()->getObject("capabilities"))
R.rawCapabilities = *RawCaps;
O.map("trace", R.trace);
O.map("initializationOptions", R.initializationOptions);
return true;
}
llvm::json::Value toJSON(const WorkDoneProgressCreateParams &P) {
return llvm::json::Object{{"token", P.token}};
}
llvm::json::Value toJSON(const WorkDoneProgressBegin &P) {
llvm::json::Object Result{
{"kind", "begin"},
{"title", P.title},
};
if (P.cancellable)
Result["cancellable"] = true;
if (P.percentage)
Result["percentage"] = 0;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}
llvm::json::Value toJSON(const WorkDoneProgressReport &P) {
llvm::json::Object Result{{"kind", "report"}};
if (P.cancellable)
Result["cancellable"] = *P.cancellable;
if (P.message)
Result["message"] = *P.message;
if (P.percentage)
Result["percentage"] = *P.percentage;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}
llvm::json::Value toJSON(const WorkDoneProgressEnd &P) {
llvm::json::Object Result{{"kind", "end"}};
if (P.message)
Result["message"] = *P.message;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}
llvm::json::Value toJSON(const MessageType &R) {
return static_cast<int64_t>(R);
}
llvm::json::Value toJSON(const ShowMessageParams &R) {
return llvm::json::Object{{"type", R.type}, {"message", R.message}};
}
bool fromJSON(const llvm::json::Value &Params, DidOpenTextDocumentParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
bool fromJSON(const llvm::json::Value &Params, DidCloseTextDocumentParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("contentChanges", R.contentChanges) &&
O.map("wantDiagnostics", R.wantDiagnostics) &&
mapOptOrNull(Params, "forceRebuild", R.forceRebuild, P);
}
bool fromJSON(const llvm::json::Value &E, FileChangeType &Out,
llvm::json::Path P) {
if (auto T = E.getAsInteger()) {
if (*T < static_cast<int>(FileChangeType::Created) ||
*T > static_cast<int>(FileChangeType::Deleted))
return false;
Out = static_cast<FileChangeType>(*T);
return true;
}
return false;
}
bool fromJSON(const llvm::json::Value &Params, FileEvent &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("uri", R.uri) && O.map("type", R.type);
}
bool fromJSON(const llvm::json::Value &Params, DidChangeWatchedFilesParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("changes", R.changes);
}
bool fromJSON(const llvm::json::Value &Params,
TextDocumentContentChangeEvent &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("range", R.range) && O.map("rangeLength", R.rangeLength) &&
O.map("text", R.text);
}
bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
;
}
bool fromJSON(const llvm::json::Value &Params,
DocumentRangesFormattingParams &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("ranges", R.ranges);
;
}
bool fromJSON(const llvm::json::Value &Params,
DocumentOnTypeFormattingParams &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("ch", R.ch);
}
bool fromJSON(const llvm::json::Value &Params, DocumentFormattingParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
bool fromJSON(const llvm::json::Value &Params, DocumentSymbolParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
llvm::json::Value toJSON(const DiagnosticRelatedInformation &DRI) {
return llvm::json::Object{
{"location", DRI.location},
{"message", DRI.message},
};
}
llvm::json::Value toJSON(DiagnosticTag Tag) { return static_cast<int>(Tag); }
llvm::json::Value toJSON(const CodeDescription &D) {
return llvm::json::Object{{"href", D.href}};
}
llvm::json::Value toJSON(const Diagnostic &D) {
llvm::json::Object Diag{
{"range", D.range},
{"severity", D.severity},
{"message", D.message},
};
if (D.category)
Diag["category"] = *D.category;
if (D.codeActions)
Diag["codeActions"] = D.codeActions;
if (!D.code.empty())
Diag["code"] = D.code;
if (D.codeDescription)
Diag["codeDescription"] = *D.codeDescription;
if (!D.source.empty())
Diag["source"] = D.source;
if (D.relatedInformation)
Diag["relatedInformation"] = *D.relatedInformation;
if (!D.data.empty())
Diag["data"] = llvm::json::Object(D.data);
if (!D.tags.empty())
Diag["tags"] = llvm::json::Array{D.tags};
// FIXME: workaround for older gcc/clang
return std::move(Diag);
}
bool fromJSON(const llvm::json::Value &Params, Diagnostic &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O)
return false;
if (auto *Data = Params.getAsObject()->getObject("data"))
R.data = *Data;
return O.map("range", R.range) && O.map("message", R.message) &&
mapOptOrNull(Params, "severity", R.severity, P) &&
mapOptOrNull(Params, "category", R.category, P) &&
mapOptOrNull(Params, "code", R.code, P) &&
mapOptOrNull(Params, "source", R.source, P);
}
llvm::json::Value toJSON(const PublishDiagnosticsParams &PDP) {
llvm::json::Object Result{
{"uri", PDP.uri},
{"diagnostics", PDP.diagnostics},
};
if (PDP.version)
Result["version"] = PDP.version;
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params, CodeActionContext &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O || !O.map("diagnostics", R.diagnostics))
return false;
O.map("only", R.only);
return true;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) {
OS << D.range << " [";
switch (D.severity) {
case 1:
OS << "error";
break;
case 2:
OS << "warning";
break;
case 3:
OS << "note";
break;
case 4:
OS << "remark";
break;
default:
OS << "diagnostic";
break;
}
return OS << '(' << D.severity << "): " << D.message << "]";
}
bool fromJSON(const llvm::json::Value &Params, CodeActionParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("range", R.range) && O.map("context", R.context);
}
bool fromJSON(const llvm::json::Value &Params, WorkspaceEdit &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("changes", R.changes) &&
O.map("documentChanges", R.documentChanges) &&
O.mapOptional("changeAnnotations", R.changeAnnotations);
}
bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O || !O.map("command", R.command))
return false;
const auto *Args = Params.getAsObject()->get("arguments");
if (!Args)
return true; // Missing args is ok, argument is null.
const auto *ArgsArray = Args->getAsArray();
if (!ArgsArray) {
P.field("arguments").report("expected array");
return false;
}
if (ArgsArray->size() > 1) {
P.field("arguments").report("Command should have 0 or 1 argument");
return false;
}
if (ArgsArray->size() == 1) {
R.argument = ArgsArray->front();
}
return true;
}
llvm::json::Value toJSON(const SymbolInformation &P) {
llvm::json::Object O{
{"name", P.name},
{"kind", static_cast<int>(P.kind)},
{"location", P.location},
{"containerName", P.containerName},
};
if (P.score)
O["score"] = *P.score;
return std::move(O);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const SymbolInformation &SI) {
O << SI.containerName << "::" << SI.name << " - " << toJSON(SI);
return O;
}
bool operator==(const SymbolDetails &LHS, const SymbolDetails &RHS) {
return LHS.name == RHS.name && LHS.containerName == RHS.containerName &&
LHS.USR == RHS.USR && LHS.ID == RHS.ID &&
LHS.declarationRange == RHS.declarationRange &&
LHS.definitionRange == RHS.definitionRange;
}
llvm::json::Value toJSON(const SymbolDetails &P) {
llvm::json::Object Result{{"name", llvm::json::Value(nullptr)},
{"containerName", llvm::json::Value(nullptr)},
{"usr", llvm::json::Value(nullptr)},
{"id", llvm::json::Value(nullptr)}};
if (!P.name.empty())
Result["name"] = P.name;
if (!P.containerName.empty())
Result["containerName"] = P.containerName;
if (!P.USR.empty())
Result["usr"] = P.USR;
if (P.ID)
Result["id"] = P.ID.str();
if (P.declarationRange)
Result["declarationRange"] = *P.declarationRange;
if (P.definitionRange)
Result["definitionRange"] = *P.definitionRange;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const SymbolDetails &S) {
if (!S.containerName.empty()) {
O << S.containerName;
llvm::StringRef ContNameRef;
if (!ContNameRef.ends_with("::")) {
O << " ";
}
}
O << S.name << " - " << toJSON(S);
return O;
}
bool fromJSON(const llvm::json::Value &Params, WorkspaceSymbolParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("query", R.query) &&
mapOptOrNull(Params, "limit", R.limit, P);
}
llvm::json::Value toJSON(const Command &C) {
auto Cmd = llvm::json::Object{{"title", C.title}, {"command", C.command}};
if (!C.argument.getAsNull())
Cmd["arguments"] = llvm::json::Array{C.argument};
return std::move(Cmd);
}
const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix";
const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor";
const llvm::StringLiteral CodeAction::INFO_KIND = "info";
llvm::json::Value toJSON(const CodeAction &CA) {
auto CodeAction = llvm::json::Object{{"title", CA.title}};
if (CA.kind)
CodeAction["kind"] = *CA.kind;
if (CA.diagnostics)
CodeAction["diagnostics"] = llvm::json::Array(*CA.diagnostics);
if (CA.isPreferred)
CodeAction["isPreferred"] = true;
if (CA.edit)
CodeAction["edit"] = *CA.edit;
if (CA.command)
CodeAction["command"] = *CA.command;
return std::move(CodeAction);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S) {
return O << S.name << " - " << toJSON(S);
}
llvm::json::Value toJSON(const DocumentSymbol &S) {
llvm::json::Object Result{{"name", S.name},
{"kind", static_cast<int>(S.kind)},
{"range", S.range},
{"selectionRange", S.selectionRange}};
if (!S.detail.empty())
Result["detail"] = S.detail;
if (!S.children.empty())
Result["children"] = S.children;
if (S.deprecated)
Result["deprecated"] = true;
if (!S.tags.empty())
Result["tags"] = S.tags;
// FIXME: workaround for older gcc/clang
return std::move(Result);
}
llvm::json::Value toJSON(const WorkspaceEdit &WE) {
llvm::json::Object Result;
if (WE.changes) {
llvm::json::Object FileChanges;
for (auto &Change : *WE.changes)
FileChanges[Change.first] = llvm::json::Array(Change.second);
Result["changes"] = std::move(FileChanges);
}
if (WE.documentChanges)
Result["documentChanges"] = *WE.documentChanges;
if (!WE.changeAnnotations.empty()) {
llvm::json::Object ChangeAnnotations;
for (auto &Annotation : WE.changeAnnotations)
ChangeAnnotations[Annotation.first] = Annotation.second;
Result["changeAnnotations"] = std::move(ChangeAnnotations);
}
return Result;
}
bool fromJSON(const llvm::json::Value &Params, TweakArgs &A,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("file", A.file) && O.map("selection", A.selection) &&
O.map("tweakID", A.tweakID);
}
llvm::json::Value toJSON(const TweakArgs &A) {
return llvm::json::Object{
{"tweakID", A.tweakID}, {"selection", A.selection}, {"file", A.file}};
}
llvm::json::Value toJSON(const ApplyWorkspaceEditParams &Params) {
return llvm::json::Object{{"edit", Params.edit}};
}
bool fromJSON(const llvm::json::Value &Response, ApplyWorkspaceEditResponse &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Response, P);
return O && O.map("applied", R.applied) &&
O.map("failureReason", R.failureReason);
}
bool fromJSON(const llvm::json::Value &Params, TextDocumentPositionParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position);
}
bool fromJSON(const llvm::json::Value &Params, CompletionContext &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
int TriggerKind;
if (!O || !O.map("triggerKind", TriggerKind) ||
!mapOptOrNull(Params, "triggerCharacter", R.triggerCharacter, P))
return false;
R.triggerKind = static_cast<CompletionTriggerKind>(TriggerKind);
return true;
}
bool fromJSON(const llvm::json::Value &Params, CompletionParams &R,
llvm::json::Path P) {
if (!fromJSON(Params, static_cast<TextDocumentPositionParams &>(R), P) ||
!mapOptOrNull(Params, "limit", R.limit, P))
return false;
if (auto *Context = Params.getAsObject()->get("context"))
return fromJSON(*Context, R.context, P.field("context"));
return true;
}
static llvm::StringRef toTextKind(MarkupKind Kind) {
switch (Kind) {
case MarkupKind::PlainText:
return "plaintext";
case MarkupKind::Markdown:
return "markdown";
}
llvm_unreachable("Invalid MarkupKind");
}
bool fromJSON(const llvm::json::Value &V, MarkupKind &K, llvm::json::Path P) {
auto Str = V.getAsString();
if (!Str) {
P.report("expected string");
return false;
}
if (*Str == "plaintext")
K = MarkupKind::PlainText;
else if (*Str == "markdown")
K = MarkupKind::Markdown;
else {
P.report("unknown markup kind");
return false;
}
return true;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind K) {
return OS << toTextKind(K);
}
llvm::json::Value toJSON(const MarkupContent &MC) {
if (MC.value.empty())
return nullptr;
return llvm::json::Object{
{"kind", toTextKind(MC.kind)},
{"value", MC.value},
};
}
llvm::json::Value toJSON(const Hover &H) {
llvm::json::Object Result{{"contents", toJSON(H.contents)}};
if (H.range)
Result["range"] = toJSON(*H.range);
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &E, CompletionItemKind &Out,
llvm::json::Path P) {
if (auto T = E.getAsInteger()) {
if (*T < static_cast<int>(CompletionItemKind::Text) ||
*T > static_cast<int>(CompletionItemKind::TypeParameter))
return false;
Out = static_cast<CompletionItemKind>(*T);
return true;
}
return false;
}
CompletionItemKind
adjustKindToCapability(CompletionItemKind Kind,
CompletionItemKindBitset &SupportedCompletionItemKinds) {
auto KindVal = static_cast<size_t>(Kind);
if (KindVal >= CompletionItemKindMin &&
KindVal <= SupportedCompletionItemKinds.size() &&
SupportedCompletionItemKinds[KindVal])
return Kind;
switch (Kind) {
// Provide some fall backs for common kinds that are close enough.
case CompletionItemKind::Folder:
return CompletionItemKind::File;
case CompletionItemKind::EnumMember:
return CompletionItemKind::Enum;
case CompletionItemKind::Struct:
return CompletionItemKind::Class;
default:
return CompletionItemKind::Text;
}
}
bool fromJSON(const llvm::json::Value &E, CompletionItemKindBitset &Out,
llvm::json::Path P) {
if (auto *A = E.getAsArray()) {
for (size_t I = 0; I < A->size(); ++I) {
CompletionItemKind KindOut;
if (fromJSON((*A)[I], KindOut, P.index(I)))
Out.set(size_t(KindOut));
}
return true;
}
return false;
}
llvm::json::Value toJSON(const CompletionItemLabelDetails &CD) {
llvm::json::Object Result;
if (!CD.detail.empty())
Result["detail"] = CD.detail;
if (!CD.description.empty())
Result["description"] = CD.description;
return Result;
}
void removeCompletionLabelDetails(CompletionItem &C) {
if (!C.labelDetails)
return;
if (!C.labelDetails->detail.empty())
C.label += C.labelDetails->detail;
if (!C.labelDetails->description.empty())
C.label = C.labelDetails->description + C.label;
C.labelDetails.reset();
}
llvm::json::Value toJSON(const CompletionItem &CI) {
assert(!CI.label.empty() && "completion item label is required");
llvm::json::Object Result{{"label", CI.label}};
if (CI.kind != CompletionItemKind::Missing)
Result["kind"] = static_cast<int>(CI.kind);
if (!CI.detail.empty())
Result["detail"] = CI.detail;
if (CI.labelDetails)
Result["labelDetails"] = *CI.labelDetails;
if (CI.documentation)
Result["documentation"] = CI.documentation;
if (!CI.sortText.empty())
Result["sortText"] = CI.sortText;
if (!CI.filterText.empty())
Result["filterText"] = CI.filterText;
if (!CI.insertText.empty())
Result["insertText"] = CI.insertText;
if (CI.insertTextFormat != InsertTextFormat::Missing)
Result["insertTextFormat"] = static_cast<int>(CI.insertTextFormat);
if (CI.textEdit)
Result["textEdit"] = *CI.textEdit;
if (!CI.additionalTextEdits.empty())
Result["additionalTextEdits"] = llvm::json::Array(CI.additionalTextEdits);
if (CI.deprecated)
Result["deprecated"] = CI.deprecated;
Result["score"] = CI.score;
return std::move(Result);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const CompletionItem &I) {
O << I.label << " - " << toJSON(I);
return O;
}
bool operator<(const CompletionItem &L, const CompletionItem &R) {
return (L.sortText.empty() ? L.label : L.sortText) <
(R.sortText.empty() ? R.label : R.sortText);
}
llvm::json::Value toJSON(const CompletionList &L) {
return llvm::json::Object{
{"isIncomplete", L.isIncomplete},
{"items", llvm::json::Array(L.items)},
};
}
llvm::json::Value toJSON(const ParameterInformation &PI) {
assert((PI.labelOffsets || !PI.labelString.empty()) &&
"parameter information label is required");
llvm::json::Object Result;
if (PI.labelOffsets)
Result["label"] =
llvm::json::Array({PI.labelOffsets->first, PI.labelOffsets->second});
else
Result["label"] = PI.labelString;
if (!PI.documentation.empty())
Result["documentation"] = PI.documentation;
return std::move(Result);
}
llvm::json::Value toJSON(const SignatureInformation &SI) {
assert(!SI.label.empty() && "signature information label is required");
llvm::json::Object Result{
{"label", SI.label},
{"parameters", llvm::json::Array(SI.parameters)},
};
if (!SI.documentation.value.empty())
Result["documentation"] = SI.documentation;
return std::move(Result);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const SignatureInformation &I) {
O << I.label << " - " << toJSON(I);
return O;
}
llvm::json::Value toJSON(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");
return llvm::json::Object{
{"activeSignature", SH.activeSignature},
{"activeParameter", SH.activeParameter},
{"signatures", llvm::json::Array(SH.signatures)},
};
}
bool fromJSON(const llvm::json::Value &Params, RenameParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("newName", R.newName);
}
llvm::json::Value toJSON(const RenameParams &R) {
return llvm::json::Object{
{"textDocument", R.textDocument},
{"position", R.position},
{"newName", R.newName},
};
}
llvm::json::Value toJSON(const PrepareRenameResult &PRR) {
if (PRR.placeholder.empty())
return toJSON(PRR.range);
return llvm::json::Object{
{"range", toJSON(PRR.range)},
{"placeholder", PRR.placeholder},
};
}
llvm::json::Value toJSON(const DocumentHighlight &DH) {
return llvm::json::Object{
{"range", toJSON(DH.range)},
{"kind", static_cast<int>(DH.kind)},
};
}
llvm::json::Value toJSON(const FileStatus &FStatus) {
return llvm::json::Object{
{"uri", FStatus.uri},
{"state", FStatus.state},
};
}
constexpr unsigned SemanticTokenEncodingSize = 5;
static llvm::json::Value encodeTokens(llvm::ArrayRef<SemanticToken> Toks) {
llvm::json::Array Result;
Result.reserve(SemanticTokenEncodingSize * Toks.size());
for (const auto &Tok : Toks) {
Result.push_back(Tok.deltaLine);
Result.push_back(Tok.deltaStart);
Result.push_back(Tok.length);
Result.push_back(Tok.tokenType);
Result.push_back(Tok.tokenModifiers);
}
assert(Result.size() == SemanticTokenEncodingSize * Toks.size());
return std::move(Result);
}
bool operator==(const SemanticToken &L, const SemanticToken &R) {
return std::tie(L.deltaLine, L.deltaStart, L.length, L.tokenType,
L.tokenModifiers) == std::tie(R.deltaLine, R.deltaStart,
R.length, R.tokenType,
R.tokenModifiers);
}
llvm::json::Value toJSON(const SemanticTokens &Tokens) {
return llvm::json::Object{{"resultId", Tokens.resultId},
{"data", encodeTokens(Tokens.tokens)}};
}
llvm::json::Value toJSON(const SemanticTokensEdit &Edit) {
return llvm::json::Object{
{"start", SemanticTokenEncodingSize * Edit.startToken},
{"deleteCount", SemanticTokenEncodingSize * Edit.deleteTokens},
{"data", encodeTokens(Edit.tokens)}};
}
llvm::json::Value toJSON(const SemanticTokensOrDelta &TE) {
llvm::json::Object Result{{"resultId", TE.resultId}};
if (TE.edits)
Result["edits"] = *TE.edits;
if (TE.tokens)
Result["data"] = encodeTokens(*TE.tokens);
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params, SemanticTokensParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
bool fromJSON(const llvm::json::Value &Params, SemanticTokensDeltaParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("previousResultId", R.previousResultId);
}
llvm::json::Value toJSON(const InactiveRegionsParams &InactiveRegions) {
return llvm::json::Object{
{"textDocument", InactiveRegions.TextDocument},
{"regions", std::move(InactiveRegions.InactiveRegions)}};
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const DocumentHighlight &V) {
O << V.range;
if (V.kind == DocumentHighlightKind::Read)
O << "(r)";
if (V.kind == DocumentHighlightKind::Write)
O << "(w)";
return O;
}
bool fromJSON(const llvm::json::Value &Params,
DidChangeConfigurationParams &CCP, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("settings", CCP.settings);
}
bool fromJSON(const llvm::json::Value &Params, ClangdCompileCommand &CDbUpdate,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("workingDirectory", CDbUpdate.workingDirectory) &&
O.map("compilationCommand", CDbUpdate.compilationCommand);
}
bool fromJSON(const llvm::json::Value &Params, ConfigurationSettings &S,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O)
return true; // 'any' type in LSP.
return mapOptOrNull(Params, "compilationDatabaseChanges",
S.compilationDatabaseChanges, P);
}
bool fromJSON(const llvm::json::Value &Params, InitializationOptions &Opts,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
if (!O)
return true; // 'any' type in LSP.
return fromJSON(Params, Opts.ConfigSettings, P) &&
O.map("compilationDatabasePath", Opts.compilationDatabasePath) &&
mapOptOrNull(Params, "fallbackFlags", Opts.fallbackFlags, P) &&
mapOptOrNull(Params, "clangdFileStatus", Opts.FileStatus, P);
}
bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
llvm::json::Path P) {
auto T = E.getAsInteger();
if (!T)
return false;
if (*T < static_cast<int>(TypeHierarchyDirection::Children) ||
*T > static_cast<int>(TypeHierarchyDirection::Both))
return false;
Out = static_cast<TypeHierarchyDirection>(*T);
return true;
}
bool fromJSON(const llvm::json::Value &Params, TypeHierarchyPrepareParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) &&
mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const TypeHierarchyItem &I) {
return O << I.name << " - " << toJSON(I);
}
llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &RP) {
llvm::json::Object Result{{"symbolID", RP.symbolID}};
if (RP.parents)
Result["parents"] = RP.parents;
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params,
TypeHierarchyItem::ResolveParams &RP, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("symbolID", RP.symbolID) &&
mapOptOrNull(Params, "parents", RP.parents, P);
}
llvm::json::Value toJSON(const TypeHierarchyItem &I) {
llvm::json::Object Result{
{"name", I.name}, {"kind", static_cast<int>(I.kind)},
{"range", I.range}, {"selectionRange", I.selectionRange},
{"uri", I.uri}, {"data", I.data},
};
if (I.detail)
Result["detail"] = I.detail;
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
// Required fields.
return O && O.map("name", I.name) && O.map("kind", I.kind) &&
O.map("uri", I.uri) && O.map("range", I.range) &&
O.map("selectionRange", I.selectionRange) &&
mapOptOrNull(Params, "detail", I.detail, P) &&
mapOptOrNull(Params, "deprecated", I.deprecated, P) &&
mapOptOrNull(Params, "parents", I.parents, P) &&
mapOptOrNull(Params, "children", I.children, P) &&
mapOptOrNull(Params, "data", I.data, P);
}
bool fromJSON(const llvm::json::Value &Params,
ResolveTypeHierarchyItemParams &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("item", R.item) &&
mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
}
bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.mapOptional("includeDeclaration", R.includeDeclaration);
}
bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R,
llvm::json::Path P) {
TextDocumentPositionParams &Base = R;
llvm::json::ObjectMapper O(Params, P);
return fromJSON(Params, Base, P) && O && O.mapOptional("context", R.context);
}
llvm::json::Value toJSON(SymbolTag Tag) {
return llvm::json::Value(static_cast<int>(Tag));
}
llvm::json::Value toJSON(const CallHierarchyItem &I) {
llvm::json::Object Result{{"name", I.name},
{"kind", static_cast<int>(I.kind)},
{"range", I.range},
{"selectionRange", I.selectionRange},
{"uri", I.uri}};
if (!I.tags.empty())
Result["tags"] = I.tags;
if (!I.detail.empty())
Result["detail"] = I.detail;
if (!I.data.empty())
Result["data"] = I.data;
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params, CallHierarchyItem &I,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
// Populate the required fields only. We don't care about the
// optional fields `Tags` and `Detail` for the purpose of
// client --> server communication.
return O && O.map("name", I.name) && O.map("kind", I.kind) &&
O.map("uri", I.uri) && O.map("range", I.range) &&
O.map("selectionRange", I.selectionRange) &&
mapOptOrNull(Params, "data", I.data, P);
}
bool fromJSON(const llvm::json::Value &Params,
CallHierarchyIncomingCallsParams &C, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("item", C.item);
}
llvm::json::Value toJSON(const CallHierarchyIncomingCall &C) {
return llvm::json::Object{{"from", C.from}, {"fromRanges", C.fromRanges}};
}
bool fromJSON(const llvm::json::Value &Params,
CallHierarchyOutgoingCallsParams &C, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("item", C.item);
}
llvm::json::Value toJSON(const CallHierarchyOutgoingCall &C) {
return llvm::json::Object{{"to", C.to}, {"fromRanges", C.fromRanges}};
}
bool fromJSON(const llvm::json::Value &Params, InlayHintsParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
}
llvm::json::Value toJSON(const InlayHintKind &Kind) {
switch (Kind) {
case InlayHintKind::Type:
return 1;
case InlayHintKind::Parameter:
return 2;
case InlayHintKind::Designator:
case InlayHintKind::BlockEnd:
case InlayHintKind::DefaultArgument:
// This is an extension, don't serialize.
return nullptr;
}
llvm_unreachable("Unknown clang.clangd.InlayHintKind");
}
llvm::json::Value toJSON(const InlayHint &H) {
llvm::json::Object Result{{"position", H.position},
{"label", H.label},
{"paddingLeft", H.paddingLeft},
{"paddingRight", H.paddingRight}};
auto K = toJSON(H.kind);
if (!K.getAsNull())
Result["kind"] = std::move(K);
return std::move(Result);
}
bool operator==(const InlayHint &A, const InlayHint &B) {
return std::tie(A.position, A.range, A.kind, A.label) ==
std::tie(B.position, B.range, B.kind, B.label);
}
bool operator<(const InlayHint &A, const InlayHint &B) {
return std::tie(A.position, A.range, A.kind, A.label) <
std::tie(B.position, B.range, B.kind, B.label);
}
std::string InlayHint::joinLabels() const {
return llvm::join(llvm::map_range(label, [](auto &L) { return L.value; }),
"");
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, InlayHintKind Kind) {
auto ToString = [](InlayHintKind K) {
switch (K) {
case InlayHintKind::Parameter:
return "parameter";
case InlayHintKind::Type:
return "type";
case InlayHintKind::Designator:
return "designator";
case InlayHintKind::BlockEnd:
return "block-end";
case InlayHintKind::DefaultArgument:
return "default-argument";
}
llvm_unreachable("Unknown clang.clangd.InlayHintKind");
};
return OS << ToString(Kind);
}
llvm::json::Value toJSON(const InlayHintLabelPart &L) {
llvm::json::Object Result{{"value", L.value}};
if (L.tooltip)
Result["tooltip"] = *L.tooltip;
if (L.location)
Result["location"] = *L.location;
if (L.command)
Result["command"] = *L.command;
return Result;
}
bool operator==(const InlayHintLabelPart &LHS, const InlayHintLabelPart &RHS) {
return std::tie(LHS.value, LHS.location) == std::tie(RHS.value, RHS.location);
}
bool operator<(const InlayHintLabelPart &LHS, const InlayHintLabelPart &RHS) {
return std::tie(LHS.value, LHS.location) < std::tie(RHS.value, RHS.location);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const InlayHintLabelPart &L) {
OS << L.value;
if (L.location)
OS << " (" << L.location << ")";
return OS;
}
static const char *toString(OffsetEncoding OE) {
switch (OE) {
case OffsetEncoding::UTF8:
return "utf-8";
case OffsetEncoding::UTF16:
return "utf-16";
case OffsetEncoding::UTF32:
return "utf-32";
case OffsetEncoding::UnsupportedEncoding:
return "unknown";
}
llvm_unreachable("Unknown clang.clangd.OffsetEncoding");
}
llvm::json::Value toJSON(const OffsetEncoding &OE) { return toString(OE); }
bool fromJSON(const llvm::json::Value &V, OffsetEncoding &OE,
llvm::json::Path P) {
auto Str = V.getAsString();
if (!Str)
return false;
OE = llvm::StringSwitch<OffsetEncoding>(*Str)
.Case("utf-8", OffsetEncoding::UTF8)
.Case("utf-16", OffsetEncoding::UTF16)
.Case("utf-32", OffsetEncoding::UTF32)
.Default(OffsetEncoding::UnsupportedEncoding);
return true;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OffsetEncoding Enc) {
return OS << toString(Enc);
}
bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", S.textDocument) &&
O.map("positions", S.positions);
}
llvm::json::Value toJSON(const SelectionRange &Out) {
if (Out.parent) {
return llvm::json::Object{{"range", Out.range},
{"parent", toJSON(*Out.parent)}};
}
return llvm::json::Object{{"range", Out.range}};
}
bool fromJSON(const llvm::json::Value &Params, DocumentLinkParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
llvm::json::Value toJSON(const DocumentLink &DocumentLink) {
return llvm::json::Object{
{"range", DocumentLink.range},
{"target", DocumentLink.target},
};
}
bool fromJSON(const llvm::json::Value &Params, FoldingRangeParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument);
}
const llvm::StringLiteral FoldingRange::REGION_KIND = "region";
const llvm::StringLiteral FoldingRange::COMMENT_KIND = "comment";
const llvm::StringLiteral FoldingRange::IMPORT_KIND = "import";
llvm::json::Value toJSON(const FoldingRange &Range) {
llvm::json::Object Result{
{"startLine", Range.startLine},
{"endLine", Range.endLine},
};
if (Range.startCharacter)
Result["startCharacter"] = Range.startCharacter;
if (Range.endCharacter)
Result["endCharacter"] = Range.endCharacter;
if (!Range.kind.empty())
Result["kind"] = Range.kind;
return Result;
}
llvm::json::Value toJSON(const MemoryTree &MT) {
llvm::json::Object Out;
int64_t Total = MT.self();
Out["_self"] = Total;
for (const auto &Entry : MT.children()) {
auto Child = toJSON(Entry.getSecond());
Total += *Child.getAsObject()->getInteger("_total");
Out[Entry.first] = std::move(Child);
}
Out["_total"] = Total;
return Out;
}
bool fromJSON(const llvm::json::Value &Params, ASTParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
}
llvm::json::Value toJSON(const ASTNode &N) {
llvm::json::Object Result{
{"role", N.role},
{"kind", N.kind},
};
if (!N.children.empty())
Result["children"] = N.children;
if (!N.detail.empty())
Result["detail"] = N.detail;
if (!N.arcana.empty())
Result["arcana"] = N.arcana;
if (N.range)
Result["range"] = *N.range;
return Result;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) {
std::function<void(const ASTNode &, unsigned)> Print = [&](const ASTNode &N,
unsigned Level) {
OS.indent(2 * Level) << N.role << ": " << N.kind;
if (!N.detail.empty())
OS << " - " << N.detail;
OS << "\n";
for (const ASTNode &C : N.children)
Print(C, Level + 1);
};
Print(Root, 0);
return OS;
}
bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) {
auto Str = E.getAsString();
if (!Str) {
P.report("expected a string");
return false;
}
auto ID = SymbolID::fromStr(*Str);
if (!ID) {
elog("Malformed symbolid: {0}", ID.takeError());
P.report("malformed symbolid");
return false;
}
S = *ID;
return true;
}
llvm::json::Value toJSON(const SymbolID &S) { return S.str(); }
} // namespace clangd
} // namespace clang