Ruturaj4 created this revision.
Herald added a reviewer: ributzka.
Herald added a project: All.
Ruturaj4 added a reviewer: dang.
Ruturaj4 edited the summary of this revision.
Ruturaj4 edited the summary of this revision.
Ruturaj4 updated this revision to Diff 537064.
Ruturaj4 added a comment.
Ruturaj4 edited the summary of this revision.
Ruturaj4 published this revision for review.
Herald added subscribers: cfe-commits, wangpc.
Herald added a project: clang.



1. Updating D152770 <https://reviews.llvm.org/D152770>: [clang][ExtractAPI] Add 
support for Objective-C categories #
2. Enter a brief description of the changes included in this update.
3. The first line is used as subject, next lines as comment. #
4. If you intended to create a new revision, use:
5. $ arc diff --create

Introducing support for Objective-C Categories for ExtractAPI. The suuport for 
Objective-C Categories is currently lacking
in terms of categories that extend a type defined in current module and 
categories that extend types that belong to external
modules such as NSString. Thus the objective of this patch is two fold, to fix 
former and the later.

This is achieved by introducing two new symbols ->

objective-c.module.extension.
objective-c.class.extension.
Note that the extension represents objective-c "category" (orienting well with 
Swift's Extension, which in spite that fact
that is different, however "broadly" serves similar purpose). The original idea 
is inspired by Max's @theMomax initial
post - 
https://forums.swift.org/t/symbol-graph-adaptions-for-documenting-extensions-to-external-types-in-docc/56684,
and Daniel's helpful comments and feedback. The implementation nonetheless is 
different serving purpose for this project.


Introducing support for Objective-C Categories for ExtractAPI. The suuport for 
Objective-C Categories is currently lacking
in terms of categories that extend a type defined in current module and 
categories that extend types that belong to external
modules such as `NSString`. Thus the objective of this patch is two fold, to 
fix former and the later.

This is achieved by introducing two new symbols ->

1. `objective-c.module.extension`.
2. `objective-c.class.extension`.

Note that the extension represents objective-c "category" (orienting well with 
Swift's Extension, which in spite that fact
that is different, however "broadly" serves similar purpose). The original idea 
is inspired by Max's `@theMomax` initial
post - 
https://forums.swift.org/t/symbol-graph-adaptions-for-documenting-extensions-to-external-types-in-docc/56684,
and Daniel's helpful comments and feedback. The implementation nonetheless is 
different serving purpose for this project.

The following diagram well represents the purpose ->
F28127510: llvm_categories.png <https://reviews.llvm.org/F28127510>


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D152770

Files:
  clang/include/clang/ExtractAPI/API.h
  clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
  clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
  clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
  clang/lib/ExtractAPI/API.cpp
  clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
  clang/test/ExtractAPI/objc_module_category.m
  clang/test/ExtractAPI/objc_various_categories.m

