[clang][ExtractAPI] Add support for Objective-C categories

Differential Revision: https://reviews.llvm.org/D152770
This commit is contained in:
ruturaj4 2023-07-04 07:29:06 -05:00
parent b0a77af4f1
commit 1849318539
8 changed files with 1016 additions and 18 deletions

View File

@ -80,6 +80,7 @@ struct APIRecord {
RK_ObjCInstanceMethod,
RK_ObjCInterface,
RK_ObjCCategory,
RK_ObjCCategoryModule,
RK_ObjCProtocol,
RK_MacroDefinition,
RK_Typedef,
@ -153,6 +154,9 @@ public:
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 @@ private:
/// 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 @@ public:
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.
///

View File

@ -578,9 +578,17 @@ bool ExtractAPIVisitorBase<Derived>::VisitObjCCategoryDecl(
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());

View File

@ -39,6 +39,8 @@ public:
getDerived()->traverseObjCProtocols();
getDerived()->traverseObjCCategories();
getDerived()->traverseMacroDefinitionRecords();
getDerived()->traverseTypedefRecords();
@ -84,6 +86,11 @@ public:
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 @@ public:
/// 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){};

View File

@ -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 @@ public:
/// 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 @@ protected:
SymbolGraphSerializerOption Options;
llvm::StringSet<> visitedCategories;
public:
/// Visit a global function record.
void visitGlobalFunctionRecord(const GlobalFunctionRecord &Record);
@ -167,6 +174,9 @@ public:
/// 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);

View File

@ -209,15 +209,16 @@ ObjCCategoryRecord *APISet::addObjCCategory(
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);

View File

@ -328,7 +328,13 @@ serializeDeclarationFragments(const DeclarationFragments &DF) {
/// 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<const ObjCCategoryRecord>(&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 @@ Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) {
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 @@ bool generatePathComponents(
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<ObjCCategoryRecord>(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 @@ StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
return "inheritsFrom";
case RelationshipKind::ConformsTo:
return "conformsTo";
case RelationshipKind::ExtensionTo:
return "extensionTo";
}
llvm_unreachable("Unhandled relationship kind");
}
@ -820,6 +833,45 @@ void SymbolGraphSerializer::visitObjCContainerRecord(
}
}
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 @@ void SymbolGraphSerializer::serializeSingleRecord(const APIRecord *Record) {
case APIRecord::RK_ObjCProtocol:
visitObjCContainerRecord(*cast<ObjCProtocolRecord>(Record));
break;
case APIRecord::RK_ObjCCategory:
visitObjCCategoryRecord(*cast<ObjCCategoryRecord>(Record));
break;
case APIRecord::RK_MacroDefinition:
visitMacroDefinitionRecord(*cast<MacroDefinitionRecord>(Record));
break;
@ -926,9 +981,6 @@ SymbolGraphSerializer::serializeSingleSymbolSGF(StringRef USR,
if (!Record)
return {};
if (isa<ObjCCategoryRecord>(Record))
return {};
Object Root;
APIIgnoresList EmptyIgnores;
SymbolGraphSerializer Serializer(API, EmptyIgnores,

View File

@ -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"
]
}
]
}

View File

@ -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"
]
}
]
}