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
23 changes: 16 additions & 7 deletions scripts/cxx-api/parser/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,32 @@ def build_snapshot_for_view(
definitions=definitions,
)

print("Running Doxygen")

# If there is already a doxygen output directory, delete it
if os.path.exists(os.path.join(react_native_dir, "api")):
print("Deleting existing output directory")
subprocess.run(["rm", "-rf", os.path.join(react_native_dir, "api")])

print("Running Doxygen")

# Run doxygen with the config file
doxygen_bin = os.environ.get("DOXYGEN_BIN", "doxygen")
result = subprocess.run(
["doxygen", DOXYGEN_CONFIG_FILE],
[doxygen_bin, DOXYGEN_CONFIG_FILE],
cwd=react_native_dir,
capture_output=True,
text=True,
)

# Delete the Doxygen config file
print("Deleting Doxygen config file")
subprocess.run(["rm", DOXYGEN_CONFIG_FILE], cwd=react_native_dir)

# Check the result
if result.returncode != 0:
print(f"Doxygen finished with error: {result.stderr}")
else:
print("Doxygen finished successfully")

# Delete the Doxygen config file
print("Deleting Doxygen config file")
subprocess.run(["rm", DOXYGEN_CONFIG_FILE], cwd=react_native_dir)

# build snapshot, convert to string, and save to file
snapshot = build_snapshot(os.path.join(react_native_dir, "api", "xml"))
snapshot_string = snapshot.to_string()
Expand Down Expand Up @@ -145,6 +146,14 @@ def main():
)
args = parser.parse_args()

doxygen_bin = os.environ.get("DOXYGEN_BIN", "doxygen")
version_result = subprocess.run(
[doxygen_bin, "--version"],
capture_output=True,
text=True,
)
print(f"Using Doxygen {version_result.stdout.strip()} ({doxygen_bin})")