Index: clang/test/ExtractAPI/objc_various_categories.m
===================================================================
--- /dev/null
+++ clang/test/ExtractAPI/objc_various_categories.m
@@ -0,0 +1,519 @@
+// 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 <Foundation/Foundation.h>
+#import "myclass_1.h"
+#import "myclass_2.h"
+
+@interface MyClass1 (MyCategory1)
+- (int) SomeMethod;
+@end
+
+@interface MyClass2 (MyCategory2)
+- (int) SomeMethod;
+@end
+
+@interface NSString (Category)
+-(void) StringMethod;
+@end
+
+//--- myclass_1.h
+@interface MyClass1
+@end
+
+//--- myclass_2.h
+@interface MyClass2
+@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": "memberOf",
+      "source": "c:objc(cs)MyClass2(im)SomeMethod",
+      "target": "c:objc(cy)MyClass2@MyCategory2",
+      "targetFallback": "MyCategory2"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cs)NSString(im)StringMethod",
+      "target": "c:objc(cy)NSString@Category",
+      "targetFallback": "Category"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cy)MyClass2@MyCategory2",
+      "target": "c:objc(cs)MyClass2",
+      "targetFallback": "MyClass2"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cy)NSString@Category",
+      "target": "c:objc(cs)NSString",
+      "targetFallback": "NSString"
+    }
+  ],
+  "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": 6
+        },
+        "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",
+      "declarationFragments": [
+        {
+          "kind": "keyword",
+          "spelling": "@interface"
+        },
+        {
+          "kind": "text",
+          "spelling": " "
+        },
+        {
+          "kind": "typeIdentifier",
+          "preciseIdentifier": "c:objc(cs)MyClass2",
+          "spelling": "MyClass2"
+        },
+        {
+          "kind": "text",
+          "spelling": " ("
+        },
+        {
+          "kind": "identifier",
+          "spelling": "MyCategory2"
+        },
+        {
+          "kind": "text",
+          "spelling": ")"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "objective-c",
+        "precise": "c:objc(cy)MyClass2@MyCategory2"
+      },
+      "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": "MyCategory2"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "MyCategory2"
+          }
+        ],
+        "title": "MyClass2 (MyCategory2)"
+      },
+      "pathComponents": [
+        "MyCategory2"
+      ]
+    },
+    {
+      "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)MyClass2(im)SomeMethod"
+      },
+      "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": "SomeMethod"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "text",
+            "spelling": "- "
+          },
+          {
+            "kind": "identifier",
+            "spelling": "SomeMethod"
+          }
+        ],
+        "title": "SomeMethod"
+      },
+      "pathComponents": [
+        "MyClass2",
+        "SomeMethod"
+      ]
+    },
+    {
+      "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": "Category"
+        },
+        {
+          "kind": "text",
+          "spelling": ")"
+        }
+      ],
+      "identifier": {
+        "interfaceLanguage": "objective-c",
+        "precise": "c:objc(cy)NSString@Category"
+      },
+      "kind": {
+        "displayName": "Class Extension",
+        "identifier": "objective-c.class.extension"
+      },
+      "location": {
+        "position": {
+          "character": 12,
+          "line": 13
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "Category"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "identifier",
+            "spelling": "Category"
+          }
+        ],
+        "title": "NSString (Category)"
+      },
+      "pathComponents": [
+        "Category"
+      ]
+    },
+    {
+      "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": 14
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "StringMethod"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "text",
+            "spelling": "- "
+          },
+          {
+            "kind": "identifier",
+            "spelling": "StringMethod"
+          }
+        ],
+        "title": "StringMethod"
+      },
+      "pathComponents": [
+        "NSString",
+        "StringMethod"
+      ]
+    },
+    {
+      "accessLevel": "public",
+      "identifier": {
+        "interfaceLanguage": "objective-c",
+        "precise": "c:objc(cs)MyClass2"
+      },
+      "kind": {
+        "displayName": "Module Extension",
+        "identifier": "objective-c.module.extension"
+      }
+    },
+    {
+      "accessLevel": "public",
+      "identifier": {
+        "interfaceLanguage": "objective-c",
+        "precise": "c:objc(cs)NSString"
+      },
+      "kind": {
+        "displayName": "Module Extension",
+        "identifier": "objective-c.module.extension"
+      }
+    }
+  ]
+}
Index: clang/test/ExtractAPI/objc_module_category.m
===================================================================
--- /dev/null
+++ clang/test/ExtractAPI/objc_module_category.m
@@ -0,0 +1,399 @@
+// 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/Foundation.h>
+/// Doc comment 1
+@interface NSString (Category1)
+-(void)method1;
+@end
+
+/// Doc comment 2
+@interface NSString (Category2)
+-(void)method2;
+@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)NSString(im)method1",
+      "target": "c:objc(cy)NSString@Category1",
+      "targetFallback": "Category1"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cs)NSString(im)method2",
+      "target": "c:objc(cy)NSString@Category2",
+      "targetFallback": "Category2"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cy)NSString@Category1",
+      "target": "c:objc(cs)NSString",
+      "targetFallback": "NSString"
+    },
+    {
+      "kind": "memberOf",
+      "source": "c:objc(cy)NSString@Category2",
+      "target": "c:objc(cs)NSString",
+      "targetFallback": "NSString"
+    }
+  ],
+  "symbols": [
+    {
+      "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": 2
+              },
+              "start": {
+                "character": 5,
+                "line": 2
+              }
+            },
+            "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": 3
+        },
+        "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": 4
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "method1"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "text",
+            "spelling": "- "
+          },
+          {
+            "kind": "identifier",
+            "spelling": "method1"
+          }
+        ],
+        "title": "method1"
+      },
+      "pathComponents": [
+        "NSString",
+        "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": 7
+              },
+              "start": {
+                "character": 5,
+                "line": 7
+              }
+            },
+            "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": 8
+        },
+        "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": 9
+        },
+        "uri": "file://INPUT_DIR/input.h"
+      },
+      "names": {
+        "navigator": [
+          {
+            "kind": "identifier",
+            "spelling": "method2"
+          }
+        ],
+        "subHeading": [
+          {
+            "kind": "text",
+            "spelling": "- "
+          },
+          {
+            "kind": "identifier",
+            "spelling": "method2"
+          }
+        ],
+        "title": "method2"
+      },
+      "pathComponents": [
+        "NSString",
+        "method2"
+      ]
+    },
+    {
+      "accessLevel": "public",
+      "identifier": {
+        "interfaceLanguage": "objective-c",
+        "precise": "c:objc(cs)NSString"
+      },
+      "kind": {
+        "displayName": "Module Extension",
+        "identifier": "objective-c.module.extension"
+      }
+    }
+  ]
+}
Index: clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
===================================================================
--- clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
+++ clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
@@ -319,7 +319,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<const ObjCCategoryRecord>(&Record))
+    Names["title"] =
+        (CategoryRecord->Interface.Name + " (" + Record.Name + ")").str();
+  else
+    Names["title"] = Record.Name;
+
   serializeArray(Names, "subHeading",
                  serializeDeclarationFragments(Record.SubHeading));
   DeclarationFragments NavigatorFragments;
