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 @@ -80,6 +80,7 @@ RK_ObjCInstanceMethod, RK_ObjCInterface, RK_ObjCCategory, + RK_ObjCCategoryModule, RK_ObjCProtocol, RK_MacroDefinition, RK_Typedef, @@ -153,6 +154,9 @@ Comment(Comment), Declaration(Declaration), SubHeading(SubHeading), IsFromSystemHeader(IsFromSystemHeader), Kind(Kind) {} + APIRecord(RecordKind Kind, StringRef USR, StringRef Name) + : USR(USR), Name(Name), Kind(Kind) {} + // Pure virtual destructor to make APIRecord abstract virtual ~APIRecord() = 0; }; @@ -643,6 +647,8 @@ /// This holds information associated with Objective-C categories. struct ObjCCategoryRecord : ObjCContainerRecord { SymbolReference Interface; + /// Determine whether the Category is derived from external class interface. + bool IsFromExternalModule = false; ObjCCategoryRecord(StringRef USR, StringRef Name, PresumedLoc Loc, AvailabilitySet Availabilities, const DocComment &Comment, @@ -895,7 +901,7 @@ AvailabilitySet Availability, const DocComment &Comment, DeclarationFragments Declaration, DeclarationFragments SubHeading, SymbolReference Interface, - bool IsFromSystemHeader); + bool IsFromSystemHeader, bool IsFromExternalModule); /// Create and add an Objective-C interface record into the API set. /// diff --git a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h --- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h +++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h @@ -578,9 +578,17 @@ SymbolReference Interface(InterfaceDecl->getName(), API.recordUSR(InterfaceDecl)); + bool IsFromExternalModule = true; + for (const auto &Interface : API.getObjCInterfaces()) { + if (InterfaceDecl->getName() == Interface.second.get()->Name) { + IsFromExternalModule = false; + break; + } + } + ObjCCategoryRecord *ObjCCategoryRecord = API.addObjCCategory( Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration, SubHeading, - Interface, isInSystemHeader(Decl)); + Interface, isInSystemHeader(Decl), IsFromExternalModule); getDerivedExtractAPIVisitor().recordObjCMethods(ObjCCategoryRecord, Decl->methods()); diff --git a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h --- a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h +++ b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h @@ -39,6 +39,8 @@ getDerived()->traverseObjCProtocols(); + getDerived()->traverseObjCCategories(); + getDerived()->traverseMacroDefinitionRecords(); getDerived()->traverseTypedefRecords(); @@ -84,6 +86,11 @@ getDerived()->visitObjCContainerRecord(*Protocol.second); } + void traverseObjCCategories() { + for (const auto &Category : API.getObjCCategories()) + getDerived()->visitObjCCategoryRecord(*Category.second); + } + void traverseMacroDefinitionRecords() { for (const auto &Macro : API.getMacros()) getDerived()->visitMacroDefinitionRecord(*Macro.second); @@ -113,6 +120,9 @@ /// Visit an Objective-C container record. void visitObjCContainerRecord(const ObjCContainerRecord &Record){}; + /// Visit an Objective-C category record. + void visitObjCCategoryRecord(const ObjCCategoryRecord &Record){}; + /// Visit a macro definition record. void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record){}; 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 @@ -21,6 +21,7 @@ #include "clang/ExtractAPI/APIIgnoresList.h" #include "clang/ExtractAPI/Serialization/SerializerBase.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/JSON.h" #include "llvm/Support/VersionTuple.h" #include "llvm/Support/raw_ostream.h" @@ -87,6 +88,10 @@ /// The source symbol conforms to the target symbol. /// For example Objective-C protocol conformances. ConformsTo, + + /// The source symbol is an extension to the target symbol. + /// For example Objective-C categories extending an external type. + ExtensionTo, }; /// Get the string representation of the relationship kind. @@ -147,6 +152,8 @@ SymbolGraphSerializerOption Options; + llvm::StringSet<> visitedCategories; + public: /// Visit a global function record. void visitGlobalFunctionRecord(const GlobalFunctionRecord &Record); @@ -167,6 +174,9 @@ /// Visit an Objective-C container record. void visitObjCContainerRecord(const ObjCContainerRecord &Record); + /// Visit an Objective-C category record. + void visitObjCCategoryRecord(const ObjCCategoryRecord &Record); + /// Visit a macro definition record. void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record); 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 @@ -209,15 +209,16 @@ StringRef Name, StringRef USR, PresumedLoc Loc, AvailabilitySet Availabilities, const DocComment &Comment, DeclarationFragments Declaration, DeclarationFragments SubHeading, - SymbolReference Interface, bool IsFromSystemHeader) { + SymbolReference Interface, bool IsFromSystemHeader, + bool IsFromExternalModule) { // Create the category record. auto *Record = addTopLevelRecord(USRBasedLookupTable, ObjCCategories, USR, Name, Loc, std::move(Availabilities), Comment, Declaration, SubHeading, Interface, IsFromSystemHeader); - // If this category is extending a known interface, associate it with the - // ObjCInterfaceRecord. + Record->IsFromExternalModule = IsFromExternalModule; + auto It = ObjCInterfaces.find(Interface.USR); if (It != ObjCInterfaces.end()) It->second->Categories.push_back(Record); 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 @@ -328,7 +328,13 @@ /// Objective-C methods). Can be used as sub-headings for documentation. Object serializeNames(const APIRecord &Record) { Object Names; - Names["title"] = Record.Name; + if (auto *CategoryRecord = + dyn_cast_or_null(&Record)) + Names["title"] = + (CategoryRecord->Interface.Name + " (" + Record.Name + ")").str(); + else + Names["title"] = Record.Name; + serializeArray(Names, "subHeading", serializeDeclarationFragments(Record.SubHeading)); DeclarationFragments NavigatorFragments; @@ -432,9 +438,12 @@ 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."); + Kind["identifier"] = AddLangPrefix("class.extension"); + Kind["displayName"] = "Class Extension"; + break; + case APIRecord::RK_ObjCCategoryModule: + Kind["identifier"] = AddLangPrefix("module.extension"); + Kind["displayName"] = "Module Extension"; break; case APIRecord::RK_ObjCProtocol: Kind["identifier"] = AddLangPrefix("protocol"); @@ -563,14 +572,16 @@ 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 the parent is a category extended from internal module then we need to + // pretend this belongs to the associated interface. if (auto *CategoryRecord = dyn_cast_or_null(ParentRecord)) { - ParentRecord = API.findRecordForUSR(CategoryRecord->Interface.USR); - CurrentParentComponent = PathComponent(CategoryRecord->Interface.USR, - CategoryRecord->Interface.Name, - APIRecord::RK_ObjCInterface); + if (!CategoryRecord->IsFromExternalModule) { + 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 @@ -709,6 +720,8 @@ return "inheritsFrom"; case RelationshipKind::ConformsTo: return "conformsTo"; + case RelationshipKind::ExtensionTo: + return "extensionTo"; } llvm_unreachable("Unhandled relationship kind"); } @@ -820,6 +833,45 @@ } } +void SymbolGraphSerializer::visitObjCCategoryRecord( + const ObjCCategoryRecord &Record) { + if (!Record.IsFromExternalModule) + return; + + // Check if the current Category' parent has been visited before, if so skip. + if (!(visitedCategories.contains(Record.Interface.Name) > 0)) { + visitedCategories.insert(Record.Interface.Name); + Object Obj; + serializeObject(Obj, "identifier", + serializeIdentifier(Record, API.getLanguage())); + serializeObject(Obj, "kind", + serializeSymbolKind(APIRecord::RK_ObjCCategoryModule, + API.getLanguage())); + Obj["accessLevel"] = "public"; + Symbols.emplace_back(std::move(Obj)); + } + + Object Relationship; + Relationship["source"] = Record.USR; + Relationship["target"] = Record.Interface.USR; + Relationship["targetFallback"] = Record.Interface.Name; + Relationship["kind"] = getRelationshipString(RelationshipKind::ExtensionTo); + Relationships.emplace_back(std::move(Relationship)); + + auto ObjCCategory = serializeAPIRecord(Record); + + if (!ObjCCategory) + return; + + Symbols.emplace_back(std::move(*ObjCCategory)); + serializeMembers(Record, Record.Methods); + serializeMembers(Record, Record.Properties); + + // Surface the protocols of the category to the interface. + for (const auto &Protocol : Record.Protocols) + serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol); +} + void SymbolGraphSerializer::visitMacroDefinitionRecord( const MacroDefinitionRecord &Record) { auto Macro = serializeAPIRecord(Record); @@ -858,6 +910,9 @@ case APIRecord::RK_ObjCProtocol: visitObjCContainerRecord(*cast(Record)); break; + case APIRecord::RK_ObjCCategory: + visitObjCCategoryRecord(*cast(Record)); + break; case APIRecord::RK_MacroDefinition: visitMacroDefinitionRecord(*cast(Record)); break; @@ -926,9 +981,6 @@ if (!Record) return {}; - if (isa(Record)) - return {}; - Object Root; APIIgnoresList EmptyIgnores; SymbolGraphSerializer Serializer(API, EmptyIgnores, diff --git a/clang/test/ExtractAPI/objc_module_category.m b/clang/test/ExtractAPI/objc_module_category.m new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/objc_module_category.m @@ -0,0 +1,404 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \ +// RUN: %t/reference.output.json.in >> %t/reference.output.json +// RUN: %clang -extract-api -x objective-c-header \ +// RUN: -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 +#import "Foundation.h" + +/// Doc comment 1 +@interface NSString (Category1) +-(void)method1; +@end + +/// Doc comment 2 +@interface NSString (Category2) +-(void)method2; +@end + +//--- Foundation.h +@interface NSString +@end + +//--- 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" + } + }, + "relationships": [ + { + "kind": "extensionTo", + "source": "c:objc(cy)NSString@Category1", + "target": "c:objc(cs)NSString", + "targetFallback": "NSString" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)NSString(im)method1", + "target": "c:objc(cy)NSString@Category1", + "targetFallback": "Category1" + }, + { + "kind": "extensionTo", + "source": "c:objc(cy)NSString@Category2", + "target": "c:objc(cs)NSString", + "targetFallback": "NSString" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)NSString(im)method2", + "target": "c:objc(cy)NSString@Category2", + "targetFallback": "Category2" + } + ], + "symbols": [ + { + "accessLevel": "public", + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category1" + }, + "kind": { + "displayName": "Module Extension", + "identifier": "objective-c.module.extension" + } + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:objc(cs)NSString", + "spelling": "NSString" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "identifier", + "spelling": "Category1" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 18, + "line": 3 + }, + "start": { + "character": 5, + "line": 3 + } + }, + "text": "Doc comment 1" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category1" + }, + "kind": { + "displayName": "Class Extension", + "identifier": "objective-c.class.extension" + }, + "location": { + "position": { + "character": 12, + "line": 4 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "Category1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "Category1" + } + ], + "title": "NSString (Category1)" + }, + "pathComponents": [ + "Category1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ") " + }, + { + "kind": "identifier", + "spelling": "method1" + }, + { + "kind": "text", + "spelling": ";" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)NSString(im)method1" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 5 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "method1" + } + ], + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "method1" + } + ], + "title": "method1" + }, + "pathComponents": [ + "Category1", + "method1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:objc(cs)NSString", + "spelling": "NSString" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "identifier", + "spelling": "Category2" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "docComment": { + "lines": [ + { + "range": { + "end": { + "character": 18, + "line": 8 + }, + "start": { + "character": 5, + "line": 8 + } + }, + "text": "Doc comment 2" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category2" + }, + "kind": { + "displayName": "Class Extension", + "identifier": "objective-c.class.extension" + }, + "location": { + "position": { + "character": 12, + "line": 9 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "Category2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "Category2" + } + ], + "title": "NSString (Category2)" + }, + "pathComponents": [ + "Category2" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ") " + }, + { + "kind": "identifier", + "spelling": "method2" + }, + { + "kind": "text", + "spelling": ";" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)NSString(im)method2" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 10 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "method2" + } + ], + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "method2" + } + ], + "title": "method2" + }, + "pathComponents": [ + "Category2", + "method2" + ] + } + ] +} diff --git a/clang/test/ExtractAPI/objc_various_categories.m b/clang/test/ExtractAPI/objc_various_categories.m new file mode 100644 --- /dev/null +++ b/clang/test/ExtractAPI/objc_various_categories.m @@ -0,0 +1,507 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \ +// RUN: %t/reference.output.json.in >> %t/reference.output.json +// RUN: %clang -extract-api -x objective-c-header \ +// RUN: -target arm64-apple-macosx \ +// RUN: %t/myclass_1.h \ +// 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 +#import "myclass_1.h" +#import "Foundation.h" + +@interface MyClass1 (MyCategory1) +- (int) SomeMethod; +@end + +@interface NSString (Category1) +-(void) StringMethod; +@end + +@interface NSString (Category2) +-(void) StringMethod2; +@end + +//--- myclass_1.h +@interface MyClass1 +@end + +//--- Foundation.h +@interface NSString +@end + +//--- 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" + } + }, + "relationships": [ + { + "kind": "memberOf", + "source": "c:objc(cs)MyClass1(im)SomeMethod", + "target": "c:objc(cs)MyClass1", + "targetFallback": "MyClass1" + }, + { + "kind": "extensionTo", + "source": "c:objc(cy)NSString@Category1", + "target": "c:objc(cs)NSString", + "targetFallback": "NSString" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)NSString(im)StringMethod", + "target": "c:objc(cy)NSString@Category1", + "targetFallback": "Category1" + }, + { + "kind": "extensionTo", + "source": "c:objc(cy)NSString@Category2", + "target": "c:objc(cs)NSString", + "targetFallback": "NSString" + }, + { + "kind": "memberOf", + "source": "c:objc(cs)NSString(im)StringMethod2", + "target": "c:objc(cy)NSString@Category2", + "targetFallback": "Category2" + } + ], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "MyClass1" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)MyClass1" + }, + "kind": { + "displayName": "Class", + "identifier": "objective-c.class" + }, + "location": { + "position": { + "character": 12, + "line": 1 + }, + "uri": "file://INPUT_DIR/myclass_1.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "MyClass1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "MyClass1" + } + ], + "title": "MyClass1" + }, + "pathComponents": [ + "MyClass1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": ") " + }, + { + "kind": "identifier", + "spelling": "SomeMethod" + }, + { + "kind": "text", + "spelling": ";" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)MyClass1(im)SomeMethod" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 5 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "SomeMethod" + } + ], + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "SomeMethod" + } + ], + "title": "SomeMethod" + }, + "pathComponents": [ + "MyClass1", + "SomeMethod" + ] + }, + { + "accessLevel": "public", + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category1" + }, + "kind": { + "displayName": "Module Extension", + "identifier": "objective-c.module.extension" + } + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:objc(cs)NSString", + "spelling": "NSString" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "identifier", + "spelling": "Category1" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category1" + }, + "kind": { + "displayName": "Class Extension", + "identifier": "objective-c.class.extension" + }, + "location": { + "position": { + "character": 12, + "line": 8 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "Category1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "Category1" + } + ], + "title": "NSString (Category1)" + }, + "pathComponents": [ + "Category1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ") " + }, + { + "kind": "identifier", + "spelling": "StringMethod" + }, + { + "kind": "text", + "spelling": ";" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)NSString(im)StringMethod" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 9 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "StringMethod" + } + ], + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "StringMethod" + } + ], + "title": "StringMethod" + }, + "pathComponents": [ + "Category1", + "StringMethod" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "@interface" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:objc(cs)NSString", + "spelling": "NSString" + }, + { + "kind": "text", + "spelling": " (" + }, + { + "kind": "identifier", + "spelling": "Category2" + }, + { + "kind": "text", + "spelling": ")" + } + ], + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cy)NSString@Category2" + }, + "kind": { + "displayName": "Class Extension", + "identifier": "objective-c.class.extension" + }, + "location": { + "position": { + "character": 12, + "line": 12 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "Category2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "Category2" + } + ], + "title": "NSString (Category2)" + }, + "pathComponents": [ + "Category2" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "text", + "spelling": "- (" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": ") " + }, + { + "kind": "identifier", + "spelling": "StringMethod2" + }, + { + "kind": "text", + "spelling": ";" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "objective-c", + "precise": "c:objc(cs)NSString(im)StringMethod2" + }, + "kind": { + "displayName": "Instance Method", + "identifier": "objective-c.method" + }, + "location": { + "position": { + "character": 1, + "line": 13 + }, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "StringMethod2" + } + ], + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "StringMethod2" + } + ], + "title": "StringMethod2" + }, + "pathComponents": [ + "Category2", + "StringMethod2" + ] + } + ] +}