fix(powershell): link enum members with case_of instead of contains

A PowerShell enum member is a discriminant case, not a declared field, so it
should hang off its enum with a case_of edge like every other language that
has enums (Java #1719, C#, Swift, Rust, VB.NET). PowerShell was routing enum
members through the same contains path used for class members.

The relation is not cosmetic: case_of targets are excluded from constructor
binding, so an enum member named like a type could previously be mistaken for
one.

Update the existing enum test to expect case_of and add a focused regression
test asserting enum members get case_of (and not contains) while the file
still contains the enum type via contains.

(cherry picked from commit 011dda9b0f)
This commit is contained in:
rajatnagda45 authored and safishamsi committed 2026-10-05 19:40:03 +01:00
1 parent 4f0eb679bf
commit 282e2bbc56
2 files changed
+36 -5

No files matched your search

+7 -1
View File
@@ -150,7 +150,13 @@ def extract_powershell(path: Path) -> dict:
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)
# An enum member is a discriminant case, not a declared
# field, so it gets `case_of` like every other language with
# enums (Java #1719, C#, Swift, Rust, VB.NET). The relation
# also matters to resolution: `case_of` targets are excluded
# from constructor binding, so an enum member named like a
# type can no longer be mistaken for one.
add_edge(enum_nid, member_nid, "case_of", m_line)
return
if t == "class_statement":
+29 -4
View File
@@ -2699,19 +2699,44 @@ def test_powershell_enum_is_extracted_and_reference_resolves(tmp_path):
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 = {
# members are captured, hanging off the enum via case_of (not contains)
cases = {
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"]
if e["relation"] == "case_of" and e["source"] == color["id"] and e["target"] == n["id"]
}
assert {"Red", "Green", "Blue"} <= contained, f"enum members missing: {contained}"
assert {"Red", "Green", "Blue"} <= cases, f"enum members missing: {cases}"
# 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_enum_members_emit_case_of_not_contains(tmp_path):
"""A PowerShell enum member is a discriminant case, so it must get a
`case_of` edge like every other language with enums (Java #1719, C#, Swift,
Rust, VB.NET), not the `contains` edge used for real class fields.
PowerShell was routing enum members through the same `contains` path as a
class property. The relation also matters to resolution: `case_of` targets
are excluded from constructor binding, so an enum member named like a type
can no longer be mistaken for one.
"""
f = tmp_path / "status.ps1"
f.write_text("enum Status {\n Active\n Paused\n Closed\n}\n")
r = extract_powershell(f)
case_of = _edge_labels(r, "case_of")
contains = _edge_labels(r, "contains")
# Each enum member hangs off its enum via case_of, not contains.
assert ("Status", "Active") in case_of
assert ("Status", "Paused") in case_of
assert ("Status", "Closed") in case_of
assert ("Status", "Active") not in contains
# Containment itself (the file owning the enum type) still uses `contains`;
# only the member-to-enum relation changed.
assert ("status.ps1", "Status") in contains
assert ("status.ps1", "Status") not in case_of
def test_powershell_property_field_type_context():
r = extract_powershell(FIXTURES / "sample.ps1")
assert ("DataProcessor", "string") in _edge_labels(r, "references", "field")