From 6045dcbfc78cbd83be728e438df6788f79736264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 01:49:53 -0800 Subject: [PATCH 1/9] Fix parsing of Objective-C interface generic inheritance Summary: Doxygen incorrectly parses Objective-C interface declarations with protocol conformance. For example: ```objc interface RCTAppearance : RCTEventEmitter ``` Doxygen splits this into **two separate base classes** in the XML: ```xml RCTEventEmitter <RCTBridgeModule> ``` This caused the parser to output: ``` interface RCTAppearance : public RCTEventEmitter, public { ``` Instead of the expected: ``` interface RCTAppearance : public RCTEventEmitter { ``` The fix detects when a "base class" name starts and ends with `<...>` (indicating it's a protocol conformance) and combines it with the preceding actual base class name. Differential Revision: D94351731 --- scripts/cxx-api/parser/builders.py | 19 ++++++++++++++++++- .../snapshot.api | 3 +++ .../test.h | 16 ++++++++++++++++ .../snapshot.api | 2 ++ .../test.h | 16 ++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/test.h create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index 85d11f7ccc63..3361c25a35d7 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -465,7 +465,24 @@ def create_interface_scope( interface_scope = snapshot.create_interface(interface_name) base_classes = get_base_classes(scope_def, base_class=InterfaceScopeKind.Base) - interface_scope.kind.add_base(base_classes) + + # Doxygen incorrectly splits "Foo " into separate base classes: + # "Foo", "", "". Combine them back into "Foo ". + combined_bases = [] + for base in base_classes: + if base.name.startswith("<") and base.name.endswith(">") and combined_bases: + prev_name = combined_bases[-1].name + protocol = base.name[1:-1] # Strip < and > + if "<" in prev_name and prev_name.endswith(">"): + # Previous base already has protocols, merge inside the brackets + combined_bases[-1].name = f"{prev_name[:-1]}, {protocol}>" + else: + # First protocol for this base class + combined_bases[-1].name = f"{prev_name} <{protocol}>" + else: + combined_bases.append(base) + + interface_scope.kind.add_base(combined_bases) interface_scope.location = scope_def.location.file _process_objc_sections( diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/snapshot.api new file mode 100644 index 000000000000..e10fec618c71 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/snapshot.api @@ -0,0 +1,3 @@ +interface RCTAppearance : public RCTEventEmitter { + public virtual instancetype init(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/test.h b/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/test.h new file mode 100644 index 000000000000..dc261f9b5083 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_class_with_generic_inheritance/test.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +@interface RCTAppearance : RCTEventEmitter +- (instancetype)init; +@end + +} // namespace test diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/snapshot.api new file mode 100644 index 000000000000..f66dd58f6c0e --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/snapshot.api @@ -0,0 +1,2 @@ +interface RCTAlertManager : public NSObject { +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/test.h b/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/test.h new file mode 100644 index 000000000000..02d7e217d22b --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_class_with_multiple_protocol_conformances/test.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace test { + +@interface RCTAlertManager : NSObject + +@end + +} // namespace test From 40428dad436300380ea62da830c1d8c4a22e8ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 01:49:53 -0800 Subject: [PATCH 2/9] Fix parsing of Objective-C block properties Summary: Doxygen splits block property types across `` and `` elements. For example: ```objc property (nonatomic, copy) void (^eventInterceptor)(NSString *eventName, NSDictionary *event, NSNumber *reactTag); ``` Produces XML like: ```xml void(^ eventInterceptor )(NSString *eventName, NSDictionary *event, NSNumber *reactTag) ``` This caused the parser to output incomplete types. The fix detects when the property type ends with `(^` and combines it with the property name and argsstring: ``` property (copy) void(^eventInterceptor)(NSString *eventName, NSDictionary *event, NSNumber *reactTag); ``` Differential Revision: D94366205 Reviewed By: cipolleschi --- scripts/cxx-api/parser/builders.py | 10 ++++++++++ scripts/cxx-api/parser/member.py | 6 +++++- .../snapshot.api | 5 +++++ .../test.h | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/test.h diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index 3361c25a35d7..4dbd7466596f 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -337,6 +337,16 @@ def get_property_member( is_readable = getattr(member_def, "readable", "no") == "yes" is_writable = getattr(member_def, "writable", "no") == "yes" + # Handle block properties: Doxygen splits the block type across and + # = "void(^" + # = ")(NSString *eventName, NSDictionary *event, NSNumber *reactTag)" + # We need to combine them: "void(^eventInterceptor)(NSString *, NSDictionary *, NSNumber *)" + if property_type.endswith("(^"): + argsstring = member_def.get_argsstring() + if argsstring: + property_type = f"{property_type}{property_name}{argsstring}" + property_name = "" + return PropertyMember( property_name, property_type, diff --git a/scripts/cxx-api/parser/member.py b/scripts/cxx-api/parser/member.py index 407fa078aecf..2069aa80a84e 100644 --- a/scripts/cxx-api/parser/member.py +++ b/scripts/cxx-api/parser/member.py @@ -404,7 +404,11 @@ def to_string( if self.is_static: result += "static " - result += f"@property {attrs_str}{self.type} {name};" + # For block properties, name is embedded in the type (e.g., "void(^eventInterceptor)(args)") + if name: + result += f"@property {attrs_str}{self.type} {name};" + else: + result += f"@property {attrs_str}{self.type};" return result diff --git a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api new file mode 100644 index 000000000000..01e1b9d3f9cc --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api @@ -0,0 +1,5 @@ +interface RCTInterfaceWithBlockProperty : public NSObject { + public @property (copy) NSString *(^blockWithReturn)(int value); + public @property (copy) void(^eventInterceptor)(NSString *eventName, NSDictionary *event, NSNumber *reactTag); + public @property (copy) void(^simpleBlock)(void); +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/test.h b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/test.h new file mode 100644 index 000000000000..e9fc1c8b9658 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/test.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@interface RCTInterfaceWithBlockProperty : NSObject + +@property (nonatomic, copy, nullable) void (^eventInterceptor) + (NSString *eventName, NSDictionary *event, NSNumber *reactTag); +@property (nonatomic, copy) void (^simpleBlock)(void); +@property (nonatomic, copy) NSString * (^blockWithReturn)(int value); + +@end From 41b80d4f313c5cb8d88f35b0737526570e4189ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 01:49:53 -0800 Subject: [PATCH 3/9] Remove `__deprecated_msg` from the snapshot Summary: Set predefined `__deprecated_msg` to empty string in the `.doxygen.config.template` as doxygen has problem with parsing and produces malformed xml. Differential Revision: D94517576 --- .../react-native/.doxygen.config.template | 2 +- .../manual_test/.doxygen.config.template | 2 +- .../tests/snapshots/.doxygen.config.template | 2 +- .../should_handle_deprecated_msg/snapshot.api | 16 +++++++++ .../should_handle_deprecated_msg/test.h | 34 +++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/test.h diff --git a/packages/react-native/.doxygen.config.template b/packages/react-native/.doxygen.config.template index 72db8de8be90..cd4047ae375f 100644 --- a/packages/react-native/.doxygen.config.template +++ b/packages/react-native/.doxygen.config.template @@ -2443,7 +2443,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" ${PREDEFINED} +PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" ${PREDEFINED} # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/manual_test/.doxygen.config.template b/scripts/cxx-api/manual_test/.doxygen.config.template index 80290595ec93..6f762bb4b57d 100644 --- a/scripts/cxx-api/manual_test/.doxygen.config.template +++ b/scripts/cxx-api/manual_test/.doxygen.config.template @@ -2442,7 +2442,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" ${PREDEFINED} +PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" ${PREDEFINED} # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/tests/snapshots/.doxygen.config.template b/scripts/cxx-api/tests/snapshots/.doxygen.config.template index a01a146395a5..13cbc7b57375 100644 --- a/scripts/cxx-api/tests/snapshots/.doxygen.config.template +++ b/scripts/cxx-api/tests/snapshots/.doxygen.config.template @@ -2442,7 +2442,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" +PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api new file mode 100644 index 000000000000..65e0334c1d09 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api @@ -0,0 +1,16 @@ +interface RCTDeprecatedInterface { + public virtual void customBubblingEventTypes(); + public virtual void legacyTitle(); + public virtual void normalMethod(); + public virtual void oldMethod(); +} + +interface RCTTestInterface { + public virtual NSArray * deprecatedMethod:(id param); + public virtual void normalMethod:(NSString * name); +} + +protocol RCTDeprecatedProtocol { + public virtual void deprecatedProtocolMethod(); + public virtual void normalProtocolMethod(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/test.h b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/test.h new file mode 100644 index 000000000000..b3d47328979e --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/test.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@interface RCTTestInterface + +- (void)normalMethod:(NSString *)name; + +- (NSArray *)deprecatedMethod:(id)param __deprecated_msg("Use newMethod instead."); + +@end + +@interface RCTDeprecatedInterface + +- (void)normalMethod; + +- (void)oldMethod __deprecated_msg("Use newMethod instead."); + +- (void)customBubblingEventTypes __deprecated_msg("Use RCTBubblingEventBlock props instead."); + +- (void)legacyTitle __deprecated_msg("This API will be removed along with the legacy architecture."); + +@end + +@protocol RCTDeprecatedProtocol + +- (void)normalProtocolMethod; + +- (void)deprecatedProtocolMethod __deprecated_msg("Protocol method is deprecated."); + +@end From c48de2adbd3e58494c275e735b623786fc72fe85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 01:49:53 -0800 Subject: [PATCH 4/9] Add doxygen input filter to strip block comments Summary: Adds a doxygen input filter (`input_filters/doxygen_strip_comments.py`) that strips block comments from source files before doxygen parses them. This prevents doxygen from incorrectly parsing Objective-C code examples (like `interface`, `protocol`) within documentation comments as actual code declarations. For example, a doc comment containing: ``` /** * Example: * interface RCT_EXTERN_MODULE(MyModule, NSObject) * end */ ``` Was being parsed by doxygen as an actual interface declaration, resulting in malformed output like `interface RCT_EXTERN_MODULE {}` in the API snapshot. The filter preserves line numbers by replacing comment content with the equivalent number of newlines, ensuring error messages remain accurate. Differential Revision: D94534964 --- .../react-native/.doxygen.config.template | 16 ++++++++- .../manual_test/.doxygen.config.template | 15 +++++++- .../tests/snapshots/.doxygen.config.template | 14 +++++++- .../should_strip_objc_macros/snapshot.api | 14 ++++++++ .../snapshots/should_strip_objc_macros/test.h | 36 +++++++++++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_strip_objc_macros/test.h diff --git a/packages/react-native/.doxygen.config.template b/packages/react-native/.doxygen.config.template index cd4047ae375f..d7f5cd197aa9 100644 --- a/packages/react-native/.doxygen.config.template +++ b/packages/react-native/.doxygen.config.template @@ -2443,7 +2443,21 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" ${PREDEFINED} +PREDEFINED = FOLLY_PACK_PUSH="" \ + FOLLY_PACK_POP="" \ + FOLLY_PACK_ATTR="" \ + __attribute__(x)="" \ + __deprecated_msg(x)="" \ + NS_REQUIRES_SUPER="" \ + NS_UNAVAILABLE="" \ + CF_RETURNS_NOT_RETAINED="" \ + NS_DESIGNATED_INITIALIZER="" \ + NS_DESIGNATED_INITIALIZER="" \ + RCT_EXTERN="" \ + RCT_DEPRECATED="" \ + RCT_EXTERN_MODULE="" \ + API_AVAILABLE(x)="" \ + ${PREDEFINED} # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/manual_test/.doxygen.config.template b/scripts/cxx-api/manual_test/.doxygen.config.template index 6f762bb4b57d..15952235cae3 100644 --- a/scripts/cxx-api/manual_test/.doxygen.config.template +++ b/scripts/cxx-api/manual_test/.doxygen.config.template @@ -2442,7 +2442,20 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" ${PREDEFINED} +PREDEFINED = FOLLY_PACK_PUSH="" \ + FOLLY_PACK_POP="" \ + FOLLY_PACK_ATTR="" \ + __attribute__(x)="" \ + __deprecated_msg(x)="" \ + NS_REQUIRES_SUPER="" \ + NS_UNAVAILABLE="" \ + CF_RETURNS_NOT_RETAINED="" \ + NS_DESIGNATED_INITIALIZER="" \ + RCT_EXTERN="" \ + RCT_DEPRECATED="" \ + RCT_EXTERN_MODULE="" \ + API_AVAILABLE(x)="" \ + ${PREDEFINED} # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/tests/snapshots/.doxygen.config.template b/scripts/cxx-api/tests/snapshots/.doxygen.config.template index 13cbc7b57375..50d418c394f7 100644 --- a/scripts/cxx-api/tests/snapshots/.doxygen.config.template +++ b/scripts/cxx-api/tests/snapshots/.doxygen.config.template @@ -2442,7 +2442,19 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" FOLLY_PACK_POP="" FOLLY_PACK_ATTR="" __attribute__(x)="" __deprecated_msg(x)="" +PREDEFINED = FOLLY_PACK_PUSH="" \ + FOLLY_PACK_POP="" \ + FOLLY_PACK_ATTR="" \ + __attribute__(x)="" \ + __deprecated_msg(x)="" \ + NS_REQUIRES_SUPER="" \ + NS_UNAVAILABLE="" \ + CF_RETURNS_NOT_RETAINED="" \ + NS_DESIGNATED_INITIALIZER="" \ + RCT_EXTERN="" \ + RCT_DEPRECATED="" \ + RCT_EXTERN_MODULE="" \ + API_AVAILABLE(x)="" # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api new file mode 100644 index 000000000000..b0c18447476a --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api @@ -0,0 +1,14 @@ +interface RCTTestMacros { + public @property (strong, readonly) dispatch_queue_t methodQueue; + public @property (weak, readonly) id bridge; + public virtual instancetype initWithDelegate:options:(id delegate, NSDictionary * options); + public virtual instancetype initWithName:(NSString * name); + public virtual static UIUserInterfaceStyle userInterfaceStyle(); + public virtual void deprecatedMethod(); +} + +protocol RCTTestProtocol { + public @property (assign, readonly) NSString * name; + public virtual void normalMethod(); + public virtual void requiredMethod(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/test.h b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/test.h new file mode 100644 index 000000000000..d03ef29db63b --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/test.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@interface RCTTestMacros + +- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithDelegate:(id)delegate options:(NSDictionary *)options NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue RCT_DEPRECATED; + +@property (nonatomic, weak, readonly) id bridge RCT_DEPRECATED; + +- (void)deprecatedMethod RCT_DEPRECATED; + ++ (UIUserInterfaceStyle)userInterfaceStyle API_AVAILABLE(ios(12)); + +@end + +RCT_EXTERN void RCTExternFunction(const char *input, NSString **output); + +RCT_EXTERN NSString *RCTParseType(const char **input); + +@protocol RCTTestProtocol + +- (void)normalMethod; + +- (void)requiredMethod NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly) NSString *name RCT_DEPRECATED; + +@end From a1cb055b92797fa7ebb66586aa8a8564f26d26a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 02:10:00 -0800 Subject: [PATCH 5/9] Add doxygen input filter to strip block comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Adds a doxygen input filter (`input_filters/doxygen_strip_comments.py`) that strips block comments from source files before doxygen parses them. This prevents doxygen from incorrectly parsing Objective-C code examples (like `interface`, `protocol`) within documentation comments as actual code declarations. For example, a doc comment containing: ``` /** * Example: * interface RCT_EXTERN_MODULE(MyModule, NSObject) * end */ ``` Was being parsed by doxygen as an actual interface declaration, resulting in malformed output like `interface RCT_EXTERN_MODULE {}` in the API snapshot. ## Changes 1. **Input Filter**: Added `doxygen_strip_comments.py` that replaces block comments with equivalent newlines to preserve line numbers. 2. **Fixed Empty Snapshots**: Fixed the `__main__.py` to properly pass the `${DOXYGEN_INPUT_FILTER}` placeholder value to doxygen config, which was causing empty snapshot generation. 3. **Normalized Pointer Spacing**: Added `normalize_pointer_spacing()` function to normalize doxygen's output: - `NSString *` → `NSString*` - `int &` → `int&` - `T &&` → `T&&` 4. **Added Snapshot Test**: Created `should_not_parse_interface_from_doc_comment` test case demonstrating the issue. Differential Revision: D94538938 --- .../react-native/.doxygen.config.template | 2 +- .../input_filters/doxygen_strip_comments.py | 60 ++++++++++++++++ .../manual_test/.doxygen.config.template | 2 +- scripts/cxx-api/parser/__main__.py | 22 ++++-- .../tests/snapshots/.doxygen.config.template | 2 +- .../snapshot.api | 3 + .../test.h | 18 +++++ scripts/cxx-api/tests/test_input_filters.py | 71 +++++++++++++++++++ scripts/cxx-api/tests/test_snapshots.py | 31 ++++++-- 9 files changed, 197 insertions(+), 14 deletions(-) create mode 100755 scripts/cxx-api/input_filters/doxygen_strip_comments.py create mode 100644 scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/test.h create mode 100644 scripts/cxx-api/tests/test_input_filters.py diff --git a/packages/react-native/.doxygen.config.template b/packages/react-native/.doxygen.config.template index d7f5cd197aa9..4f982c734bc2 100644 --- a/packages/react-native/.doxygen.config.template +++ b/packages/react-native/.doxygen.config.template @@ -1123,7 +1123,7 @@ IMAGE_PATH = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. -INPUT_FILTER = +INPUT_FILTER = ${DOXYGEN_INPUT_FILTER} # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the diff --git a/scripts/cxx-api/input_filters/doxygen_strip_comments.py b/scripts/cxx-api/input_filters/doxygen_strip_comments.py new file mode 100755 index 000000000000..d379f7d1c9d2 --- /dev/null +++ b/scripts/cxx-api/input_filters/doxygen_strip_comments.py @@ -0,0 +1,60 @@ +#!/usr/bin/env fbpython +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +Doxygen input filter to strip block comments from source files. + +This prevents Doxygen from incorrectly parsing code examples within +documentation comments as actual code declarations (e.g., @interface, +@protocol examples in doc comments being parsed as real interfaces). + +Usage in doxygen config: + INPUT_FILTER = "python3 /path/to/doxygen_strip_comments.py" +""" + +import re +import sys + + +def strip_block_comments(content: str) -> str: + """ + Remove all block comments (/* ... */ and /** ... */) from content. + Preserves line count by replacing comment content with newlines. + """ + + def replace_with_newlines(match: re.Match) -> str: + # Count newlines in original comment to preserve line numbers + newline_count = match.group().count("\n") + return "\n" * newline_count + + # Pattern to match block comments (non-greedy) + comment_pattern = re.compile(r"/\*[\s\S]*?\*/") + + return comment_pattern.sub(replace_with_newlines, content) + + +def main(): + if len(sys.argv) < 2: + print("Usage: doxygen_strip_comments.py ", file=sys.stderr) + sys.exit(1) + + filename = sys.argv[1] + + try: + with open(filename, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + + filtered = strip_block_comments(content) + print(filtered, end="") + except Exception as e: + # On error, output original content to not break the build + print(f"Warning: Filter error for {filename}: {e}", file=sys.stderr) + with open(filename, "r", encoding="utf-8", errors="replace") as f: + print(f.read(), end="") + + +if __name__ == "__main__": + main() diff --git a/scripts/cxx-api/manual_test/.doxygen.config.template b/scripts/cxx-api/manual_test/.doxygen.config.template index 15952235cae3..cac0975942a6 100644 --- a/scripts/cxx-api/manual_test/.doxygen.config.template +++ b/scripts/cxx-api/manual_test/.doxygen.config.template @@ -1122,7 +1122,7 @@ IMAGE_PATH = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. -INPUT_FILTER = +INPUT_FILTER = ${DOXYGEN_INPUT_FILTER} # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the diff --git a/scripts/cxx-api/parser/__main__.py b/scripts/cxx-api/parser/__main__.py index 96673ad2dfe5..2d7a8cfd52c0 100644 --- a/scripts/cxx-api/parser/__main__.py +++ b/scripts/cxx-api/parser/__main__.py @@ -33,6 +33,7 @@ def build_doxygen_config( include_directories: list[str] = None, exclude_patterns: list[str] = None, definitions: dict[str, str | int] = None, + input_filter: str = None, ) -> None: if include_directories is None: include_directories = [] @@ -53,6 +54,8 @@ def build_doxygen_config( ] ) + input_filter_str = input_filter if input_filter else "" + # read the template file with open(os.path.join(directory, ".doxygen.config.template")) as f: template = f.read() @@ -62,6 +65,7 @@ def build_doxygen_config( template.replace("${INPUTS}", include_directories_str) .replace("${EXCLUDE_PATTERNS}", exclude_patterns_str) .replace("${PREDEFINED}", definitions_str) + .replace("${DOXYGEN_INPUT_FILTER}", input_filter_str) ) # write the config file @@ -77,6 +81,7 @@ def build_snapshot_for_view( definitions: dict[str, str | int], output_dir: str, verbose: bool = True, + input_filter: str = None, ) -> None: if verbose: print(f"Generating API view: {api_view}") @@ -87,6 +92,7 @@ def build_snapshot_for_view( include_directories=include_directories, exclude_patterns=exclude_patterns, definitions=definitions, + input_filter=input_filter, ) # If there is already a doxygen output directory, delete it @@ -188,6 +194,17 @@ def main(): if verbose and args.codegen_path: print(f"Codegen output path: {os.path.abspath(args.codegen_path)}") + input_filter_path = os.path.join( + get_react_native_dir(), + "scripts", + "cxx-api", + "input_filters", + "doxygen_strip_comments.py", + ) + input_filter = None + if os.path.exists(input_filter_path): + input_filter = f"python3 {input_filter_path}" + # Parse config file config_path = os.path.join( get_react_native_dir(), "scripts", "cxx-api", "config.yml" @@ -219,6 +236,7 @@ def build_snapshots(output_dir: str, verbose: bool) -> None: definitions={}, output_dir=output_dir, verbose=verbose, + input_filter=input_filter, ) if verbose: @@ -245,7 +263,3 @@ def build_snapshots(output_dir: str, verbose: bool) -> None: ) ) build_snapshots(output_dir, verbose=True) - - -if __name__ == "__main__": - main() diff --git a/scripts/cxx-api/tests/snapshots/.doxygen.config.template b/scripts/cxx-api/tests/snapshots/.doxygen.config.template index 50d418c394f7..f5f568360014 100644 --- a/scripts/cxx-api/tests/snapshots/.doxygen.config.template +++ b/scripts/cxx-api/tests/snapshots/.doxygen.config.template @@ -1122,7 +1122,7 @@ IMAGE_PATH = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. -INPUT_FILTER = +INPUT_FILTER = ${DOXYGEN_INPUT_FILTER} # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the diff --git a/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/snapshot.api b/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/snapshot.api new file mode 100644 index 000000000000..e5a54251d3c4 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/snapshot.api @@ -0,0 +1,3 @@ +interface RCTRealInterface { + public virtual void realMethod(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/test.h b/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/test.h new file mode 100644 index 000000000000..3ffcc2f84748 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_not_parse_interface_from_doc_comment/test.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Example of how to use this module: + * + * @interface RCT_EXTERN_MODULE(MyModule, NSObject) + * @end + */ +@interface RCTRealInterface + +- (void)realMethod; + +@end diff --git a/scripts/cxx-api/tests/test_input_filters.py b/scripts/cxx-api/tests/test_input_filters.py new file mode 100644 index 000000000000..2aa3bf50881d --- /dev/null +++ b/scripts/cxx-api/tests/test_input_filters.py @@ -0,0 +1,71 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import annotations + +import unittest + +from ..input_filters.doxygen_strip_comments import strip_block_comments + + +class TestDoxygenStripComments(unittest.TestCase): + def test_strips_single_line_block_comment(self): + content = "/* comment */ code" + result = strip_block_comments(content) + self.assertEqual(result, " code") + + def test_strips_multiline_block_comment(self): + content = """/** + * Doc comment + * with multiple lines + */ +@interface RealInterface +@end""" + result = strip_block_comments(content) + # Should preserve 4 newlines (one for each line in the comment) + self.assertEqual( + result, + """\n\n\n +@interface RealInterface +@end""", + ) + + def test_preserves_code_outside_comments(self): + content = """@interface MyClass +- (void)method; +@end""" + result = strip_block_comments(content) + self.assertEqual(result, content) + + def test_strips_comment_with_objc_keywords(self): + """This is the main use case - stripping comments that contain @interface etc.""" + content = """/** + * Example: + * @interface RCT_EXTERN_MODULE(MyModule, NSObject) + * @end + */ +@interface RealInterface +@end""" + result = strip_block_comments(content) + self.assertNotIn("RCT_EXTERN_MODULE", result) + self.assertIn("@interface RealInterface", result) + + def test_handles_multiple_comments(self): + content = """/* first */ code /* second */ more""" + result = strip_block_comments(content) + self.assertEqual(result, " code more") + + def test_handles_empty_content(self): + result = strip_block_comments("") + self.assertEqual(result, "") + + def test_handles_no_comments(self): + content = "just code without comments" + result = strip_block_comments(content) + self.assertEqual(result, content) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/cxx-api/tests/test_snapshots.py b/scripts/cxx-api/tests/test_snapshots.py index c78687904d44..4b21fcd728f7 100644 --- a/scripts/cxx-api/tests/test_snapshots.py +++ b/scripts/cxx-api/tests/test_snapshots.py @@ -49,18 +49,21 @@ def _assert_text_equal_with_diff( tc.fail(diff) -def _get_doxygen_bin() -> str: - return os.environ.get("DOXYGEN_BIN", "doxygen") - - -def _generate_doxygen_api(case_dir_path: str, doxygen_config_path: str) -> None: +def _generate_doxygen_api( + case_dir_path: str, doxygen_config_path: str, filter_script_path: str | None = None +) -> None: """Run doxygen to generate XML API documentation.""" - doxygen_bin = _get_doxygen_bin() + env = os.environ.copy() + if filter_script_path: + env["DOXYGEN_INPUT_FILTER"] = f"python3 {filter_script_path}" + + doxygen_bin = env.get("DOXYGEN_BIN", "doxygen") result = subprocess.run( [doxygen_bin, doxygen_config_path], cwd=case_dir_path, capture_output=True, text=True, + env=env, ) if result.returncode != 0: raise RuntimeError(f"Doxygen failed: {result.stderr}") @@ -101,8 +104,22 @@ def _test(self: unittest.TestCase) -> None: case_dir_path = tests_root_path / case_dir.name doxygen_config_path = tests_root_path / ".doxygen.config.template" + # Find the filter script in the package resources + pkg_root = ir.files(__package__ if __package__ else "__main__") + filter_script = ( + pkg_root.parent / "input_filters" / "doxygen_strip_comments.py" + ) + + # Get real filesystem path for filter script if it exists + filter_script_path = None + if filter_script.is_file(): + with ir.as_file(filter_script) as fs_path: + filter_script_path = str(fs_path) + # Run doxygen to generate the XML - _generate_doxygen_api(str(case_dir_path), str(doxygen_config_path)) + _generate_doxygen_api( + str(case_dir_path), str(doxygen_config_path), filter_script_path + ) # Parse the generated XML xml_dir = case_dir_path / "api" / "xml" From ebec20c96c29af4c2eb74e2e14e3e3ae5750a037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 02:12:18 -0800 Subject: [PATCH 6/9] Normalize pointer and reference spacing in parser output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fixes inconsistent spacing around pointer (`*`) and reference (`&`, `&&`) symbols in the parser output. Doxygen outputs types with a space before `*` and `&` (e.g., `NSString *`), which is inconsistent. This diff adds `normalize_pointer_spacing()` to standardize the output: - `NSString *` → `NSString*` - `int &` → `int&` - `T &&` → `T&&` - For function arguments: `NSString *name` → `NSString* name` ## Changes 1. Added `normalize_pointer_spacing()` function in `text_resolution.py` 2. Applied normalization in `resolve_ref_text_name()` and `resolve_linked_text_name()` 3. Applied normalization to block property argsstrings in `builders.py` 4. Updated all affected snapshot files to use normalized spacing Differential Revision: D95078214 --- scripts/cxx-api/parser/builders.py | 5 ++- scripts/cxx-api/parser/utils/__init__.py | 1 + .../cxx-api/parser/utils/text_resolution.py | 40 ++++++++++++++++++- .../snapshot.api | 2 +- .../should_handle_concept/snapshot.api | 2 +- .../should_handle_concept_indent/snapshot.api | 8 ++-- .../should_handle_deprecated_msg/snapshot.api | 4 +- .../snapshot.api | 2 +- .../snapshot.api | 4 +- .../snapshot.api | 4 +- .../snapshot.api | 2 +- .../snapshot.api | 4 +- .../snapshot.api | 8 ++-- .../snapshot.api | 4 +- .../snapshot.api | 2 +- .../snapshot.api | 2 +- .../snapshot.api | 2 +- .../snapshot.api | 4 +- .../snapshot.api | 2 +- .../snapshot.api | 2 +- .../should_strip_objc_macros/snapshot.api | 6 +-- 21 files changed, 75 insertions(+), 35 deletions(-) diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index 4dbd7466596f..e3c0b772818c 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -33,6 +33,7 @@ Argument, extract_qualifiers, InitializerType, + normalize_pointer_spacing, parse_qualified_path, resolve_linked_text_name, ) @@ -344,7 +345,9 @@ def get_property_member( if property_type.endswith("(^"): argsstring = member_def.get_argsstring() if argsstring: - property_type = f"{property_type}{property_name}{argsstring}" + # Normalize pointer spacing in the argsstring + normalized_argsstring = normalize_pointer_spacing(argsstring) + property_type = f"{property_type}{property_name}{normalized_argsstring}" property_name = "" return PropertyMember( diff --git a/scripts/cxx-api/parser/utils/__init__.py b/scripts/cxx-api/parser/utils/__init__.py index 25fd05b4183a..248136e621f9 100644 --- a/scripts/cxx-api/parser/utils/__init__.py +++ b/scripts/cxx-api/parser/utils/__init__.py @@ -18,6 +18,7 @@ extract_namespace_from_refid, InitializerType, normalize_angle_brackets, + normalize_pointer_spacing, resolve_linked_text_name, ) from .type_qualification import qualify_arguments, qualify_parsed_type, qualify_type_str diff --git a/scripts/cxx-api/parser/utils/text_resolution.py b/scripts/cxx-api/parser/utils/text_resolution.py index 497e1267a5de..8d5e7c46ad2f 100644 --- a/scripts/cxx-api/parser/utils/text_resolution.py +++ b/scripts/cxx-api/parser/utils/text_resolution.py @@ -83,13 +83,46 @@ def extract_namespace_from_refid(refid: str) -> str: def normalize_angle_brackets(text: str) -> str: """Doxygen adds spaces around < and > to avoid XML ambiguity. - e.g. "NSArray< id< RCTBridgeMethod > > *" -> "NSArray> *" + e.g. "NSArray< id< RCTBridgeMethod > > *" -> "NSArray>*" """ text = re.sub(r"<\s+", "<", text) text = re.sub(r"\s+>", ">", text) return text +def normalize_pointer_spacing(text: str) -> str: + """Normalize spacing around pointer (*) and reference (&, &&) symbols. + + Doxygen outputs types with a space before * and &, e.g.: + - "NSString *" -> "NSString*" + - "int &" -> "int&" + - "T &&" -> "T&&" + + But for function arguments like "NSString *name", we want "NSString* name" + (space moved from before * to after *). + + This normalizes to have no space before the pointer/reference symbol, + while preserving the space after if there's an identifier following. + """ + # For patterns like "Type *name" -> "Type* name" (move space from before to after) + # Match: word/type character or > followed by space(s), then *, then word char + text = re.sub(r"(\w|>)\s+\*(\w)", r"\1* \2", text) + # For patterns like "Type *" at end or before comma/paren/angle/open-paren -> "Type*" + text = re.sub(r"(\w|>)\s+\*(?=\s*[,)>(]|$)", r"\1*", text) + # For patterns like "Type *" followed by another * (pointer to pointer) + text = re.sub(r"(\w|>)\s+\*(?=\*)", r"\1*", text) + + # Same for && (rvalue reference) + text = re.sub(r"(\w|>)\s+&&(\w)", r"\1&& \2", text) + text = re.sub(r"(\w|>)\s+&&(?=\s*[,)>(]|$)", r"\1&&", text) + + # Same for & (lvalue reference) - but don't match && + text = re.sub(r"(\w|>)\s+&(?!&)(\w)", r"\1& \2", text) + text = re.sub(r"(\w|>)\s+&(?!&)(?=\s*[,)>(]|$)", r"\1&", text) + + return text + + class InitializerType(Enum): NONE = (0,) ASSIGNMENT = (1,) @@ -145,7 +178,10 @@ def resolve_linked_text_name( initialier_type = InitializerType.BRACE name = name[1:-1].strip() - return (normalize_angle_brackets(name.strip()), initialier_type) + return ( + normalize_pointer_spacing(normalize_angle_brackets(name.strip())), + initialier_type, + ) def _qualify_text_with_refid(text: str, refid: str) -> str: diff --git a/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api index f0d525d1c1c9..264305555fc9 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_class_public_method_arguments/snapshot.api @@ -10,7 +10,7 @@ class test::Clss { public void fn7(std::vector>> v); public void fn8(std::map> m); public void fn9(int(*)(int, int) callback); - public void fn10(void(*)(const char *, size_t) handler); + public void fn10(void(*)(const char*, size_t) handler); public void fn11(int(*(*fp)(int))(double)); public void fn12(int x = 5, std::string s = "default"); public void fn13(std::function f = nullptr); diff --git a/scripts/cxx-api/tests/snapshots/should_handle_concept/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_concept/snapshot.api index 1b3a474c3a09..e3b332471d7d 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_concept/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_concept/snapshot.api @@ -1,4 +1,4 @@ template -concept test::CSSParserSink = requires(CSSSyntaxParser &parser) { +concept test::CSSParserSink = requires(CSSSyntaxParser& parser) { { T::consume(parser) } -> std::convertible_to; }; diff --git a/scripts/cxx-api/tests/snapshots/should_handle_concept_indent/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_concept_indent/snapshot.api index 8ed0e695f88e..28740457b615 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_concept_indent/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_concept_indent/snapshot.api @@ -1,9 +1,9 @@ template -concept test::CSSUniqueComponentValueVisitors = CSSSyntaxVisitorReturn && (CSSComponentValueVisitor && ...) && - ((CSSFunctionVisitor ? 1 : 0) + ... + 0) <= 1 && - ((CSSPreservedTokenVisitor ? 1 : 0) + ... + 0) <= 1 && +concept test::CSSUniqueComponentValueVisitors = CSSSyntaxVisitorReturn&& (CSSComponentValueVisitor && ...) && + ((CSSFunctionVisitor ? 1 : 0) + ... + 0) <= 1&& + ((CSSPreservedTokenVisitor ? 1 : 0) + ... + 0) <= 1&& ((CSSSimpleBlockVisitor ? 1 : 0) + ... + 0) <= 1; template -concept test::CSSParserSink = requires(CSSSyntaxParser &parser) { +concept test::CSSParserSink = requires(CSSSyntaxParser& parser) { { T::consume(parser) } -> std::convertible_to; }; diff --git a/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api index 65e0334c1d09..3e8df8d033ed 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api @@ -6,8 +6,8 @@ interface RCTDeprecatedInterface { } interface RCTTestInterface { - public virtual NSArray * deprecatedMethod:(id param); - public virtual void normalMethod:(NSString * name); + public virtual NSArray* deprecatedMethod:(id param); + public virtual void normalMethod:(NSString* name); } protocol RCTDeprecatedProtocol { diff --git a/scripts/cxx-api/tests/snapshots/should_handle_function_pointer_variable/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_function_pointer_variable/snapshot.api index 9ad3ff78f6ba..2ca7bdec3361 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_function_pointer_variable/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_function_pointer_variable/snapshot.api @@ -1,5 +1,5 @@ struct test::FunctionPointers { public int(*withReturn)(double, float); - public void *(*returnsPointer)(const char *); public void(*simple)(int); + public void*(*returnsPointer)(const char *); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api index 01e1b9d3f9cc..4293bf02625e 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_block_property/snapshot.api @@ -1,5 +1,5 @@ interface RCTInterfaceWithBlockProperty : public NSObject { - public @property (copy) NSString *(^blockWithReturn)(int value); - public @property (copy) void(^eventInterceptor)(NSString *eventName, NSDictionary *event, NSNumber *reactTag); + public @property (copy) NSString*(^blockWithReturn)(int value); + public @property (copy) void(^eventInterceptor)(NSString* eventName, NSDictionary* event, NSNumber* reactTag); public @property (copy) void(^simpleBlock)(void); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_method/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_method/snapshot.api index 09d8acf70147..df124af7b029 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_method/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_method/snapshot.api @@ -1,5 +1,5 @@ interface RCTInterfaceWithMethod { - public virtual NSString * getTitle(); + public virtual NSString* getTitle(); public virtual void doSomething(); - public virtual void processValue:withName:(int value, NSString * name); + public virtual void processValue:withName:(int value, NSString* name); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_property/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_property/snapshot.api index b5649f46485f..a478845b7008 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_interface_with_property/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_interface_with_property/snapshot.api @@ -1,5 +1,5 @@ interface RCTInterfaceWithProperty { public @property (assign) BOOL enabled; - public @property (copy) NSString * title; + public @property (copy) NSString* title; public @property (weak) id delegate; } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_method_with_attribute/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_method_with_attribute/snapshot.api index bf63be615694..522f4d92cdd6 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_method_with_attribute/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_method_with_attribute/snapshot.api @@ -1,4 +1,4 @@ interface RCTTestInterface { - public virtual NSArray * interfaceDeprecatedMethod:(id param); - public virtual void normalMethod:(NSString * name); + public virtual NSArray* interfaceDeprecatedMethod:(id param); + public virtual void normalMethod:(NSString* name); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api index 32cebd4b82ff..d3f3804fada7 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api @@ -1,12 +1,12 @@ category RCTConvert(CategoryOne) { - public virtual static NSString * methodOne:(id json); + public virtual static NSString* methodOne:(id json); } category RCTConvert(CategoryTwo) { - public virtual static NSString * methodTwo:(id json); + public virtual static NSString* methodTwo:(id json); } interface RCTConvert : public NSObject { - public virtual static NSString * methodOne:(id json); - public virtual static NSString * methodTwo:(id json); + public virtual static NSString* methodOne:(id json); + public virtual static NSString* methodTwo:(id json); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_method/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_method/snapshot.api index 3cd0361bbc06..3ca06a48508b 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_method/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_method/snapshot.api @@ -1,5 +1,5 @@ protocol RCTProtocolWithMethod { - public virtual NSString * getTitle(); + public virtual NSString* getTitle(); public virtual void doSomething(); - public virtual void processValue:withName:(int value, NSString * name); + public virtual void processValue:withName:(int value, NSString* name); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_property/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_property/snapshot.api index c2322a45e0bb..1b95f53fce45 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_property/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_protocol_with_property/snapshot.api @@ -1,5 +1,5 @@ protocol RCTProtocolWithProperty { public @property (assign) BOOL enabled; - public @property (copy) NSString * title; + public @property (copy) NSString* title; public @property (weak) id delegate; } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api index ea4b88b13cd4..d814c71a0dd4 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_pure_virtual_trailing_return/snapshot.api @@ -1,5 +1,5 @@ class test::Base { - public virtual const char * getName() = 0; + public virtual const char* getName() = 0; public virtual std::vector getMethods() = 0; } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_unnamed_template_param_with_default_value/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_unnamed_template_param_with_default_value/snapshot.api index 2e9194009dbb..50a277c93f21 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_unnamed_template_param_with_default_value/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_unnamed_template_param_with_default_value/snapshot.api @@ -12,5 +12,5 @@ class test::Symbol { struct test::Value { template ::value || std::is_base_of::value || std::is_base_of::value || std::is_base_of::value>> - public Value(T && other); + public Value(T&& other); } diff --git a/scripts/cxx-api/tests/snapshots/should_not_requalify_globally_qualified_type/snapshot.api b/scripts/cxx-api/tests/snapshots/should_not_requalify_globally_qualified_type/snapshot.api index af18855b4a51..88badbbccb09 100644 --- a/scripts/cxx-api/tests/snapshots/should_not_requalify_globally_qualified_type/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_not_requalify_globally_qualified_type/snapshot.api @@ -1,7 +1,7 @@ struct other::Consumer { public ::test::inner::MyType create(); - public void process(const ::test::inner::MyType & param); - public void processPtr(::test::inner::MyType * param); + public void process(const ::test::inner::MyType& param); + public void processPtr(::test::inner::MyType* param); } diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_type_with_underscore/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_type_with_underscore/snapshot.api index 616760305e9b..1f67e496a5b5 100644 --- a/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_type_with_underscore/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_qualify_nested_template_type_with_underscore/snapshot.api @@ -1,5 +1,5 @@ template -facebook::react::detail::is_dynamic::type & facebook::react::jsArgAsDynamic(T && args, size_t n); +facebook::react::detail::is_dynamic::type& facebook::react::jsArgAsDynamic(T&& args, size_t n); template diff --git a/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api index a75e6cc67a16..ec31650e9467 100644 --- a/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_qualify_ptr_const_args/snapshot.api @@ -1 +1 @@ -void test::doSomething(const Node *const node, int x); +void test::doSomething(const Node* const node, int x); diff --git a/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api index b0c18447476a..e42d0b90b9d2 100644 --- a/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_strip_objc_macros/snapshot.api @@ -1,14 +1,14 @@ interface RCTTestMacros { public @property (strong, readonly) dispatch_queue_t methodQueue; public @property (weak, readonly) id bridge; - public virtual instancetype initWithDelegate:options:(id delegate, NSDictionary * options); - public virtual instancetype initWithName:(NSString * name); + public virtual instancetype initWithDelegate:options:(id delegate, NSDictionary* options); + public virtual instancetype initWithName:(NSString* name); public virtual static UIUserInterfaceStyle userInterfaceStyle(); public virtual void deprecatedMethod(); } protocol RCTTestProtocol { - public @property (assign, readonly) NSString * name; + public @property (assign, readonly) NSString* name; public virtual void normalMethod(); public virtual void requiredMethod(); } From b2ce35393089cbaa93bc853b15d77596b26c62dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 02:12:18 -0800 Subject: [PATCH 7/9] Include definitions behind __cplusplus macro Summary: Includes definitions behind the `__cplusplus` macro to also be visible in the API snapshot. Differential Revision: D95189538 --- scripts/cxx-api/config.yml | 1 + .../tests/snapshots/.doxygen.config.template | 3 ++- .../should_handle_ifdef_cplusplus/snapshot.api | 7 +++++++ .../should_handle_ifdef_cplusplus/test.h | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/test.h diff --git a/scripts/cxx-api/config.yml b/scripts/cxx-api/config.yml index 65ceefb00227..78a1f74a9859 100644 --- a/scripts/cxx-api/config.yml +++ b/scripts/cxx-api/config.yml @@ -61,6 +61,7 @@ ReactApple: - "*/platform/macos/*" - "*/platform/android/*" definitions: + __cplusplus: 1 variants: debug: definitions: diff --git a/scripts/cxx-api/tests/snapshots/.doxygen.config.template b/scripts/cxx-api/tests/snapshots/.doxygen.config.template index f5f568360014..00ae22eb9d26 100644 --- a/scripts/cxx-api/tests/snapshots/.doxygen.config.template +++ b/scripts/cxx-api/tests/snapshots/.doxygen.config.template @@ -2442,7 +2442,8 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = FOLLY_PACK_PUSH="" \ +PREDEFINED = __cplusplus=1 \ + FOLLY_PACK_PUSH="" \ FOLLY_PACK_POP="" \ FOLLY_PACK_ATTR="" \ __attribute__(x)="" \ diff --git a/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api new file mode 100644 index 000000000000..eb5f4fcd4d99 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api @@ -0,0 +1,7 @@ +category RCTBridgeProxy(Cxx) { + public @property (assign) int cxxOnlyProperty; +} + +interface RCTBridgeProxy : public NSObject { + public @property (assign) int cxxOnlyProperty; +} diff --git a/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/test.h b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/test.h new file mode 100644 index 000000000000..fe2a5d4afcc8 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/test.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@interface RCTBridgeProxy : NSObject + +@end + +@interface RCTBridgeProxy (Cxx) + +#ifdef __cplusplus +@property (nonatomic, readwrite) int cxxOnlyProperty; +#endif + +@end From dd0fc5c43b08095c2e1fb36eda48649937b9e029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 02:12:18 -0800 Subject: [PATCH 8/9] Filter category members from interface scope Summary: Doxygen incorrectly merges category members into the base interface XML output. This causes duplicate members to appear in both the category scope and the interface scope. The fix detects category members by checking if their definition contains the pattern `ClassName(CategoryName)::` (e.g., `RCTBridgeProxy(Cxx)::cxxOnlyProperty`) and filters them out when processing interface sections. Differential Revision: D95191148 --- scripts/cxx-api/parser/builders.py | 34 +++++++++++++++++++ .../snapshot.api | 1 - .../snapshot.api | 2 -- .../snapshot.api | 1 - .../snapshot.api | 2 -- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scripts/cxx-api/parser/builders.py b/scripts/cxx-api/parser/builders.py index e3c0b772818c..95c8dba7fafa 100644 --- a/scripts/cxx-api/parser/builders.py +++ b/scripts/cxx-api/parser/builders.py @@ -389,15 +389,40 @@ def create_enum_scope(snapshot: Snapshot, enum_def: compound.EnumdefType) -> Non ) +def _is_category_member(member_def: compound.MemberdefType) -> bool: + """ + Check if a member comes from a category based on its definition. + + Doxygen merges category members into the base interface XML output, but the + member's definition field contains the category name in parentheses, e.g.: + "int RCTBridgeProxy(Cxx)::cxxOnlyProperty" + + We use this to filter out category members from the interface scope. + """ + definition = member_def.definition + if not definition: + return False + + # Look for pattern: ClassName(CategoryName)::memberName + # The definition contains the qualified name with category info + return bool(re.search(r"\w+\([^)]+\)::", definition)) + + def _process_objc_sections( snapshot: Snapshot, scope, section_defs: list, location_file: str, scope_type: str, + filter_category_members: bool = False, ) -> None: """ Common section processing for protocols and interfaces. + + Args: + filter_category_members: If True, skip members that come from categories. + This is used for interfaces since Doxygen incorrectly merges category + members into the base interface XML output. """ for section_def in section_defs: kind = section_def.kind @@ -412,11 +437,15 @@ def _process_objc_sections( if member_type == "attrib": for member_def in section_def.memberdef: if member_def.kind == "variable": + if filter_category_members and _is_category_member(member_def): + continue scope.add_member( get_variable_member(member_def, visibility, is_static) ) elif member_type == "func": for function_def in section_def.memberdef: + if filter_category_members and _is_category_member(function_def): + continue scope.add_member( get_function_member(function_def, visibility, is_static) ) @@ -425,6 +454,8 @@ def _process_objc_sections( if member_def.kind == "enum": create_enum_scope(snapshot, member_def) elif member_def.kind == "typedef": + if filter_category_members and _is_category_member(member_def): + continue scope.add_member(get_typedef_member(member_def, visibility)) else: print( @@ -435,6 +466,8 @@ def _process_objc_sections( elif visibility == "property": for member_def in section_def.memberdef: if member_def.kind == "property": + if filter_category_members and _is_category_member(member_def): + continue scope.add_member( get_property_member(member_def, "public", is_static) ) @@ -504,6 +537,7 @@ def create_interface_scope( scope_def.sectiondef, scope_def.location.file, "interface", + filter_category_members=True, ) diff --git a/scripts/cxx-api/tests/snapshots/should_handle_category_with_method/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_category_with_method/snapshot.api index a09b6d80df73..55910acb2edb 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_category_with_method/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_category_with_method/snapshot.api @@ -3,5 +3,4 @@ category RCTConvert(UIActivityIndicatorView) { } interface RCTConvert : public NSObject { - public virtual static UIActivityIndicatorViewStyle UIActivityIndicatorViewStyle:(id json); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_category_with_multiple_methods/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_category_with_multiple_methods/snapshot.api index 1c99214249af..8159b254a2cb 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_category_with_multiple_methods/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_category_with_multiple_methods/snapshot.api @@ -4,6 +4,4 @@ category RCTConvert(Transform) { } interface RCTConvert : public NSObject { - public virtual static CATransform3D CATransform3D:(id json); - public virtual static RCTTransformOrigin RCTTransformOrigin:(id json); } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api index eb5f4fcd4d99..2ce3fc4be46b 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_ifdef_cplusplus/snapshot.api @@ -3,5 +3,4 @@ category RCTBridgeProxy(Cxx) { } interface RCTBridgeProxy : public NSObject { - public @property (assign) int cxxOnlyProperty; } diff --git a/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api b/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api index d3f3804fada7..b1e280967e5f 100644 --- a/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api +++ b/scripts/cxx-api/tests/snapshots/should_handle_multiple_categories/snapshot.api @@ -7,6 +7,4 @@ category RCTConvert(CategoryTwo) { } interface RCTConvert : public NSObject { - public virtual static NSString* methodOne:(id json); - public virtual static NSString* methodTwo:(id json); } From 3919a69bd3966a3eb105cfa4346ead25a245d42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Thu, 5 Mar 2026 02:31:34 -0800 Subject: [PATCH 9/9] Normalize Objective-C nullability annotations (#55898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/55898 Normalize Objective-C nullability annotations in the cxx-api parser to ensure consistent output in API snapshots. Objective-C has three different forms of nullability annotations that are semantically equivalent: - `nonnull`/`nullable` (context-sensitive keywords) - `_Nonnull`/`_Nullable` (type qualifiers) - `__nonnull`/`__nullable` (legacy Apple macros) This diff normalizes all forms to `_Nonnull`/`_Nullable` for consistency. **Examples:** - `nonnull NSString*` → `_Nonnull NSString*` - `NSString* __nonnull` → `NSString* _Nonnull` - `nullable id` → `_Nullable id` - `id __nullable` → `id _Nullable` - `NSString* _Nonnull` → `NSString* _Nonnull` (unchanged) Added a test case to verify the normalization works correctly. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D95195669 --- .../cxx-api/parser/utils/text_resolution.py | 28 ++++++++++++++++++- .../should_normalize_nullability/snapshot.api | 10 +++++++ .../should_normalize_nullability/test.h | 21 ++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 scripts/cxx-api/tests/snapshots/should_normalize_nullability/snapshot.api create mode 100644 scripts/cxx-api/tests/snapshots/should_normalize_nullability/test.h diff --git a/scripts/cxx-api/parser/utils/text_resolution.py b/scripts/cxx-api/parser/utils/text_resolution.py index 8d5e7c46ad2f..e9b67f64203e 100644 --- a/scripts/cxx-api/parser/utils/text_resolution.py +++ b/scripts/cxx-api/parser/utils/text_resolution.py @@ -90,6 +90,30 @@ def normalize_angle_brackets(text: str) -> str: return text +def normalize_nullability(text: str) -> str: + """Normalize Objective-C nullability annotations to a consistent form. + + There are three forms of nullability annotations in Objective-C: + - nonnull/nullable (context-sensitive keywords, typically used as prefix) + - _Nonnull/_Nullable (type qualifiers, can appear after the type) + - __nonnull/__nullable (legacy Apple macros, deprecated but still used) + + This function normalizes all forms to _Nonnull/_Nullable, which is the + most flexible form that can be used in any position. + """ + # Normalize __nonnull -> _Nonnull (must come before nonnull) + text = re.sub(r"\b__nonnull\b", "_Nonnull", text) + # Normalize nonnull -> _Nonnull (negative lookbehind to avoid _Nonnull) + text = re.sub(r"(? _Nullable (must come before nullable) + text = re.sub(r"\b__nullable\b", "_Nullable", text) + # Normalize nullable -> _Nullable (negative lookbehind to avoid _Nullable) + text = re.sub(r"(? str: """Normalize spacing around pointer (*) and reference (&, &&) symbols. @@ -179,7 +203,9 @@ def resolve_linked_text_name( name = name[1:-1].strip() return ( - normalize_pointer_spacing(normalize_angle_brackets(name.strip())), + normalize_nullability( + normalize_pointer_spacing(normalize_angle_brackets(name.strip())) + ), initialier_type, ) diff --git a/scripts/cxx-api/tests/snapshots/should_normalize_nullability/snapshot.api b/scripts/cxx-api/tests/snapshots/should_normalize_nullability/snapshot.api new file mode 100644 index 000000000000..dc16c53ac345 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_normalize_nullability/snapshot.api @@ -0,0 +1,10 @@ +interface NullabilityTest : public NSObject { + public @property (assign) NSString* nonnullProperty; + public @property (assign) NSString* nullableProperty; + public virtual NSString* _Nonnull legacyNonnullMethod(); + public virtual NSString* _Nonnull modernNonnullMethod(); + public virtual NSString* _Nullable legacyNullableMethod(); + public virtual NSString* _Nullable modernNullableMethod(); + public virtual _Nonnull NSString* nonnullMethod(); + public virtual _Nullable NSString* nullableMethod(); +} diff --git a/scripts/cxx-api/tests/snapshots/should_normalize_nullability/test.h b/scripts/cxx-api/tests/snapshots/should_normalize_nullability/test.h new file mode 100644 index 000000000000..b702b80276b8 --- /dev/null +++ b/scripts/cxx-api/tests/snapshots/should_normalize_nullability/test.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@interface NullabilityTest : NSObject + +// Different nullability annotation styles that should be normalized +- (nonnull NSString *)nonnullMethod; +- (nullable NSString *)nullableMethod; +- (NSString *__nonnull)legacyNonnullMethod; +- (NSString *__nullable)legacyNullableMethod; +- (NSString *_Nonnull)modernNonnullMethod; +- (NSString *_Nullable)modernNullableMethod; + +@property (nonatomic, nonnull) NSString *nonnullProperty; +@property (nonatomic, nullable) NSString *nullableProperty; + +@end