Skip to content

Commit 95cd8cb

Browse files
committed
feat: add semantic contexts for Swift, Kotlin, PHP, Rust, and Go
1 parent 3efae38 commit 95cd8cb

8 files changed

Lines changed: 1213 additions & 66 deletions

File tree

graphify/extract.py

Lines changed: 869 additions & 43 deletions
Large diffs are not rendered by default.

tests/fixtures/sample.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@ func (s *Server) Stop() {
2121
fmt.Println("stopped")
2222
}
2323

24+
type Logger interface {
25+
Log(msg string)
26+
}
27+
28+
type Reader interface {
29+
Read() string
30+
}
31+
32+
type ReaderLogger interface {
33+
Logger
34+
Reader
35+
}
36+
37+
type BaseProcessor struct{}
38+
39+
type Result struct {
40+
value int
41+
}
42+
43+
type DataProcessor struct {
44+
BaseProcessor
45+
current *Result
46+
}
47+
48+
func (d *DataProcessor) Build(input *DataProcessor) (*Result, error) {
49+
return nil, nil
50+
}
51+
2452
func main() {
2553
s := NewServer(8080)
2654
s.Start()

tests/fixtures/sample.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ class HttpClient(private val config: Config) {
1717
}
1818
}
1919

20+
interface Loggable {
21+
fun log()
22+
}
23+
24+
open class BaseProcessor
25+
26+
class Result<T>
27+
28+
class DataProcessor : BaseProcessor(), Loggable {
29+
var current: Result<DataProcessor> = Result()
30+
31+
fun run(input: DataProcessor): Result<DataProcessor> {
32+
return current
33+
}
34+
35+
override fun log() {}
36+
}
37+
2038
fun createClient(baseUrl: String): HttpClient {
2139
val config = Config(baseUrl, 30)
2240
return HttpClient(config)

tests/fixtures/sample.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,39 @@ private function fetch(string $path, string $method): string
3333
}
3434
}
3535

36+
interface Loggable
37+
{
38+
public function log(): void;
39+
}
40+
41+
trait HasName
42+
{
43+
public function getName(): string
44+
{
45+
return '';
46+
}
47+
}
48+
49+
class BaseProcessor {}
50+
51+
class Result {}
52+
53+
class DataProcessor extends BaseProcessor implements Loggable
54+
{
55+
use HasName;
56+
57+
private Result $current;
58+
59+
public function run(DataProcessor $input): Result
60+
{
61+
return new Result();
62+
}
63+
64+
public function log(): void
65+
{
66+
}
67+
}
68+
3669
function parseResponse(string $raw): array
3770
{
3871
return json_decode($raw, true);

tests/fixtures/sample.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,29 @@ fn build_graph(edges: Vec<(String, String)>) -> Graph {
2525
}
2626
g
2727
}
28+
29+
trait Processor {
30+
fn run(&self);
31+
}
32+
33+
trait Logger: Processor {
34+
fn log(&self);
35+
}
36+
37+
struct Result<T> {
38+
value: T,
39+
}
40+
41+
struct DataProcessor {
42+
current: Result<DataProcessor>,
43+
}
44+
45+
impl Processor for DataProcessor {
46+
fn run(&self) {}
47+
}
48+
49+
impl DataProcessor {
50+
fn build(input: DataProcessor) -> Result<DataProcessor> {
51+
Result { value: input }
52+
}
53+
}

tests/fixtures/sample.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ protocol Loggable {
99
func log()
1010
}
1111

