diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -30,6 +30,25 @@ #include "llvm/Support/Casting.h" #include +namespace { + +/// \brief A custom deleter used for ``std::unique_ptr`` to APIRecords stored +/// in the BumpPtrAllocator. +/// +/// \tparam T the exact type of the APIRecord subclass. +template struct UniquePtrBumpPtrAllocatorDeleter { + void operator()(T *Instance) { Instance->~T(); } +}; + +/// A unique pointer to an APIRecord stored in the BumpPtrAllocator. +/// +/// \tparam T the exact type of the APIRecord subclass. +template +using APIRecordUniquePtr = + std::unique_ptr>; + +} // anonymous namespace + namespace clang { namespace extractapi { @@ -73,6 +92,8 @@ /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum RecordKind { RK_Global, + RK_EnumConstant, + RK_Enum, }; private: @@ -125,6 +146,36 @@ virtual void anchor(); }; +/// This holds information associated with enum constants. +struct EnumConstantRecord : APIRecord { + EnumConstantRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) + : APIRecord(RK_EnumConstant, Name, USR, Loc, Availability, + LinkageInfo::none(), Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_EnumConstant; + } +}; + +/// This holds information associated with enums. +struct EnumRecord : APIRecord { + SmallVector> Constants; + + EnumRecord(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading) + : APIRecord(RK_Enum, Name, USR, Loc, Availability, LinkageInfo::none(), + Comment, Declaration, SubHeading) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_Enum; + } +}; + /// APISet holds the set of API records collected from given inputs. class APISet { public: @@ -166,28 +217,41 @@ DeclarationFragments SubHeading, FunctionSignature Signature); -private: - /// \brief A custom deleter used for ``std::unique_ptr`` to APIRecords stored - /// in the BumpPtrAllocator. + /// Create and add an enum constant record into the API set. /// - /// \tparam T the exact type of the APIRecord subclass. - template struct UniquePtrBumpPtrAllocatorDeleter { - void operator()(T *Instance) { Instance->~T(); } - }; - -public: - /// A unique pointer to an APIRecord stored in the BumpPtrAllocator. + /// Note: the caller is responsible for keeping the StringRef \p Name and + /// \p USR alive. APISet::copyString provides a way to copy strings into + /// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method + /// to generate the USR for \c D and keep it alive in APISet. + EnumConstantRecord *addEnumConstant(EnumRecord *Enum, StringRef Name, + StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); + + /// Create and add an enum record into the API set. /// - /// \tparam T the exact type of the APIRecord subclass. - template - using APIRecordUniquePtr = - std::unique_ptr>; + /// Note: the caller is responsible for keeping the StringRef \p Name and + /// \p USR alive. APISet::copyString provides a way to copy strings into + /// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method + /// to generate the USR for \c D and keep it alive in APISet. + EnumRecord *addEnum(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading); /// A map to store the set of GlobalRecord%s with the declaration name as the /// key. using GlobalRecordMap = llvm::MapVector>; + /// A map to store the set of EnumRecord%s with the declaration name as the + /// key. + using EnumRecordMap = + llvm::MapVector>; + /// Get the target triple for the ExtractAPI invocation. const llvm::Triple &getTarget() const { return Target; } @@ -195,6 +259,7 @@ const LangOptions &getLangOpts() const { return LangOpts; } const GlobalRecordMap &getGlobals() const { return Globals; } + const EnumRecordMap &getEnums() const { return Enums; } /// Generate and store the USR of declaration \p D. /// @@ -219,6 +284,7 @@ const LangOptions LangOpts; GlobalRecordMap Globals; + EnumRecordMap Enums; }; } // namespace extractapi diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -188,6 +188,14 @@ /// Build DeclarationFragments for a function declaration FunctionDecl. static DeclarationFragments getFragmentsForFunction(const FunctionDecl *); + /// Build DeclarationFragments for an enum constant declaration + /// EnumConstantDecl. + static DeclarationFragments + getFragmentsForEnumConstant(const EnumConstantDecl *); + + /// Build DeclarationFragments for an enum declaration EnumDecl. + static DeclarationFragments getFragmentsForEnum(const EnumDecl *); + /// Build sub-heading fragments for a NamedDecl. static DeclarationFragments getSubHeading(const NamedDecl *); diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -56,6 +56,17 @@ /// write out the serialized JSON object to \p os. void serialize(raw_ostream &os) override; + /// The kind of a relationship between two symbols. + enum RelationshipKind { + /// The source symbol is a member of the target symbol. + /// For example enum constants are members of the enum, class/instance + /// methods are members of the class, etc. + MemberOf, + }; + + /// Get the string representation of the relationship kind. + static StringRef getRelationshipString(RelationshipKind Kind); + private: /// Synthesize the metadata section of the Symbol Graph format. /// @@ -86,9 +97,19 @@ /// containing common symbol information of \p Record. Optional serializeAPIRecord(const APIRecord &Record) const; + /// Serialize the \p Kind relationship between \p Source and \p Target. + /// + /// Record the relationship between the two symbols in + /// SymbolGraphSerializer::Relationships. + void serializeRelationship(RelationshipKind Kind, const APIRecord &Source, + const APIRecord &Target); + /// Serialize a global record. void serializeGlobalRecord(const GlobalRecord &Record); + /// Serialize an enum record. + void serializeEnumRecord(const EnumRecord &Record); + public: SymbolGraphSerializer(const APISet &API, StringRef ProductName, APISerializerOption Options = {}) diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -59,6 +59,31 @@ Comment, Fragments, SubHeading, Signature); } +EnumConstantRecord *APISet::addEnumConstant( + EnumRecord *Enum, StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading) { + auto Record = + APIRecordUniquePtr(new (Allocator) EnumConstantRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}); + return Enum->Constants.emplace_back(std::move(Record)).get(); +} + +EnumRecord *APISet::addEnum(StringRef Name, StringRef USR, PresumedLoc Loc, + const AvailabilityInfo &Availability, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading) { + auto Result = Enums.insert({Name, nullptr}); + if (Result.second) { + // Create the record if it does not already exist. + auto Record = APIRecordUniquePtr(new (Allocator) EnumRecord{ + Name, USR, Loc, Availability, Comment, Declaration, SubHeading}); + Result.first->second = std::move(Record); + } + return Result.first->second.get(); +} + StringRef APISet::recordUSR(const Decl *D) { SmallString<128> USR; index::generateUSRForDecl(D, USR); diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -401,6 +401,35 @@ return Fragments; } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForEnumConstant( + const EnumConstantDecl *EnumConstDecl) { + DeclarationFragments Fragments; + return Fragments.append(EnumConstDecl->getName(), + DeclarationFragments::FragmentKind::Identifier); +} + +DeclarationFragments +DeclarationFragmentsBuilder::getFragmentsForEnum(const EnumDecl *EnumDecl) { + // TODO: After we support typedef records, if there's a typedef for this enum + // just use the declaration fragments of the typedef decl. + + DeclarationFragments Fragments, After; + Fragments.append("enum", DeclarationFragments::FragmentKind::Keyword); + + if (!EnumDecl->getName().empty()) + Fragments.appendSpace().append( + EnumDecl->getName(), DeclarationFragments::FragmentKind::Identifier); + + QualType IntegerType = EnumDecl->getIntegerType(); + if (!IntegerType.isNull()) + Fragments.append(": ", DeclarationFragments::FragmentKind::Text) + .append( + getFragmentsForType(IntegerType, EnumDecl->getASTContext(), After)) + .append(std::move(After)); + + return Fragments; +} + FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) { FunctionSignature Signature; diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -145,6 +145,40 @@ return true; } + bool VisitEnumDecl(const EnumDecl *Decl) { + if (!Decl->isComplete()) + return true; + + // Skip forward declaration. + if (!Decl->isThisDeclarationADefinition()) + return true; + + // Collect symbol information. + StringRef Name = Decl->getName(); + StringRef USR = API.recordUSR(Decl); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Decl->getLocation()); + AvailabilityInfo Availability = getAvailability(Decl); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments and sub-heading for the enum. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForEnum(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + EnumRecord *EnumRecord = API.addEnum(Name, USR, Loc, Availability, Comment, + Declaration, SubHeading); + + // Now collect information about the enumerators in this enum. + recordEnumConstants(EnumRecord, Decl->enumerators()); + + return true; + } + private: /// Get availability information of the declaration \p D. AvailabilityInfo getAvailability(const Decl *D) const { @@ -177,6 +211,33 @@ return Availability; } + /// Collect API information for the enum constants and associate with the + /// parent enum. + void recordEnumConstants(EnumRecord *EnumRecord, + const EnumDecl::enumerator_range Constants) { + for (const auto *Constant : Constants) { + // Collect symbol information. + StringRef Name = Constant->getName(); + StringRef USR = API.recordUSR(Constant); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Constant->getLocation()); + AvailabilityInfo Availability = getAvailability(Constant); + DocComment Comment; + if (auto *RawComment = Context.getRawCommentForDeclNoCache(Constant)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments and sub-heading for the enum constant. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForEnumConstant(Constant); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Constant); + + API.addEnumConstant(EnumRecord, Name, USR, Loc, Availability, Comment, + Declaration, SubHeading); + } + } + ASTContext &Context; APISet API; }; diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -336,17 +336,21 @@ /// the kind, and a \c displayName for rendering human-readable names. Object serializeSymbolKind(const APIRecord &Record, const LangOptions &LangOpts) { + auto AddLangPrefix = [&LangOpts](StringRef S) -> std::string { + return (getLanguageName(LangOpts) + "." + S).str(); + }; + Object Kind; switch (Record.getKind()) { - case APIRecord::RK_Global: + case APIRecord::RK_Global: { auto *GR = dyn_cast(&Record); switch (GR->GlobalKind) { case GVKind::Function: - Kind["identifier"] = (getLanguageName(LangOpts) + ".func").str(); + Kind["identifier"] = AddLangPrefix("func"); Kind["displayName"] = "Function"; break; case GVKind::Variable: - Kind["identifier"] = (getLanguageName(LangOpts) + ".var").str(); + Kind["identifier"] = AddLangPrefix("var"); Kind["displayName"] = "Global Variable"; break; case GVKind::Unknown: @@ -355,6 +359,15 @@ } 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; + } return Kind; } @@ -413,6 +426,25 @@ return Obj; } +StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) { + switch (Kind) { + case RelationshipKind::MemberOf: + return "memberOf"; + } + llvm_unreachable("Unhandled relationship kind"); +} + +void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind, + const APIRecord &Source, + const APIRecord &Target) { + Object Relationship; + Relationship["source"] = Source.USR; + Relationship["target"] = Target.USR; + Relationship["kind"] = getRelationshipString(Kind); + + Relationships.emplace_back(std::move(Relationship)); +} + void SymbolGraphSerializer::serializeGlobalRecord(const GlobalRecord &Record) { auto Obj = serializeAPIRecord(Record); if (!Obj) @@ -425,6 +457,23 @@ 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)); + + for (const auto &Constant : Record.Constants) { + auto EnumConstant = serializeAPIRecord(*Constant); + if (!EnumConstant) + continue; + + Symbols.emplace_back(std::move(*EnumConstant)); + serializeRelationship(RelationshipKind::MemberOf, *Constant, Record); + } +} + Object SymbolGraphSerializer::serialize() { Object Root; serializeObject(Root, "metadata", serializeMetadata()); @@ -434,6 +483,10 @@ for (const auto &Global : API.getGlobals()) serializeGlobalRecord(*Global.second); + // Serialize enum records in the API set. + for (const auto &Enum : API.getEnums()) + serializeEnumRecord(*Enum.second); + Root["symbols"] = std::move(Symbols); Root["relationhips"] = std::move(Relationships); diff --git a/clang/test/ExtractAPI/enum.c b/clang/test/ExtractAPI/enum.c new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/enum.c @@ -0,0 +1,505 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ +// RUN: %t/reference.output.json +// RUN: %clang -extract-api -target arm64-apple-macosx \ +// RUN: %t/input.h -o %t/output.json | FileCheck -allow-empty %s + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/output.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- input.h +/// Kinds of vehicles +enum Vehicle { + Bicycle, + Car, + Train, ///< Move this to the top! -Sheldon + Ship, + Airplane, +}; + +enum Direction : unsigned char { + North = 0, + East, + South, + West +}; + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "", + "platform": { + "architecture": "arm64", + "operatingSystem": { + "minimumVersion": { + "major": 11, + "minor": 0, + "patch": 0 + }, + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationhips": [ + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Bicycle", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Car", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Train", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Ship", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Vehicle@Airplane", + "target": "c:@E@Vehicle" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@North", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@East", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@South", + "target": "c:@E@Direction" + }, + { + "kind": "memberOf", + "source": "c:@E@Direction@West", + "target": "c:@E@Direction" + } + ], + "symbols": [ + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "enum" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Vehicle" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:i", + "spelling": "unsigned int" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 22, + "line": 1 + }, + "start": { + "character": 5, + "line": 1 + } + }, + "text": "Kinds of vehicles" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle" + }, + "kind": { + "displayName": "Enumeration", + "identifier": "c.enum" + }, + "location": { + "character": 6, + "line": 2, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Vehicle" + } + ], + "title": "Vehicle" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Bicycle" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Bicycle" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 3, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Bicycle" + } + ], + "title": "Bicycle" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Car" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Car" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 4, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Car" + } + ], + "title": "Car" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Train" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 45, + "line": 5 + }, + "start": { + "character": 15, + "line": 5 + } + }, + "text": "Move this to the top! -Sheldon" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Train" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 5, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Train" + } + ], + "title": "Train" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Ship" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Ship" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 6, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Ship" + } + ], + "title": "Ship" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "Airplane" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Vehicle@Airplane" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 7, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Airplane" + } + ], + "title": "Airplane" + } + }, + { + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "enum" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "Direction" + }, + { + "kind": "text", + "spelling": ": " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:c", + "spelling": "unsigned char" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction" + }, + "kind": { + "displayName": "Enumeration", + "identifier": "c.enum" + }, + "location": { + "character": 6, + "line": 10, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "Direction" + } + ], + "title": "Direction" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "North" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@North" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 11, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "North" + } + ], + "title": "North" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "East" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@East" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 12, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "East" + } + ], + "title": "East" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "South" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@South" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 13, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "South" + } + ], + "title": "South" + } + }, + { + "declarationFragments": [ + { + "kind": "identifier", + "spelling": "West" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@E@Direction@West" + }, + "kind": { + "displayName": "Enumeration Case", + "identifier": "c.enum.case" + }, + "location": { + "character": 3, + "line": 14, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "identifier", + "spelling": "West" + } + ], + "title": "West" + } + } + ] +}