@@ -391,9 +397,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");
@@ -735,6 +744,42 @@
   }
 }
 
+void SymbolGraphSerializer::visitObjCCategoryModuleRecord(
+    const ObjCCategoryModuleRecord &Record) {
+  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));
+
+  // If the category is extended from an external module, it is then member of
+  // that module.
+  for (const auto &Category : Record.Categories)
+    serializeRelationship(RelationshipKind::MemberOf, *Category, Record);
+}
+
+void SymbolGraphSerializer::visitObjCCategoryRecord(
+    const ObjCCategoryRecord &Record) {
+  if (!Record.IsFromExternalModule)
+    return;
+
+  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);
@@ -767,6 +812,12 @@
   case APIRecord::RK_ObjCProtocol:
     visitObjCContainerRecord(*cast<ObjCProtocolRecord>(Record));
     break;
+  case APIRecord::RK_ObjCCategory:
+    visitObjCCategoryRecord(*cast<ObjCCategoryRecord>(Record));
+    break;
+  case APIRecord::RK_ObjCCategoryModule:
+    visitObjCCategoryModuleRecord(*cast<ObjCCategoryModuleRecord>(Record));
+    break;
   case APIRecord::RK_MacroDefinition:
     visitMacroDefinitionRecord(*cast<MacroDefinitionRecord>(Record));
     break;
@@ -835,9 +886,6 @@
   if (!Record)
     return {};
 
-  if (isa<ObjCCategoryRecord>(Record))
-    return {};
-
   Object Root;
   APIIgnoresList EmptyIgnores;
   SymbolGraphSerializer Serializer(API, EmptyIgnores,
Index: clang/lib/ExtractAPI/API.cpp
===================================================================
--- clang/lib/ExtractAPI/API.cpp
+++ clang/lib/ExtractAPI/API.cpp
@@ -120,22 +120,39 @@
                            SubHeading, IsFromSystemHeader);
 }
 