12-
class DataProcessor: Processor {
12+
class BaseProcessor {}
13+
14+
class Result<T> {}
15+
16+
class DataProcessor: BaseProcessor, Processor {
1317
private var items: [String] = []
18+
var current: Result<DataProcessor> = Result<DataProcessor>()
1419

1520
init() {}
1621

@@ -24,6 +29,10 @@ class DataProcessor: Processor {
2429
return validate(items)
2530
}
2631

32+
func run(input: DataProcessor) -> Result<DataProcessor> {
33+
return current
34+
}
35+
2736
private func validate(_ data: [String]) -> [String] {
2837
return data.filter { !$0.isEmpty }
2938
}

tests/test_languages.py

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,20 @@ def test_kotlin_emits_in_file_calls():
347347
assert ("createClient()", "HttpClient") in calls
348348

349349

350+
def test_kotlin_splits_inherits_and_implements():
351+
r = extract_kotlin(FIXTURES / "sample.kt")
352+
assert ("DataProcessor", "BaseProcessor") in _edge_labels(r, "inherits")
353+
assert ("DataProcessor", "Loggable") in _edge_labels(r, "implements")
354+
355+
356+
def test_kotlin_parameter_return_generic_and_field_contexts():
357+
r = extract_kotlin(FIXTURES / "sample.kt")
358+
assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")
359+
assert ("run", "Result") in _edge_labels(r, "references", "return_type")
360+
assert ("run", "DataProcessor") in _edge_labels(r, "references", "generic_arg")
361+
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
362+
363+
350364
# ── Scala ─────────────────────────────────────────────────────────────────────
351365

352366
def test_scala_no_error():
@@ -473,6 +487,20 @@ def test_php_event_listener_links_event_to_listener():
473487
assert any("UserRegistered" in src and "SendWelcomeEmail" in tgt for src, tgt in listened)
474488

475489

490+
def test_php_splits_inherits_implements_mixes_in():
491+
r = extract_php(FIXTURES / "sample.php")
492+
assert ("DataProcessor", "BaseProcessor") in _edge_labels(r, "inherits")
493+
assert ("DataProcessor", "Loggable") in _edge_labels(r, "implements")
494+
assert ("DataProcessor", "HasName") in _edge_labels(r, "mixes_in")
495+
496+
497+
def test_php_property_parameter_and_return_contexts():
498+
r = extract_php(FIXTURES / "sample.php")
499+
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
500+
assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")
501+
assert ("run", "Result") in _edge_labels(r, "references", "return_type")
502+
503+
476504
# ── Swift ────────────────────────────────────────────────────────────────────
477505

478506
def test_swift_no_error():
@@ -567,31 +595,28 @@ def test_swift_extension_does_not_duplicate_type_node():
567595
config_nodes = [n for n in r["nodes"] if n["label"] == "Config"]
568596
assert len(config_nodes) == 1, f"Config should appear once, got {len(config_nodes)}"
569597

570-
def test_swift_conformance_edge():
598+
def test_swift_protocol_conformance_emits_implements():
571599
r = extract_swift(FIXTURES / "sample.swift")
572-
inherits_edges = [e for e in r["edges"] if e["relation"] == "inherits"]
573-
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
574-
found = False
575-
for e in inherits_edges:
576-
src_label = node_by_id.get(e["source"], "")
577-
tgt_label = node_by_id.get(e["target"], "")
578-
if "DataProcessor" in src_label and "Processor" in tgt_label:
579-
found = True
580-
break
581-
assert found, "DataProcessor should have inherits edge to Processor"
600+
assert ("DataProcessor", "Processor") in _edge_labels(r, "implements")
582601

583-
def test_swift_extension_conformance_edge():
602+
603+
def test_swift_extension_conformance_emits_implements():
584604
r = extract_swift(FIXTURES / "sample.swift")
585-
inherits_edges = [e for e in r["edges"] if e["relation"] == "inherits"]
586-
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
587-
found = False
588-
for e in inherits_edges:
589-
src_label = node_by_id.get(e["source"], "")
590-
tgt_label = node_by_id.get(e["target"], "")
591-
if "DataProcessor" in src_label and "Loggable" in tgt_label:
592-
found = True
593-
break
594-
assert found, "extension should add conformance edge DataProcessor -> Loggable"
605+
assert ("DataProcessor", "Loggable") in _edge_labels(r, "implements")
606+
607+
608+
def test_swift_splits_inherits_and_implements():
609+
r = extract_swift(FIXTURES / "sample.swift")
610+
assert ("DataProcessor", "BaseProcessor") in _edge_labels(r, "inherits")
611+
assert ("DataProcessor", "Processor") in _edge_labels(r, "implements")
612+
613+
614+
def test_swift_parameter_return_generic_and_field_contexts():
615+
r = extract_swift(FIXTURES / "sample.swift")
616+
assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")
617+
assert ("run", "Result") in _edge_labels(r, "references", "return_type")
618+
assert ("run", "DataProcessor") in _edge_labels(r, "references", "generic_arg")
619+
assert ("DataProcessor", "Result") in _edge_labels(r, "references", "field")
595620

596621
def test_swift_emits_calls():
597622
r = extract_swift(FIXTURES / "sample.swift")

0 commit comments

Comments
 (0)