llvm-project/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
Daniel Grumberg 1cfe1e732a [clang][ExtractAPI] Add queried symbol to parent contexts in libclang
Ensure that the current symbol is added to the parent contexts in the
output of libclang function for generating symbol graphs for single symbols.

Differential Revision: https://reviews.llvm.org/D147138
2023-03-29 16:32:26 +01:00

920 lines
31 KiB
C++

//===- ExtractAPI/Serialization/SymbolGraphSerializer.cpp -------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements the SymbolGraphSerializer.
///
//===----------------------------------------------------------------------===//
#include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Version.h"
#include "clang/ExtractAPI/API.h"
#include "clang/ExtractAPI/APIIgnoresList.h"
#include "clang/ExtractAPI/DeclarationFragments.h"
#include "clang/ExtractAPI/Serialization/SerializerBase.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VersionTuple.h"
#include <optional>
#include <type_traits>
using namespace clang;
using namespace clang::extractapi;
using namespace llvm;
using namespace llvm::json;
namespace {
/// Helper function to inject a JSON object \p Obj into another object \p Paren
/// at position \p Key.
void serializeObject(Object &Paren, StringRef Key, std::optional<Object> Obj) {
if (Obj)
Paren[Key] = std::move(*Obj);
}
/// Helper function to inject a JSON array \p Array into object \p Paren at
/// position \p Key.
void serializeArray(Object &Paren, StringRef Key, std::optional<Array> Array) {
if (Array)
Paren[Key] = std::move(*Array);
}
/// Serialize a \c VersionTuple \p V with the Symbol Graph semantic version
/// format.
///
/// A semantic version object contains three numeric fields, representing the
/// \c major, \c minor, and \c patch parts of the version tuple.
/// For example version tuple 1.0.3 is serialized as:
/// \code
/// {
/// "major" : 1,
/// "minor" : 0,
/// "patch" : 3
/// }
/// \endcode
///
/// \returns \c std::nullopt if the version \p V is empty, or an \c Object
/// containing the semantic version representation of \p V.
std::optional<Object> serializeSemanticVersion(const VersionTuple &V) {
if (V.empty())
return std::nullopt;
Object Version;
Version["major"] = V.getMajor();
Version["minor"] = V.getMinor().value_or(0);
Version["patch"] = V.getSubminor().value_or(0);
return Version;
}
/// Serialize the OS information in the Symbol Graph platform property.
///
/// The OS information in Symbol Graph contains the \c name of the OS, and an
/// optional \c minimumVersion semantic version field.
Object serializeOperatingSystem(const Triple &T) {
Object OS;
OS["name"] = T.getOSTypeName(T.getOS());
serializeObject(OS, "minimumVersion",
serializeSemanticVersion(T.getMinimumSupportedOSVersion()));
return OS;
}
/// Serialize the platform information in the Symbol Graph module section.
///
/// The platform object describes a target platform triple in corresponding
/// three fields: \c architecture, \c vendor, and \c operatingSystem.
Object serializePlatform(const Triple &T) {
Object Platform;
Platform["architecture"] = T.getArchName();
Platform["vendor"] = T.getVendorName();
Platform["operatingSystem"] = serializeOperatingSystem(T);
return Platform;
}
/// Serialize a source position.
Object serializeSourcePosition(const PresumedLoc &Loc) {
assert(Loc.isValid() && "invalid source position");
Object SourcePosition;
SourcePosition["line"] = Loc.getLine();
SourcePosition["character"] = Loc.getColumn();
return SourcePosition;
}
/// Serialize a source location in file.
///
/// \param Loc The presumed location to serialize.
/// \param IncludeFileURI If true, include the file path of \p Loc as a URI.
/// Defaults to false.
Object serializeSourceLocation(const PresumedLoc &Loc,
bool IncludeFileURI = false) {
Object SourceLocation;
serializeObject(SourceLocation, "position", serializeSourcePosition(Loc));
if (IncludeFileURI) {
std::string FileURI = "file://";
// Normalize file path to use forward slashes for the URI.
FileURI += sys::path::convert_to_slash(Loc.getFilename());
SourceLocation["uri"] = FileURI;
}
return SourceLocation;
}
/// Serialize a source range with begin and end locations.
Object serializeSourceRange(const PresumedLoc &BeginLoc,
const PresumedLoc &EndLoc) {
Object SourceRange;
serializeObject(SourceRange, "start", serializeSourcePosition(BeginLoc));
serializeObject(SourceRange, "end", serializeSourcePosition(EndLoc));
return SourceRange;
}
/// Serialize the availability attributes of a symbol.
///
/// Availability information contains the introduced, deprecated, and obsoleted
/// versions of the symbol for a given domain (roughly corresponds to a
/// platform) as semantic versions, if not default. Availability information
/// also contains flags to indicate if the symbol is unconditionally unavailable
/// or deprecated, i.e. \c __attribute__((unavailable)) and \c
/// __attribute__((deprecated)).
///
/// \returns \c std::nullopt if the symbol has default availability attributes,
/// or an \c Array containing the formatted availability information.
std::optional<Array>
serializeAvailability(const AvailabilitySet &Availabilities) {
if (Availabilities.isDefault())
return std::nullopt;
Array AvailabilityArray;
if (Availabilities.isUnconditionallyDeprecated()) {
Object UnconditionallyDeprecated;
UnconditionallyDeprecated["domain"] = "*";
UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true;
AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated));
}
// Note unconditionally unavailable records are skipped.
for (const auto &AvailInfo : Availabilities) {
Object Availability;
Availability["domain"] = AvailInfo.Domain;
if (AvailInfo.Unavailable)
Availability["isUnconditionallyUnavailable"] = true;
else {
serializeObject(Availability, "introducedVersion",
serializeSemanticVersion(AvailInfo.Introduced));
serializeObject(Availability, "deprecatedVersion",
serializeSemanticVersion(AvailInfo.Deprecated));
serializeObject(Availability, "obsoletedVersion",
serializeSemanticVersion(AvailInfo.Obsoleted));
}
AvailabilityArray.emplace_back(std::move(Availability));
}
return AvailabilityArray;
}
/// Get the language name string for interface language references.
StringRef getLanguageName(Language Lang) {
switch (Lang) {
case Language::C:
return "c";
case Language::ObjC:
return "objective-c";
// Unsupported language currently
case Language::CXX:
case Language::ObjCXX:
case Language::OpenCL:
case Language::OpenCLCXX:
case Language::CUDA:
case Language::RenderScript:
case Language::HIP:
case Language::HLSL:
// Languages that the frontend cannot parse and compile
case Language::Unknown:
case Language::Asm:
case Language::LLVM_IR:
llvm_unreachable("Unsupported language kind");
}
llvm_unreachable("Unhandled language kind");
}
/// Serialize the identifier object as specified by the Symbol Graph format.
///
/// The identifier property of a symbol contains the USR for precise and unique
/// references, and the interface language name.
Object serializeIdentifier(const APIRecord &Record, Language Lang) {
Object Identifier;
Identifier["precise"] = Record.USR;
Identifier["interfaceLanguage"] = getLanguageName(Lang);
return Identifier;
}
/// Serialize the documentation comments attached to a symbol, as specified by
/// the Symbol Graph format.
///
/// The Symbol Graph \c docComment object contains an array of lines. Each line
/// represents one line of striped documentation comment, with source range
/// information.
/// e.g.
/// \code
/// /// This is a documentation comment
/// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' First line.
/// /// with multiple lines.
/// ^~~~~~~~~~~~~~~~~~~~~~~' Second line.
/// \endcode
///
/// \returns \c std::nullopt if \p Comment is empty, or an \c Object containing
/// the formatted lines.
std::optional<Object> serializeDocComment(const DocComment &Comment) {
if (Comment.empty())
return std::nullopt;
Object DocComment;
Array LinesArray;
for (const auto &CommentLine : Comment) {
Object Line;
Line["text"] = CommentLine.Text;
serializeObject(Line, "range",
serializeSourceRange(CommentLine.Begin, CommentLine.End));
LinesArray.emplace_back(std::move(Line));
}
serializeArray(DocComment, "lines", LinesArray);
return DocComment;
}
/// Serialize the declaration fragments of a symbol.
///
/// The Symbol Graph declaration fragments is an array of tagged important
/// parts of a symbol's declaration. The fragments sequence can be joined to
/// form spans of declaration text, with attached information useful for
/// purposes like syntax-highlighting etc. For example:
/// \code
/// const int pi; -> "declarationFragments" : [
/// {
/// "kind" : "keyword",
/// "spelling" : "const"
/// },
/// {
/// "kind" : "text",
/// "spelling" : " "
/// },
/// {
/// "kind" : "typeIdentifier",
/// "preciseIdentifier" : "c:I",
/// "spelling" : "int"
/// },
/// {
/// "kind" : "text",
/// "spelling" : " "
/// },
/// {
/// "kind" : "identifier",
/// "spelling" : "pi"
/// }
/// ]
/// \endcode
///
/// \returns \c std::nullopt if \p DF is empty, or an \c Array containing the
/// formatted declaration fragments array.
std::optional<Array>
serializeDeclarationFragments(const DeclarationFragments &DF) {
if (DF.getFragments().empty())
return std::nullopt;
Array Fragments;
for (const auto &F : DF.getFragments()) {
Object Fragment;
Fragment["spelling"] = F.Spelling;
Fragment["kind"] = DeclarationFragments::getFragmentKindString(F.Kind);
if (!F.PreciseIdentifier.empty())
Fragment["preciseIdentifier"] = F.PreciseIdentifier;
Fragments.emplace_back(std::move(Fragment));
}
return Fragments;
}
/// Serialize the \c names field of a symbol as specified by the Symbol Graph
/// format.
///
/// The Symbol Graph names field contains multiple representations of a symbol
/// that can be used for different applications:
/// - \c title : The simple declared name of the symbol;
/// - \c subHeading : An array of declaration fragments that provides tags,
/// and potentially more tokens (for example the \c +/- symbol for
/// Objective-C methods). Can be used as sub-headings for documentation.
Object serializeNames(const APIRecord &Record) {
Object Names;
Names["title"] = Record.Name;
serializeArray(Names, "subHeading",
serializeDeclarationFragments(Record.SubHeading));
DeclarationFragments NavigatorFragments;
NavigatorFragments.append(Record.Name,
DeclarationFragments::FragmentKind::Identifier,
/*PreciseIdentifier*/ "");
serializeArray(Names, "navigator",
serializeDeclarationFragments(NavigatorFragments));
return Names;
}
Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) {
auto AddLangPrefix = [&Lang](StringRef S) -> std::string {
return (getLanguageName(Lang) + "." + S).str();
};
Object Kind;
switch (RK) {
case APIRecord::RK_Unknown:
llvm_unreachable("Records should have an explicit kind");
break;
case APIRecord::RK_GlobalFunction:
Kind["identifier"] = AddLangPrefix("func");
Kind["displayName"] = "Function";
break;
case APIRecord::RK_GlobalVariable:
Kind["identifier"] = AddLangPrefix("var");
Kind["displayName"] = "Global Variable";
break;
case APIRecord::RK_EnumConstant:
Kind["identifier"] = AddLangPrefix("enum.case");
Kind["displayName"] = "Enumeration Case";
break;
case APIRecord::RK_Enum:
Kind["identifier"] = AddLangPrefix("enum");
Kind["displayName"] = "Enumeration";
break;
case APIRecord::RK_StructField:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_Struct:
Kind["identifier"] = AddLangPrefix("struct");
Kind["displayName"] = "Structure";
break;
case APIRecord::RK_ObjCIvar:
Kind["identifier"] = AddLangPrefix("ivar");
Kind["displayName"] = "Instance Variable";
break;
case APIRecord::RK_ObjCInstanceMethod:
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Instance Method";
break;
case APIRecord::RK_ObjCClassMethod:
Kind["identifier"] = AddLangPrefix("type.method");
Kind["displayName"] = "Type Method";
break;
case APIRecord::RK_ObjCInstanceProperty:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_ObjCClassProperty:
Kind["identifier"] = AddLangPrefix("type.property");
Kind["displayName"] = "Type Property";
break;
case APIRecord::RK_ObjCInterface:
Kind["identifier"] = AddLangPrefix("class");
Kind["displayName"] = "Class";
break;
case APIRecord::RK_ObjCCategory:
// We don't serialize out standalone Objective-C category symbols yet.
llvm_unreachable("Serializing standalone Objective-C category symbols is "
"not supported.");
break;
case APIRecord::RK_ObjCProtocol:
Kind["identifier"] = AddLangPrefix("protocol");
Kind["displayName"] = "Protocol";
break;
case APIRecord::RK_MacroDefinition:
Kind["identifier"] = AddLangPrefix("macro");
Kind["displayName"] = "Macro";
break;
case APIRecord::RK_Typedef:
Kind["identifier"] = AddLangPrefix("typealias");
Kind["displayName"] = "Type Alias";
break;
}
return Kind;
}
/// Serialize the symbol kind information.
///
/// The Symbol Graph symbol kind property contains a shorthand \c identifier
/// which is prefixed by the source language name, useful for tooling to parse
/// the kind, and a \c displayName for rendering human-readable names.
Object serializeSymbolKind(const APIRecord &Record, Language Lang) {
return serializeSymbolKind(Record.getKind(), Lang);
}
template <typename RecordTy>
std::optional<Object>
serializeFunctionSignatureMixinImpl(const RecordTy &Record, std::true_type) {
const auto &FS = Record.Signature;
if (FS.empty())
return std::nullopt;
Object Signature;
serializeArray(Signature, "returns",
serializeDeclarationFragments(FS.getReturnType()));
Array Parameters;
for (const auto &P : FS.getParameters()) {
Object Parameter;
Parameter["name"] = P.Name;
serializeArray(Parameter, "declarationFragments",
serializeDeclarationFragments(P.Fragments));
Parameters.emplace_back(std::move(Parameter));
}
if (!Parameters.empty())
Signature["parameters"] = std::move(Parameters);
return Signature;
}
template <typename RecordTy>
std::optional<Object>
serializeFunctionSignatureMixinImpl(const RecordTy &Record, std::false_type) {
return std::nullopt;
}
/// Serialize the function signature field, as specified by the
/// Symbol Graph format.
///
/// The Symbol Graph function signature property contains two arrays.
/// - The \c returns array is the declaration fragments of the return type;
/// - The \c parameters array contains names and declaration fragments of the
/// parameters.
///
/// \returns \c std::nullopt if \p FS is empty, or an \c Object containing the
/// formatted function signature.
template <typename RecordTy>
void serializeFunctionSignatureMixin(Object &Paren, const RecordTy &Record) {
serializeObject(Paren, "functionSignature",
serializeFunctionSignatureMixinImpl(
Record, has_function_signature<RecordTy>()));
}
struct PathComponent {
StringRef USR;
StringRef Name;
APIRecord::RecordKind Kind;
PathComponent(StringRef USR, StringRef Name, APIRecord::RecordKind Kind)
: USR(USR), Name(Name), Kind(Kind) {}
};
template <typename RecordTy>
bool generatePathComponents(
const RecordTy &Record, const APISet &API,
function_ref<void(const PathComponent &)> ComponentTransformer) {
SmallVector<PathComponent, 4> ReverseComponenents;
ReverseComponenents.emplace_back(Record.USR, Record.Name, Record.getKind());
const auto *CurrentParent = &Record.ParentInformation;
bool FailedToFindParent = false;
while (CurrentParent && !CurrentParent->empty()) {
PathComponent CurrentParentComponent(CurrentParent->ParentUSR,
CurrentParent->ParentName,
CurrentParent->ParentKind);
auto *ParentRecord = CurrentParent->ParentRecord;
// Slow path if we don't have a direct reference to the ParentRecord
if (!ParentRecord)
ParentRecord = API.findRecordForUSR(CurrentParent->ParentUSR);
// If the parent is a category then we need to pretend this belongs to the
// associated interface.
if (auto *CategoryRecord =
dyn_cast_or_null<ObjCCategoryRecord>(ParentRecord)) {
ParentRecord = API.findRecordForUSR(CategoryRecord->Interface.USR);
CurrentParentComponent = PathComponent(CategoryRecord->Interface.USR,
CategoryRecord->Interface.Name,
APIRecord::RK_ObjCInterface);
}
// The parent record doesn't exist which means the symbol shouldn't be
// treated as part of the current product.
if (!ParentRecord) {
FailedToFindParent = true;
break;
}
ReverseComponenents.push_back(std::move(CurrentParentComponent));
CurrentParent = &ParentRecord->ParentInformation;
}
for (const auto &PC : reverse(ReverseComponenents))
ComponentTransformer(PC);
return FailedToFindParent;
}
Object serializeParentContext(const PathComponent &PC, Language Lang) {
Object ParentContextElem;
ParentContextElem["usr"] = PC.USR;
ParentContextElem["name"] = PC.Name;
ParentContextElem["kind"] = serializeSymbolKind(PC.Kind, Lang)["identifier"];
return ParentContextElem;
}
template <typename RecordTy>
Array generateParentContexts(const RecordTy &Record, const APISet &API,
Language Lang) {
Array ParentContexts;
generatePathComponents(Record, API,
[Lang, &ParentContexts](const PathComponent &PC) {
ParentContexts.push_back(
serializeParentContext(PC, Lang));
});
return ParentContexts;
}
} // namespace
void SymbolGraphSerializer::anchor() {}
/// Defines the format version emitted by SymbolGraphSerializer.
const VersionTuple SymbolGraphSerializer::FormatVersion{0, 5, 3};
Object SymbolGraphSerializer::serializeMetadata() const {
Object Metadata;
serializeObject(Metadata, "formatVersion",
serializeSemanticVersion(FormatVersion));
Metadata["generator"] = clang::getClangFullVersion();
return Metadata;
}
Object SymbolGraphSerializer::serializeModule() const {
Object Module;
// The user is expected to always pass `--product-name=` on the command line
// to populate this field.
Module["name"] = API.ProductName;
serializeObject(Module, "platform", serializePlatform(API.getTarget()));
return Module;
}
bool SymbolGraphSerializer::shouldSkip(const APIRecord &Record) const {
// Skip explicitly ignored symbols.
if (IgnoresList.shouldIgnore(Record.Name))
return true;
// Skip unconditionally unavailable symbols
if (Record.Availabilities.isUnconditionallyUnavailable())
return true;
// Filter out symbols prefixed with an underscored as they are understood to
// be symbols clients should not use.
if (Record.Name.startswith("_"))
return true;
return false;
}
template <typename RecordTy>
std::optional<Object>
SymbolGraphSerializer::serializeAPIRecord(const RecordTy &Record) const {
if (shouldSkip(Record))
return std::nullopt;
Object Obj;
serializeObject(Obj, "identifier",
serializeIdentifier(Record, API.getLanguage()));
serializeObject(Obj, "kind", serializeSymbolKind(Record, API.getLanguage()));
serializeObject(Obj, "names", serializeNames(Record));
serializeObject(
Obj, "location",
serializeSourceLocation(Record.Location, /*IncludeFileURI=*/true));
serializeArray(Obj, "availability",
serializeAvailability(Record.Availabilities));
serializeObject(Obj, "docComment", serializeDocComment(Record.Comment));
serializeArray(Obj, "declarationFragments",
serializeDeclarationFragments(Record.Declaration));
// TODO: Once we keep track of symbol access information serialize it
// correctly here.
Obj["accessLevel"] = "public";
SmallVector<StringRef, 4> PathComponentsNames;
// If this returns true it indicates that we couldn't find a symbol in the
// hierarchy.
if (generatePathComponents(Record, API,
[&PathComponentsNames](const PathComponent &PC) {
PathComponentsNames.push_back(PC.Name);
}))
return {};
serializeArray(Obj, "pathComponents", Array(PathComponentsNames));
serializeFunctionSignatureMixin(Obj, Record);
return Obj;
}
template <typename MemberTy>
void SymbolGraphSerializer::serializeMembers(
const APIRecord &Record,
const SmallVector<std::unique_ptr<MemberTy>> &Members) {
// Members should not be serialized if we aren't recursing.
if (!ShouldRecurse)
return;
for (const auto &Member : Members) {
auto MemberRecord = serializeAPIRecord(*Member);
if (!MemberRecord)
continue;
Symbols.emplace_back(std::move(*MemberRecord));
serializeRelationship(RelationshipKind::MemberOf, *Member, Record);
}
}
StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
switch (Kind) {
case RelationshipKind::MemberOf:
return "memberOf";
case RelationshipKind::InheritsFrom:
return "inheritsFrom";
case RelationshipKind::ConformsTo:
return "conformsTo";
}
llvm_unreachable("Unhandled relationship kind");
}
void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind,
SymbolReference Source,
SymbolReference Target) {
Object Relationship;
Relationship["source"] = Source.USR;
Relationship["target"] = Target.USR;
Relationship["targetFallback"] = Target.Name;
Relationship["kind"] = getRelationshipString(Kind);
Relationships.emplace_back(std::move(Relationship));
}
void SymbolGraphSerializer::serializeGlobalFunctionRecord(
const GlobalFunctionRecord &Record) {
auto Obj = serializeAPIRecord(Record);
if (!Obj)
return;
Symbols.emplace_back(std::move(*Obj));
}
void SymbolGraphSerializer::serializeGlobalVariableRecord(
const GlobalVariableRecord &Record) {
auto Obj = serializeAPIRecord(Record);
if (!Obj)
return;
Symbols.emplace_back(std::move(*Obj));
}
void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) {
auto Enum = serializeAPIRecord(Record);
if (!Enum)
return;
Symbols.emplace_back(std::move(*Enum));
serializeMembers(Record, Record.Constants);
}
void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) {
auto Struct = serializeAPIRecord(Record);
if (!Struct)
return;
Symbols.emplace_back(std::move(*Struct));
serializeMembers(Record, Record.Fields);
}
void SymbolGraphSerializer::serializeObjCContainerRecord(
const ObjCContainerRecord &Record) {
auto ObjCContainer = serializeAPIRecord(Record);
if (!ObjCContainer)
return;
Symbols.emplace_back(std::move(*ObjCContainer));
serializeMembers(Record, Record.Ivars);
serializeMembers(Record, Record.Methods);
serializeMembers(Record, Record.Properties);
for (const auto &Protocol : Record.Protocols)
// Record that Record conforms to Protocol.
serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record)) {
if (!ObjCInterface->SuperClass.empty())
// If Record is an Objective-C interface record and it has a super class,
// record that Record is inherited from SuperClass.
serializeRelationship(RelationshipKind::InheritsFrom, Record,
ObjCInterface->SuperClass);
// Members of categories extending an interface are serialized as members of
// the interface.
for (const auto *Category : ObjCInterface->Categories) {
serializeMembers(Record, Category->Ivars);
serializeMembers(Record, Category->Methods);
serializeMembers(Record, Category->Properties);
// Surface the protocols of the category to the interface.
for (const auto &Protocol : Category->Protocols)
serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
}
}
}
void SymbolGraphSerializer::serializeMacroDefinitionRecord(
const MacroDefinitionRecord &Record) {
auto Macro = serializeAPIRecord(Record);
if (!Macro)
return;
Symbols.emplace_back(std::move(*Macro));
}
void SymbolGraphSerializer::serializeSingleRecord(const APIRecord *Record) {
switch (Record->getKind()) {
case APIRecord::RK_Unknown:
llvm_unreachable("Records should have a known kind!");
case APIRecord::RK_GlobalFunction:
serializeGlobalFunctionRecord(*cast<GlobalFunctionRecord>(Record));
break;
case APIRecord::RK_GlobalVariable:
serializeGlobalVariableRecord(*cast<GlobalVariableRecord>(Record));
break;
case APIRecord::RK_Enum:
serializeEnumRecord(*cast<EnumRecord>(Record));
break;
case APIRecord::RK_Struct:
serializeStructRecord(*cast<StructRecord>(Record));
break;
case APIRecord::RK_ObjCInterface:
serializeObjCContainerRecord(*cast<ObjCInterfaceRecord>(Record));
break;
case APIRecord::RK_ObjCProtocol:
serializeObjCContainerRecord(*cast<ObjCProtocolRecord>(Record));
break;
case APIRecord::RK_MacroDefinition:
serializeMacroDefinitionRecord(*cast<MacroDefinitionRecord>(Record));
break;
case APIRecord::RK_Typedef:
serializeTypedefRecord(*cast<TypedefRecord>(Record));
break;
default:
if (auto Obj = serializeAPIRecord(*Record)) {
Symbols.emplace_back(std::move(*Obj));
auto &ParentInformation = Record->ParentInformation;
if (!ParentInformation.empty())
serializeRelationship(RelationshipKind::MemberOf, *Record,
*ParentInformation.ParentRecord);
}
break;
}
}
void SymbolGraphSerializer::serializeTypedefRecord(
const TypedefRecord &Record) {
// Typedefs of anonymous types have their entries unified with the underlying
// type.
bool ShouldDrop = Record.UnderlyingType.Name.empty();
// enums declared with `NS_OPTION` have a named enum and a named typedef, with
// the same name
ShouldDrop |= (Record.UnderlyingType.Name == Record.Name);
if (ShouldDrop)
return;
auto Typedef = serializeAPIRecord(Record);
if (!Typedef)
return;
(*Typedef)["type"] = Record.UnderlyingType.USR;
Symbols.emplace_back(std::move(*Typedef));
}
Object SymbolGraphSerializer::serialize() {
// Serialize global variables in the API set.
for (const auto &GlobalVar : API.getGlobalVariables())
serializeGlobalVariableRecord(*GlobalVar.second);
for (const auto &GlobalFunction : API.getGlobalFunctions())
serializeGlobalFunctionRecord(*GlobalFunction.second);
// Serialize enum records in the API set.
for (const auto &Enum : API.getEnums())
serializeEnumRecord(*Enum.second);
// Serialize struct records in the API set.
for (const auto &Struct : API.getStructs())
serializeStructRecord(*Struct.second);
// Serialize Objective-C interface records in the API set.
for (const auto &ObjCInterface : API.getObjCInterfaces())
serializeObjCContainerRecord(*ObjCInterface.second);
// Serialize Objective-C protocol records in the API set.
for (const auto &ObjCProtocol : API.getObjCProtocols())
serializeObjCContainerRecord(*ObjCProtocol.second);
for (const auto &Macro : API.getMacros())
serializeMacroDefinitionRecord(*Macro.second);
for (const auto &Typedef : API.getTypedefs())
serializeTypedefRecord(*Typedef.second);
return serializeCurrentGraph();
}
Object SymbolGraphSerializer::serializeCurrentGraph() {
Object Root;
serializeObject(Root, "metadata", serializeMetadata());
serializeObject(Root, "module", serializeModule());
Root["symbols"] = std::move(Symbols);
Root["relationships"] = std::move(Relationships);
return Root;
}
void SymbolGraphSerializer::serialize(raw_ostream &os) {
Object root = serialize();
if (Options.Compact)
os << formatv("{0}", Value(std::move(root))) << "\n";
else
os << formatv("{0:2}", Value(std::move(root))) << "\n";
}
std::optional<Object>
SymbolGraphSerializer::serializeSingleSymbolSGF(StringRef USR,
const APISet &API) {
APIRecord *Record = API.findRecordForUSR(USR);
if (!Record)
return {};
if (isa<ObjCCategoryRecord>(Record))
return {};
Object Root;
APIIgnoresList EmptyIgnores;
SymbolGraphSerializer Serializer(API, EmptyIgnores,
/*Options.Compact*/ {true},
/*ShouldRecurse*/ false);
Serializer.serializeSingleRecord(Record);
serializeObject(Root, "symbolGraph", Serializer.serializeCurrentGraph());
Language Lang = API.getLanguage();
serializeArray(Root, "parentContexts",
generateParentContexts(*Record, API, Lang));
Array RelatedSymbols;
for (const auto &Fragment : Record->Declaration.getFragments()) {
// If we don't have a USR there isn't much we can do.
if (Fragment.PreciseIdentifier.empty())
continue;
APIRecord *RelatedRecord = API.findRecordForUSR(Fragment.PreciseIdentifier);
// If we can't find the record let's skip.
if (!RelatedRecord)
continue;
Object RelatedSymbol;
RelatedSymbol["usr"] = RelatedRecord->USR;
RelatedSymbol["declarationLanguage"] = getLanguageName(Lang);
// TODO: once we record this properly let's serialize it right.
RelatedSymbol["accessLevel"] = "public";
RelatedSymbol["filePath"] = RelatedRecord->Location.getFilename();
RelatedSymbol["moduleName"] = API.ProductName;
RelatedSymbol["isSystem"] = RelatedRecord->IsFromSystemHeader;
serializeArray(RelatedSymbol, "parentContexts",
generateParentContexts(*RelatedRecord, API, Lang));
RelatedSymbols.push_back(std::move(RelatedSymbol));
}
serializeArray(Root, "relatedSymbols", RelatedSymbols);
return Root;
}