+ObjCCategoryModuleRecord *APISet::addObjCCategoryModule(StringRef Name,
+                                                        StringRef USR) {
+  // Create the category record.
+  auto *Record =
+      addTopLevelRecord(USRBasedLookupTable, ObjCCategoryModule, USR, Name);
+
+  return Record;
+}
+
 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.
-  auto It = ObjCInterfaces.find(Interface.USR);
-  if (It != ObjCInterfaces.end())
-    It->second->Categories.push_back(Record);
+  Record->IsFromExternalModule = IsFromExternalModule;
+  // If this category is extending an external module, associate it with that
+  // module.
+  if (IsFromExternalModule) {
+    auto It = ObjCCategoryModule.find(Interface.USR);
+    if (It != ObjCCategoryModule.end())
+      It->second->Categories.push_back(Record);
+  } else {
+    auto It = ObjCInterfaces.find(Interface.USR);
+    if (It != ObjCInterfaces.end())
+      It->second->Categories.push_back(Record);
+  }
 
   return Record;
 }
@@ -295,6 +312,7 @@
 void ObjCInstanceMethodRecord::anchor() {}
 void ObjCClassMethodRecord::anchor() {}
 void ObjCCategoryRecord::anchor() {}
+void ObjCCategoryModuleRecord::anchor() {}
 void ObjCInterfaceRecord::anchor() {}
 void ObjCProtocolRecord::anchor() {}
 void MacroDefinitionRecord::anchor() {}
Index: clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
===================================================================
--- clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
+++ clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
@@ -163,6 +163,12 @@
   /// Visit an Objective-C container record.
   void visitObjCContainerRecord(const ObjCContainerRecord &Record);
 
+  /// Visit an Objective-C category record.
+  void visitObjCCategoryRecord(const ObjCCategoryRecord &Record);
+
+  /// Visit an Objective-C category module record.
+  void visitObjCCategoryModuleRecord(const ObjCCategoryModuleRecord &Record);
+
   /// Visit a macro definition record.
   void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record);
 
Index: clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
===================================================================
--- clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
+++ clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
@@ -35,6 +35,10 @@
 
     getDerived()->traverseObjCProtocols();
 
+    getDerived()->traverseObjCCategories();
+
+    getDerived()->traverseObjCCategoryModule();
+
     getDerived()->traverseMacroDefinitionRecords();
 
     getDerived()->traverseTypedefRecords();
@@ -70,6 +74,16 @@
       getDerived()->visitObjCContainerRecord(*Protocol.second);
   }
 
+  void traverseObjCCategories() {
+    for (const auto &Category : API.getObjCCategories())
+      getDerived()->visitObjCCategoryRecord(*Category.second);
+  }
+
+  void traverseObjCCategoryModule() {
+    for (const auto &CategoryModule : API.getObjCCategoryModule())
+      getDerived()->visitObjCCategoryModuleRecord(*CategoryModule.second);
+  }
+
   void traverseMacroDefinitionRecords() {
     for (const auto &Macro : API.getMacros())
       getDerived()->visitMacroDefinitionRecord(*Macro.second);
@@ -95,6 +109,12 @@
   /// Visit an Objective-C container record.
   void visitObjCContainerRecord(const ObjCContainerRecord &Record){};
 
+  /// Visit an Objective-C category record.
+  void visitObjCCategoryRecord(const ObjCCategoryRecord &Record){};
+
+  /// Visit an Objective-C category module record.
+  void visitObjCCategoryModuleRecord(const ObjCCategoryModuleRecord &Record){};
+
   /// Visit a macro definition record.
   void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record){};
 
Index: clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
===================================================================
--- clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
+++ clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
@@ -125,6 +125,15 @@
   }
 }
 
