From a129ff2cd60b423debb9351f793a0551faa9dc71 Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:20:39 +1000 Subject: [PATCH] fix(powershell): emit inherits/implements edges for class base types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `class_statement` handler read only the first `simple_name` child — the class name — and never inspected the base type(s) after the `:` token. As a result `class Dog : Animal` dropped the Dog->Animal inheritance edge entirely; derived classes appeared as isolated nodes. Walk the class_statement children, and once the `:` token is seen treat each following `simple_name` as a base type. Matching the C# convention (PowerShell has no syntactic base-vs-interface split), the first base is emitted as `inherits` and the rest as `implements`, resolved via ensure_named_node. Adds a Shape/Circle inheritance pair to tests/fixtures/sample.ps1 and a regression test asserting ("Circle","Shape") in the inherits edges. --- graphify/extract.py | 16 ++++++++++++++++ tests/fixtures/sample.ps1 | 16 ++++++++++++++++ tests/test_languages.py | 7 +++++++ 3 files changed, 39 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index 7101d616..19dcaa24 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -8495,6 +8495,22 @@ def extract_powershell(path: Path) -> dict: class_nid = _make_id(stem, class_name) add_node(class_nid, class_name, line) add_edge(file_nid, class_nid, "contains", line) + # Base type(s) after ':'. PowerShell has no syntactic base vs + # interface split, so (matching the C# convention) treat the + # first base as the superclass (inherits) and the rest as + # interfaces (implements). Bases are the simple_name children + # after the ':' token. + colon_seen = False + base_index = 0 + for child in node.children: + if child.type == ":": + colon_seen = True + elif colon_seen and child.type == "simple_name": + base_nid = ensure_named_node(_read_text(child, source), line) + if base_nid != class_nid: + rel = "inherits" if base_index == 0 else "implements" + add_edge(class_nid, base_nid, rel, line) + base_index += 1 for child in node.children: walk(child, parent_class_nid=class_nid) return diff --git a/tests/fixtures/sample.ps1 b/tests/fixtures/sample.ps1 index 2cdb6aa7..43c27fd7 100644 --- a/tests/fixtures/sample.ps1 +++ b/tests/fixtures/sample.ps1 @@ -30,3 +30,19 @@ class DataProcessor { Set-Content -Path $path -Value $this.Source } } + +class Shape { + [string]$Kind + + [double] Area() { + return 0.0 + } +} + +class Circle : Shape { + [double]$Radius + + [double] Area() { + return 3.14159 * $this.Radius * $this.Radius + } +} diff --git a/tests/test_languages.py b/tests/test_languages.py index bd370c9d..35569c50 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -1631,6 +1631,13 @@ def test_powershell_finds_class_and_method(): assert any("Transform" in l for l in labels) +def test_powershell_class_base_type_emits_inherits_edge(): + # `class Circle : Shape` — the base type after ':' was previously dropped + # because the handler only read the first simple_name (the class name). + r = extract_powershell(FIXTURES / "sample.ps1") + assert ("Circle", "Shape") in _edge_labels(r, "inherits") + + def test_powershell_property_field_type_context(): r = extract_powershell(FIXTURES / "sample.ps1") assert ("DataProcessor", "string") in _edge_labels(r, "references", "field")