diff --git a/graphify/extractors/objc.py b/graphify/extractors/objc.py index 67924c891..d4e852fae 100644 --- a/graphify/extractors/objc.py +++ b/graphify/extractors/objc.py @@ -295,7 +295,9 @@ def extract_objc(path: Path) -> dict: accepted = [] for macro in descendants(root, "macro_type_specifier"): macro_name = macro.child_by_field_name("name") - if macro_name is None or _read(macro_name) not in ("NS_ENUM", "NS_OPTIONS"): + if macro_name is None or _read(macro_name) not in ( + "NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM" + ): continue # The closest preceding structural token in an enclosing node must @@ -622,7 +624,7 @@ def extract_objc(path: Path) -> dict: # `typedef enum { ... } Name;` / `typedef enum Tag { ... } Name;`. # The enum body sits on a child enum_specifier that carries no usable # name; the name callers reference is the typedef alias (the trailing - # type_identifier). NS_ENUM/NS_OPTIONS are handled separately through + # type_identifier). NS_ENUM/NS_OPTIONS/NS_CLOSED_ENUM are handled separately through # their structural macro headers and isolated enum body fragments. enum_spec = next((c for c in node.children if c.type == "enum_specifier"), None) if enum_spec is not None and any( diff --git a/tests/test_objc_enum_declarations.py b/tests/test_objc_enum_declarations.py index 169fdf162..a82eb4b3c 100644 --- a/tests/test_objc_enum_declarations.py +++ b/tests/test_objc_enum_declarations.py @@ -6,7 +6,7 @@ definitions were dropped entirely — the type and all of its enumerators were invisible. Every other language with enums emits a type node plus a `case_of` edge per member (Java #1719, C++ #3939, Swift); ObjC now matches. -NS_ENUM and NS_OPTIONS typedefs use the same graph representation. +NS_ENUM, NS_OPTIONS and NS_CLOSED_ENUM typedefs use the same graph representation. """ from __future__ import annotations @@ -86,7 +86,7 @@ def test_objc_anonymous_and_bodyless_enums_mint_no_phantom(tmp_path): @pytest.mark.parametrize("suffix", [".h", ".m", ".mm"]) -@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS"]) +@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM"]) def test_objc_macro_enum_types_and_members_survive_cache(tmp_path, suffix, macro): source = tmp_path / ("Window" + suffix) source.write_text( @@ -164,7 +164,6 @@ def test_objc_macro_enum_preserves_original_utf8_member_lines(tmp_path, newline) "typedef OTHER_ENUM(NSInteger, Broken) { Bad };", "typedef MY_NS_ENUM(NSInteger, Broken) { Bad };", "typedef CF_ENUM(NSInteger, Broken) { Bad };", - "typedef NS_CLOSED_ENUM(NSInteger, Broken) { Bad };", "NS_ENUM(NSInteger, Broken) { Bad };", "typedef NS_ENUM(NSInteger, ) { Bad };", "typedef NS_ENUM(NSInteger, Broken, Extra) { Bad };", @@ -234,7 +233,7 @@ def test_objc_macro_enum_unsupported_parts_do_not_emit_partial_enums(tmp_path, d assert _edge_labels(result, "case_of") == {("Good", "Kept")} -@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS"]) +@pytest.mark.parametrize("macro", ["NS_ENUM", "NS_OPTIONS", "NS_CLOSED_ENUM"]) def test_objc_macro_enum_preserves_members_with_attributes(tmp_path, macro): source = tmp_path / "Attributes.h" source.write_text(