+template <typename T>
+static bool isExternalCategory(const T &Records, const StringRef &Name) {
+  for (const auto &Record : Records) {
+    if (Name == Record.second.get()->Name)
+      return false;
+  }
+  return true;
+}
+
 template <typename Derived>
 bool ExtractAPIVisitorBase<Derived>::VisitVarDecl(const VarDecl *Decl) {
   // skip function parameters.
@@ -484,9 +493,16 @@
   SymbolReference Interface(InterfaceDecl->getName(),
                             API.recordUSR(InterfaceDecl));
 
+  bool IsFromExternalModule =
+      isExternalCategory(API.getObjCInterfaces(), InterfaceDecl->getName());
+
+  if (IsFromExternalModule)
+    API.addObjCCategoryModule(InterfaceDecl->getName(),
+                              API.recordUSR(InterfaceDecl));
+
   ObjCCategoryRecord *ObjCCategoryRecord = API.addObjCCategory(
       Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration, SubHeading,
-      Interface, isInSystemHeader(Decl));
+      Interface, isInSystemHeader(Decl), IsFromExternalModule);
 
   getDerivedExtractAPIVisitor().recordObjCMethods(ObjCCategoryRecord,
                                                   Decl->methods());
Index: clang/include/clang/ExtractAPI/API.h
===================================================================
--- clang/include/clang/ExtractAPI/API.h
+++ clang/include/clang/ExtractAPI/API.h
@@ -71,6 +71,7 @@
     RK_ObjCInstanceMethod,
     RK_ObjCInterface,
     RK_ObjCCategory,
+    RK_ObjCCategoryModule,
     RK_ObjCProtocol,
     RK_MacroDefinition,
     RK_Typedef,
@@ -144,6 +145,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;
 };
@@ -468,6 +472,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,
@@ -488,6 +494,21 @@
   virtual void anchor();
 };
 
+struct ObjCCategoryModuleRecord : APIRecord {
+  // ObjCCategoryRecord%s are stored in and owned by APISet.
+  SmallVector<ObjCCategoryRecord *> Categories;
+
+  ObjCCategoryModuleRecord(StringRef USR, StringRef Name)
+      : APIRecord(RK_ObjCCategoryModule, USR, Name) {}
+
+  static bool classof(const APIRecord *Record) {
+    return Record->getKind() == RK_ObjCCategoryModule;
+  }
+
+private:
+  virtual void anchor();
+};
+
 /// This holds information associated with Objective-C interfaces/classes.
 struct ObjCInterfaceRecord : ObjCContainerRecord {
   SymbolReference SuperClass;
@@ -668,6 +689,9 @@
                           DeclarationFragments SubHeading,
                           bool IsFromSystemHeader);
 
+  ObjCCategoryModuleRecord *addObjCCategoryModule(StringRef Name,
+                                                  StringRef USR);
+
   /// Create and add an Objective-C category record into the API set.
   ///
   /// Note: the caller is responsible for keeping the StringRef \p Name and
@@ -679,7 +703,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.
   ///
@@ -795,6 +819,9 @@
   const RecordMap<ObjCCategoryRecord> &getObjCCategories() const {
     return ObjCCategories;
   }
+  const RecordMap<ObjCCategoryModuleRecord> &getObjCCategoryModule() const {
+    return ObjCCategoryModule;
+  }
   const RecordMap<ObjCInterfaceRecord> &getObjCInterfaces() const {
     return ObjCInterfaces;
   }
@@ -848,6 +875,7 @@
   RecordMap<EnumRecord> Enums;
   RecordMap<StructRecord> Structs;
   RecordMap<ObjCCategoryRecord> ObjCCategories;
+  RecordMap<ObjCCategoryModuleRecord> ObjCCategoryModule;
   RecordMap<ObjCInterfaceRecord> ObjCInterfaces;
   RecordMap<ObjCProtocolRecord> ObjCProtocols;
   RecordMap<MacroDefinitionRecord> Macros;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to