Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/react-native/.doxygen.config.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)="" ${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
Expand Down
60 changes: 60 additions & 0 deletions scripts/cxx-api/input_filters/doxygen_strip_comments.py
Original file line number Diff line number Diff line change
@@ -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 <filename>", 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()
17 changes: 15 additions & 2 deletions scripts/cxx-api/manual_test/.doxygen.config.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)="" ${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
Expand Down
22 changes: 18 additions & 4 deletions scripts/cxx-api/parser/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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}")
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand All @@ -245,7 +263,3 @@ def build_snapshots(output_dir: str, verbose: bool) -> None:
)
)
build_snapshots(output_dir, verbose=True)


if __name__ == "__main__":
main()
29 changes: 28 additions & 1 deletion scripts/cxx-api/parser/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <type> and <argsstring>
# <type> = "void(^"
# <argsstring> = ")(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,
Expand Down Expand Up @@ -465,7 +475,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 <Protocol1, Protocol2>" into separate base classes:
# "Foo", "<Protocol1>", "<Protocol2>". Combine them back into "Foo <Protocol1, Protocol2>".
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(
Expand Down
6 changes: 5 additions & 1 deletion scripts/cxx-api/parser/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 14 additions & 2 deletions scripts/cxx-api/tests/snapshots/.doxygen.config.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)=""
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface RCTAppearance : public RCTEventEmitter <RCTBridgeModule> {
public virtual instancetype init();
}
Original file line number Diff line number Diff line change
@@ -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 <RCTBridgeModule>
- (instancetype)init;
@end

} // namespace test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
interface RCTAlertManager : public NSObject <RCTBridgeModule, RCTInvalidating> {
}
Original file line number Diff line number Diff line change
@@ -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 <RCTBridgeModule, RCTInvalidating>

@end

} // namespace test
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading