1+ using System . IO ;
2+ using System . Linq ;
13using Microsoft . CodeAnalysis ;
24using Microsoft . CodeAnalysis . CSharp ;
35using Xunit ;
@@ -6,66 +8,169 @@ namespace CodeAnalysisTool.Tests;
68
79public class WalkerSmokeTests
810{
9- [ Fact ]
10- public void Walker_emits_namespace_class_and_method_nodes_for_trivial_source ( )
11- {
12- const string source = @"
13- namespace Demo
14- {
15- public class Greeter
11+ private static MetadataReference [ ] GetReferences ( )
1612 {
17- public string Hello(string name) => $""hi {name}"";
13+ var runtimeDir = Path . GetDirectoryName ( typeof ( object ) . Assembly . Location ) ! ;
14+ return new MetadataReference [ ]
15+ {
16+ MetadataReference . CreateFromFile ( typeof ( object ) . Assembly . Location ) ,
17+ MetadataReference . CreateFromFile ( Path . Combine ( runtimeDir , "System.Runtime.dll" ) ) ,
18+ MetadataReference . CreateFromFile ( Path . Combine ( runtimeDir , "System.Console.dll" ) ) ,
19+ } ;
1820 }
19- }" ;
2021
22+ private static D3Graph WalkSource ( string source , string fileName = "Test.cs" )
23+ {
2124 var tree = CSharpSyntaxTree . ParseText ( source ) ;
2225 var compilation = CSharpCompilation . Create (
23- "Smoke " ,
26+ "TestAssembly " ,
2427 new [ ] { tree } ,
25- new [ ] { MetadataReference . CreateFromFile ( typeof ( object ) . Assembly . Location ) } ) ;
28+ GetReferences ( ) ) ;
2629
2730 var walker = new CodeGraphWalker
2831 {
2932 SemanticModel = compilation . GetSemanticModel ( tree )
3033 } ;
31- walker . SetCurrentFile ( "Greeter.cs" ) ;
34+ walker . SetCurrentFile ( fileName ) ;
3235 walker . Visit ( tree . GetRoot ( ) ) ;
36+ return walker . GetGraph ( ) ;
37+ }
3338
34- var graph = walker . GetGraph ( ) ;
39+ [ Fact ]
40+ public void Emits_namespace_class_and_method_nodes ( )
41+ {
42+ var graph = WalkSource ( @"
43+ namespace Demo
44+ {
45+ public class Greeter
46+ {
47+ public string Hello(string name) => $""hi {name}"";
48+ }
49+ }" ) ;
3550
36- Assert . NotEmpty ( graph . Nodes ) ;
37- Assert . Contains ( graph . Nodes , n => n . Group == "namespace" ) ;
38- Assert . Contains ( graph . Nodes , n => n . Group == "class" ) ;
39- Assert . Contains ( graph . Nodes , n => n . Group == "method" ) ;
51+ Assert . Contains ( graph . Nodes , n => n . Group == "namespace" && n . Id == "Demo" ) ;
52+ Assert . Contains ( graph . Nodes , n => n . Group == "class" && n . Id == "Demo.Greeter" ) ;
53+ Assert . Contains ( graph . Nodes , n => n . Group == "method" && n . Id == "Demo.Greeter.Hello" ) ;
4054 }
4155
4256 [ Fact ]
43- public void Walker_emits_links_between_namespace_and_class ( )
57+ public void Emits_containment_links_for_class_hierarchy ( )
4458 {
45- const string source = @"
59+ var graph = WalkSource ( @"
4660namespace MyApp
4761{
4862 public class Service
4963 {
5064 public void Run() { }
5165 }
52- }" ;
66+ }" ) ;
5367
54- var tree = CSharpSyntaxTree . ParseText ( source ) ;
55- var compilation = CSharpCompilation . Create (
56- "Links" ,
57- new [ ] { tree } ,
58- new [ ] { MetadataReference . CreateFromFile ( typeof ( object ) . Assembly . Location ) } ) ;
68+ Assert . Contains ( graph . Links , l => l . Source == "MyApp" && l . Target == "MyApp.Service" && l . Type == "containment" ) ;
69+ Assert . Contains ( graph . Links , l => l . Source == "MyApp.Service" && l . Target == "MyApp.Service.Run" && l . Type == "containment" ) ;
70+ }
5971
60- var walker = new CodeGraphWalker
72+ [ Fact ]
73+ public void Detects_properties ( )
74+ {
75+ var graph = WalkSource ( @"
76+ namespace Models
77+ {
78+ public class User
79+ {
80+ public string Name { get; set; }
81+ public int Age { get; set; }
82+ }
83+ }" ) ;
84+
85+ Assert . Contains ( graph . Nodes , n => n . Group == "property" && n . Id == "Models.User.Name" ) ;
86+ Assert . Contains ( graph . Nodes , n => n . Group == "property" && n . Id == "Models.User.Age" ) ;
87+ }
88+
89+ [ Fact ]
90+ public void Tracks_method_calls_as_call_links ( )
91+ {
92+ var graph = WalkSource ( @"
93+ namespace App
94+ {
95+ public class Caller
96+ {
97+ public void Execute()
6198 {
62- SemanticModel = compilation . GetSemanticModel ( tree )
63- } ;
64- walker . SetCurrentFile ( "Service.cs" ) ;
65- walker . Visit ( tree . GetRoot ( ) ) ;
99+ DoWork();
100+ }
101+
102+ public void DoWork() { }
103+ }
104+ }" ) ;
105+
106+ Assert . Contains ( graph . Links , l =>
107+ l . Source == "App.Caller.Execute" &&
108+ l . Target == "App.Caller.DoWork" &&
109+ l . Type == "call" ) ;
110+ }
111+
112+ [ Fact ]
113+ public void Marks_called_methods_as_used ( )
114+ {
115+ // Helper declared before Entry so the walker has registered it
116+ // by the time the invocation is visited (depth-first source order).
117+ var graph = WalkSource ( @"
118+ namespace App
119+ {
120+ public class Svc
121+ {
122+ public void Helper() { }
123+ public void Orphan() { }
124+
125+ public void Entry()
126+ {
127+ Helper();
128+ }
129+ }
130+ }" ) ;
131+
132+ var helper = graph . Nodes . First ( n => n . Id == "App.Svc.Helper" ) ;
133+ var orphan = graph . Nodes . First ( n => n . Id == "App.Svc.Orphan" ) ;
66134
67- var graph = walker . GetGraph ( ) ;
135+ Assert . True ( helper . Used ) ;
136+ Assert . False ( orphan . Used ) ;
137+ }
138+
139+ [ Fact ]
140+ public void Stores_file_path_and_line_number ( )
141+ {
142+ var graph = WalkSource ( @"
143+ namespace Loc
144+ {
145+ public class Positioned
146+ {
147+ public void Method() { }
148+ }
149+ }" , "Located.cs" ) ;
150+
151+ var cls = graph . Nodes . First ( n => n . Id == "Loc.Positioned" ) ;
152+ Assert . Equal ( "Located.cs" , cls . FilePath ) ;
153+ Assert . True ( cls . LineNumber > 0 ) ;
154+ }
155+
156+ [ Fact ]
157+ public void External_method_calls_produce_external_link_type ( )
158+ {
159+ var graph = WalkSource ( @"
160+ using System;
161+ namespace App
162+ {
163+ public class Logger
164+ {
165+ public void Log(string msg)
166+ {
167+ Console.WriteLine(msg);
168+ }
169+ }
170+ }" ) ;
68171
69- Assert . NotEmpty ( graph . Links ) ;
172+ Assert . Contains ( graph . Links , l =>
173+ l . Source == "App.Logger.Log" &&
174+ l . Type == "external" ) ;
70175 }
71176}
0 commit comments