# Define the path to the react-native directory
react_native_dir = (
os.path.join(get_react_native_dir(), "packages", "react-native")
Expand Down
39 changes: 34 additions & 5 deletions scripts/cxx-api/parser/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .member import (
ConceptMember,
EnumMember,
FriendMember,
FunctionMember,
TypedefMember,
VariableMember,
Expand Down Expand Up @@ -86,7 +87,9 @@ def resolve_linked_text_name(
refid = getattr(part.value, "refid", None)
if refid and not in_string:
ns = extract_namespace_from_refid(refid)
if ns and not text.startswith(ns):
# Skip re-qualification if text is already globally qualified
# (starts with "::") - it's already an absolute path
if ns and not text.startswith(ns) and not text.startswith("::"):
# The text may already start with a trailing portion of
# the namespace. For example ns="facebook::react::HighResDuration"
# and text="HighResDuration::zero". We need to find the
Expand Down Expand Up @@ -323,6 +326,12 @@ def get_function_member(
# Strip the trailing "=0" from the type string.
function_type = re.sub(r"\s*=\s*0\s*$", "", function_type)

# For constexpr constructors, Doxygen outputs "constexpr" both as an
# attribute (constexpr="yes") and as the return type (<type>constexpr</type>).
# Remove the redundant type to avoid "constexpr constexpr" in output.
if is_constexpr and function_type == "constexpr":
function_type = ""

doxygen_params = get_doxygen_params(function_def)

function = FunctionMember(
Expand Down Expand Up @@ -474,11 +483,26 @@ def build_snapshot(xml_dir: str) -> Snapshot:
if member_type == "attrib":
for member_def in section_def.memberdef:
if member_def.kind == "variable":
class_scope.add_member(
get_variable_member(
member_def, visibility, is_static
)
(var_type, _) = resolve_linked_text_name(
member_def.get_type()
)

# Skip anonymous variables
if "@" in var_type:
continue

if var_type == "friend":
class_scope.add_member(
FriendMember(
member_def.get_name(), visibility
)
)
else:
class_scope.add_member(
get_variable_member(
member_def, visibility, is_static
)
)
elif member_type == "func":
for function_def in section_def.memberdef:
class_scope.add_member(
Expand Down Expand Up @@ -514,6 +538,11 @@ def build_snapshot(xml_dir: str) -> Snapshot:
f"Unknown class visibility: {visibility} in {compound_object.location.file}"
)
elif compound_object.kind == "namespace":
# Skip anonymous namespaces (internal linkage, not public API).
# Doxygen encodes them with a '@' prefix in the compound name.
if "@" in compound_object.compoundname:
continue

namespace_scope = snapshot.create_or_get_namespace(
compound_object.compoundname
)
Expand Down
23 changes: 23 additions & 0 deletions scripts/cxx-api/parser/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class MemberKind(IntEnum):
FUNCTION = 3
OPERATOR = 4
VARIABLE = 5
FRIEND = 6


class Member(ABC):
Expand Down Expand Up @@ -358,6 +359,28 @@ def to_string(
return result


class FriendMember(Member):
def __init__(self, name: str, visibility: str = "public") -> None:
super().__init__(name, visibility)

@property
def member_kind(self) -> MemberKind:
return MemberKind.FRIEND

def to_string(
self,
indent: int = 0,
qualification: str | None = None,
hide_visibility: bool = False,
) -> str:
name = self._get_qualified_name(qualification)
result = " " * indent
if not hide_visibility:
result += self.visibility + " "
result += f"friend {name};"
return result


class ConceptMember(Member):
def __init__(
self,
Expand Down
16 changes: 14 additions & 2 deletions scripts/cxx-api/parser/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from natsort import natsort_keygen, natsorted

from .member import Member, MemberKind
from .member import FriendMember, Member, MemberKind
from .template import Template, TemplateList
from .utils import parse_qualified_path

Expand Down Expand Up @@ -226,7 +226,10 @@ def qualify_name(self, name: str | None) -> str | None:
if base_name in current_scope.inner_scopes:
matched_segments.append(path_segment)
current_scope = current_scope.inner_scopes[base_name]
elif any(m.name == base_name for m in current_scope._members):
elif any(
m.name == base_name and not isinstance(m, FriendMember)
for m in current_scope._members
):
# Found as a member, assume following segments exist in the scope
prefix = "::".join(matched_segments)
suffix = "::".join(path[i:])
Expand All @@ -239,6 +242,15 @@ def qualify_name(self, name: str | None) -> str | None:
if anchor_prefix:
return f"{anchor_prefix}::{suffix}"
return suffix
elif any(
isinstance(m, FriendMember) and m.name == base_name
for m in current_scope._members
):
# The name matches a friend declaration, not a real member.
# Re-qualify from the remaining segments so the type resolves
# to its actual definition rather than the friend's owning class.
remaining = "::".join(path[i:])
return self.qualify_name(remaining)
else:
return None

Expand Down
14 changes: 14 additions & 0 deletions scripts/cxx-api/parser/utils/type_qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:
if not type_str:
return type_str

# Names starting with "::" are already globally qualified - skip re-qualification
if type_str.lstrip().startswith("::"):
return type_str

# Handle template arguments first: qualify types inside angle brackets
angle_start = type_str.find("<")
if angle_start != -1:
Expand All @@ -53,6 +57,16 @@ def qualify_type_str(type_str: str, scope: Scope) -> str:

return qualified_prefix + qualified_template + qualified_suffix

# Handle leading qualifiers (const, volatile) that prevent qualify_name
# from matching. Strip them, qualify the rest, and prepend back.
for qualifier in ("const ", "volatile "):
if type_str.startswith(qualifier):
inner = type_str[len(qualifier) :]
qualified_inner = qualify_type_str(inner, scope)
if qualified_inner != inner:
return qualifier + qualified_inner
break

# Try qualifying the entire string (handles simple cases without templates)
qualified = scope.qualify_name(type_str)
if qualified is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class test::Clss {
public constexpr Clss() = default;
public constexpr Clss(const test::Clss&) = default;
public constexpr Clss(int value);
public constexpr Clss(test::Clss&&) = default;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 {

class Clss {
public:
constexpr Clss() = default;
constexpr Clss(const Clss &) = default;
constexpr Clss(Clss &&) = default;
explicit constexpr Clss(int value);
};

} // namespace test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class test::Clss {
public void hello();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 {
enum class InternalEnum {
A,
B,
};
} // namespace

namespace test {

class Clss {
public:
void hello();
};

} // namespace test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
struct test::ComponentDescriptor {
public friend ShadowNode;
public virtual void adopt(test::ShadowNode& shadowNode) const = 0;
}

struct test::ShadowNode {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 {

struct ShadowNode {};

struct ComponentDescriptor {
friend ShadowNode;

virtual void adopt(ShadowNode &shadowNode) const = 0;
};

} // namespace test
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
struct other::Consumer {
public ::test::inner::MyType create();
public void process(const ::test::inner::MyType & param);
public void processPtr(::test::inner::MyType * param);
}


struct test::inner::MyType {
public int value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 {
namespace inner {

struct MyType {
int value;
};

} // namespace inner
} // namespace test

namespace other {

struct Consumer {
void process(const ::test::inner::MyType &param);
void processPtr(::test::inner::MyType *param);
::test::inner::MyType create();
};

} // namespace other
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
struct test::ComponentDescriptor {
public friend ShadowNode;
public virtual std::shared_ptr< const test::ShadowNode > clone() const = 0;
}

struct test::ShadowNode {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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

#include <memory>

namespace test {

struct ShadowNode {};

struct ComponentDescriptor {
friend ShadowNode;

virtual std::shared_ptr<const ShadowNode> clone() const = 0;
};

} // namespace test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
struct test::BundleHeader {
public uint32_t magic32;
public uint32_t version;
public uint64_t magic64;
}
Loading
Loading