Skip to content

Commit 0686cde

Browse files
coadometa-codesync[bot]
authored andcommitted
Strip __deprecated_msg in the preprocessing (#55970)
Summary: Pull Request resolved: #55970 Strips `__deprecated_msg` annotations due to incorrect parsing when in front of the interfaces (predefining does not help unfortunately). It doesn't fully solve the issue stated in the added test as doxygen also has problem with parsing interfaces for which the base class is in the new line. ``` interface Foo : Bar<NSObject> end ``` This will be handled by the fix in the doxygen. This diff also adds stripping the `__deprecated` label which is again incorrectly parsed by doxygen. Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D95541064 fbshipit-source-id: 08aca2eb5ed8b1897166d385ab513879e7ecbad7
1 parent 7d45ea8 commit 0686cde

8 files changed

Lines changed: 110 additions & 9 deletions

File tree

scripts/cxx-api/input_filters/doxygen_strip_comments.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ def replace_with_newlines(match: re.Match) -> str:
3636
return comment_pattern.sub(replace_with_newlines, content)
3737

3838

39+
def strip_deprecated_msg(content: str) -> str:
40+
"""
41+
Remove __deprecated_msg(...) macros and standalone __deprecated annotations
42+
from content.
43+
44+
These macros cause Doxygen to produce malformed XML output when they appear
45+
before @interface declarations, creating __pad0__ artifacts and missing
46+
members. Standalone __deprecated on method declarations causes the annotation
47+
to be parsed as a parameter name. Since the macros are stripped, deprecation
48+
info won't appear in the API snapshot output.
49+
"""
50+
# Pattern to match __deprecated_msg("...") with any content inside quotes
51+
pattern = re.compile(r'__deprecated_msg\s*\(\s*"[^"]*"\s*\)\s*')
52+
content = pattern.sub("", content)
53+
54+
# Pattern to match standalone __deprecated (not followed by _msg or other suffix)
55+
standalone_pattern = re.compile(r"\b__deprecated\b(?!_)\s*")
56+
content = standalone_pattern.sub("", content)
57+
58+
return content
59+
60+
3961
def main():
4062
if len(sys.argv) < 2:
4163
print("Usage: doxygen_strip_comments.py <filename>", file=sys.stderr)
@@ -48,6 +70,7 @@ def main():
4870
content = f.read()
4971

5072
filtered = strip_block_comments(content)
73+
filtered = strip_deprecated_msg(filtered)
5174
print(filtered, end="")
5275
except Exception as e:
5376
# On error, output original content to not break the build

scripts/cxx-api/parser/__main__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def build_doxygen_config(
6060
with open(os.path.join(directory, ".doxygen.config.template")) as f:
6161
template = f.read()
6262

63-
# replace the placeholder with the actual path
63+
# replace the placeholders with the actual values
6464
config = (
6565
template.replace("${INPUTS}", include_directories_str)
6666
.replace("${EXCLUDE_PATTERNS}", exclude_patterns_str)
@@ -103,9 +103,12 @@ def build_snapshot_for_view(
103103

104104
if verbose:
105105
print("Running Doxygen")
106+
if input_filter:
107+
print(f" Using input filter: {input_filter}")
106108

107109
# Run doxygen with the config file
108110
doxygen_bin = os.environ.get("DOXYGEN_BIN", "doxygen")
111+
109112
result = subprocess.run(
110113
[doxygen_bin, DOXYGEN_CONFIG_FILE],
111114
cwd=react_native_dir,
@@ -227,6 +230,7 @@ def build_snapshots(output_dir: str, verbose: bool) -> None:
227230
definitions=config.definitions,
228231
output_dir=output_dir,
229232
verbose=verbose,
233+
input_filter=input_filter,
230234
)
231235
else:
232236
snapshot = build_snapshot_for_view(

scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/snapshot.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface RCTDeprecatedInterface {
77

88
interface RCTTestInterface {
99
public virtual NSArray* deprecatedMethod:(id param);
10+
public virtual instancetype initWithURL:launchOptions:(NSURL* url, NSDictionary* launchOptions);
1011
public virtual void normalMethod:(NSString* name);
1112
}
1213

scripts/cxx-api/tests/snapshots/should_handle_deprecated_msg/test.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
- (NSArray *)deprecatedMethod:(id)param __deprecated_msg("Use newMethod instead.");
1313

14+
- (instancetype)initWithURL:(NSURL *)url launchOptions:(NSDictionary *)launchOptions __deprecated;
15+
1416
@end
1517

1618
@interface RCTDeprecatedInterface
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface RCTSurface {
2+
protected __pad0__;
3+
public @property (assign, readonly) CGSize minimumSize;
4+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
__deprecated_msg("This API will be removed along with the legacy architecture.") @interface RCTSurface
9+
: NSObject<RCTSurfaceProtocol>
10+
11+
- (instancetype)initWithBridge:(RCTBridge *)bridge
12+
moduleName:(NSString *)moduleName
13+
initialProperties:(NSDictionary *)initialProperties;
14+
15+
@property (atomic, assign, readonly) CGSize minimumSize;
16+
17+
@end

scripts/cxx-api/tests/test_input_filters.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
import unittest
99

10-
from ..input_filters.doxygen_strip_comments import strip_block_comments
10+
from ..input_filters.doxygen_strip_comments import (
11+
strip_block_comments,
12+
strip_deprecated_msg,
13+
)
1114

1215

1316
class TestDoxygenStripComments(unittest.TestCase):
@@ -67,5 +70,51 @@ def test_handles_no_comments(self):
6770
self.assertEqual(result, content)
6871

6972

73+
class TestStripDeprecatedMsg(unittest.TestCase):
74+
def test_strips_deprecated_msg(self):
75+
content = '- (void)oldMethod __deprecated_msg("Use newMethod instead.");'
76+
result = strip_deprecated_msg(content)
77+
self.assertEqual(result, "- (void)oldMethod ;")
78+
79+
def test_strips_standalone_deprecated(self):
80+
content = "- (instancetype)initWithBundleURL:(NSURL *)bundleURL launchOptions:(nullable NSDictionary *)launchOptions __deprecated;"
81+
result = strip_deprecated_msg(content)
82+
self.assertEqual(
83+
result,
84+
"- (instancetype)initWithBundleURL:(NSURL *)bundleURL launchOptions:(nullable NSDictionary *)launchOptions ;",
85+
)
86+
87+
def test_standalone_deprecated_does_not_match_deprecated_msg(self):
88+
content = '__deprecated_msg("msg") and __deprecated'
89+
result = strip_deprecated_msg(content)
90+
self.assertEqual(result, "and ")
91+
92+
def test_preserves_deprecated_in_identifiers(self):
93+
content = (
94+
"- (void)findComponentViewWithTag_DO_NOT_USE_DEPRECATED:(NSInteger)tag;"
95+
)
96+
result = strip_deprecated_msg(content)
97+
self.assertEqual(result, content)
98+
99+
def test_preserves_DEPRECATED_suffix_in_names(self):
100+
content = "@property (weak) RCTViewRegistry *viewRegistry_DEPRECATED;"
101+
result = strip_deprecated_msg(content)
102+
self.assertEqual(result, content)
103+
104+
def test_handles_no_deprecated(self):
105+
content = "- (void)normalMethod;"
106+
result = strip_deprecated_msg(content)
107+
self.assertEqual(result, content)
108+
109+
def test_handles_empty_content(self):
110+
result = strip_deprecated_msg("")
111+
self.assertEqual(result, "")
112+
113+
def test_strips_deprecated_before_interface(self):
114+
content = '__deprecated_msg("This API will be removed.") @interface RCTSurface : NSObject'
115+
result = strip_deprecated_msg(content)
116+
self.assertEqual(result, "@interface RCTSurface : NSObject")
117+
118+
70119
if __name__ == "__main__":
71120
unittest.main()

scripts/cxx-api/tests/test_snapshots.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,15 +111,16 @@ def _test(self: unittest.TestCase) -> None:
111111
)
112112

113113
# Get real filesystem path for filter script if it exists
114-
filter_script_path = None
114+
# IMPORTANT: Keep the context manager active while Doxygen runs,
115+
# otherwise the extracted file may be cleaned up before use
115116
if filter_script.is_file():
116117
with ir.as_file(filter_script) as fs_path:
117-
filter_script_path = str(fs_path)
118-
119-
# Run doxygen to generate the XML
120-
_generate_doxygen_api(
121-
str(case_dir_path), str(doxygen_config_path), filter_script_path
122-
)
118+
_generate_doxygen_api(
119+
str(case_dir_path), str(doxygen_config_path), str(fs_path)
120+
)
121+
else:
122+
# No filter script available - run without filter
123+
_generate_doxygen_api(str(case_dir_path), str(doxygen_config_path))
123124

124125
# Parse the generated XML
125126
xml_dir = case_dir_path / "api" / "xml"

0 commit comments

Comments
 (0)