From be5110a7893ada5dc615238222f51e1940b27027 Mon Sep 17 00:00:00 2001 From: rajatnagda45 <215653880+rajatnagda45@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:16:45 +0100 Subject: [PATCH] fix(powershell): extract enum definitions and their members A PowerShell enum definition and its members were not extracted, so a [Color] type reference resolved to a sourceless phantom stub instead of the real enum. Emit the enum as a real sourced node with its members as contained children (mirroring class_statement), so references resolve to it; no builtins are fabricated. --- graphify/extractors/powershell.py | 26 ++++++++++++++++++++++++++ tests/test_languages.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/graphify/extractors/powershell.py b/graphify/extractors/powershell.py index 9f8526c7..206c9e26 100644 --- a/graphify/extractors/powershell.py +++ b/graphify/extractors/powershell.py @@ -127,6 +127,32 @@ def extract_powershell(path: Path) -> dict: walk(body, parent_class_nid) return + if t == "enum_statement": + # PowerShell enums are first-class type definitions (referenced via + # `[Color]` type literals) but were not extracted at all, so those + # references resolved to a sourceless phantom stub instead of the real + # in-file definition — and the enum's members were lost entirely. + name_node = next((c for c in node.children if c.type == "simple_name"), None) + if name_node: + enum_name = _read_text(name_node, source) + line = node.start_point[0] + 1 + enum_nid = _make_id(stem, enum_name) + add_node(enum_nid, enum_name, line) + add_edge(file_nid, enum_nid, "contains", line) + for member in node.children: + if member.type != "enum_member": + continue + mname_node = next( + (c for c in member.children if c.type == "simple_name"), None) + if mname_node is None: + continue + member_name = _read_text(mname_node, source) + m_line = member.start_point[0] + 1 + member_nid = _make_id(enum_nid, member_name) + add_node(member_nid, member_name, m_line) + add_edge(enum_nid, member_nid, "contains", m_line) + return + if t == "class_statement": name_node = next((c for c in node.children if c.type == "simple_name"), None) if name_node: diff --git a/tests/test_languages.py b/tests/test_languages.py index 80ea4386..f78865a8 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -2065,6 +2065,35 @@ def test_powershell_class_base_type_emits_inherits_edge(): assert ("Circle", "Shape") in _edge_labels(r, "inherits") +def test_powershell_enum_is_extracted_and_reference_resolves(tmp_path): + """A PowerShell enum must be a real definition, and `[Enum]` refs resolve to it. + + Enums were not extracted at all, so `[Color]$c` produced a `references` edge to + a sourceless phantom stub instead of the enum, and the enum's members were lost. + """ + f = tmp_path / "colors.ps1" + f.write_text( + "enum Color {\n Red\n Green = 5\n Blue\n}\n\n" + "class Widget {\n [Color]$color\n}\n" + ) + r = extract_powershell(f) + assert "error" not in r + color = next((n for n in r["nodes"] if n["label"] == "Color"), None) + assert color is not None, "enum definition dropped" + assert color["source_file"] != "", "enum must be a real sourced definition, not a phantom stub" + # members are captured + contained = { + n["label"] for n in r["nodes"] + for e in r["edges"] + if e["relation"] == "contains" and e["source"] == color["id"] and e["target"] == n["id"] + } + assert {"Red", "Green", "Blue"} <= contained, f"enum members missing: {contained}" + # the field type reference resolves to the real enum node + ref_targets = {e["target"] for e in r["edges"] + if e["relation"] == "references" and e.get("context") == "field"} + assert color["id"] in ref_targets, "[Color] field reference did not resolve to the enum" + + def test_powershell_property_field_type_context(): r = extract_powershell(FIXTURES / "sample.ps1") assert ("DataProcessor", "string") in _edge_labels(r, "references", "field")