Skip to content

Commit ca4776e

Browse files
authored
Merge pull request #20 from magic5644:feature-add-namespace-assembly-properties-to-dependencyRelation
Enhance dependency model and graph generation with async support
2 parents 2e63290 + 4812c7a commit ca4776e

File tree

9 files changed

+256
-39
lines changed

9 files changed

+256
-39
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"name": "C#: CodeLineCounter - new",
66
"type": "coreclr",
77
"request": "launch",
8-
"program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net8.0/CodeLineCounter.dll",
8+
"program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net9.0/CodeLineCounter.dll",
99
"args": ["-d", "${workspaceFolder}"],
1010
"cwd": "${workspaceFolder}",
1111
"stopAtEntry": false,

CodeLineCounter.Tests/DataExporterTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public void export_dependencies_creates_files_in_correct_formats()
217217
// Arrange
218218
var dependencies = new List<DependencyRelation>
219219
{
220-
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB", FilePath = "file1.cs", StartLine = 10 },
220+
new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 },
221221
};
222222

223223
var testFilePath = "test_export";

CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ namespace CodeLineCounter.Tests
66
public class DependencyGraphGeneratorTests
77
{
88
[Fact]
9-
public void generate_graph_with_valid_dependencies_creates_dot_file()
9+
public async Task generate_graph_with_valid_dependencies_creates_dot_file()
1010
{
1111
// Arrange
1212
var dependencies = new List<DependencyRelation>
1313
{
14-
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB" , FilePath = "path/to/file", StartLine = 1},
15-
new DependencyRelation { SourceClass = "ClassB", TargetClass = "ClassC", FilePath = "path/to/file", StartLine = 1}
14+
new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB" , TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1},
15+
new DependencyRelation { SourceClass = "ClassB", SourceNamespace = "NamespaceB", SourceAssembly = "AssemblyB", TargetClass = "ClassC", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1}
1616
};
1717

1818
string outputPath = Path.Combine(Path.GetTempPath(), "test_graph.dot");
1919

2020
// Act
21-
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
21+
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
2222

2323
// Assert
2424
Assert.True(File.Exists(outputPath));
25-
string content = File.ReadAllText(outputPath);
25+
string content = await File.ReadAllTextAsync(outputPath);
2626
Assert.Contains("ClassA", content);
2727
Assert.Contains("ClassB", content);
2828
Assert.Contains("ClassC", content);
@@ -32,18 +32,18 @@ public void generate_graph_with_valid_dependencies_creates_dot_file()
3232

3333
// Empty dependencies list
3434
[Fact]
35-
public void generate_graph_with_empty_dependencies_creates_empty_graph()
35+
public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
3636
{
3737
// Arrange
3838
var dependencies = new List<DependencyRelation>();
3939
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");
4040

4141
// Act
42-
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
42+
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
4343

4444
// Assert
4545
Assert.True(File.Exists(outputPath));
46-
string content = File.ReadAllText(outputPath);
46+
string content = await File.ReadAllTextAsync(outputPath);
4747
Assert.Contains("digraph", content);
4848
Assert.DoesNotContain("->", content);
4949

CodeLineCounter/CodeLineCounter.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
<PrivateAssets>all</PrivateAssets>
1515
</PackageReference>
1616
<PackageReference Include="CsvHelper" Version="33.0.1" />
17+
<PackageReference Include="DotNetGraph" Version="3.2.0" />
1718
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
19+
<PackageReference Include="QuikGraph" Version="2.5.0" />
1820
<PackageReference Include="QuikGraph.Graphviz" Version="2.5.0" />
1921
<PackageReference Include="Graphviz.NET" Version="1.0.0" />
2022

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,66 @@
11
using CsvHelper.Configuration.Attributes;
2+
23
namespace CodeLineCounter.Models
34
{
45
public class DependencyRelation
56
{
67
[Name("SourceClass")]
78
public required string SourceClass { get; set; }
89

10+
[Name("SourceNamespace")]
11+
public required string SourceNamespace { get; set; }
12+
13+
[Name("SourceAssembly")]
14+
public required string SourceAssembly { get; set; }
15+
916
[Name("TargetClass")]
1017
public required string TargetClass { get; set; }
1118

19+
[Name("TargetNamespace")]
20+
public required string TargetNamespace { get; set; }
21+
22+
[Name("TargetAssembly")]
23+
public required string TargetAssembly { get; set; }
24+
1225
[Name("FilePath")]
1326
public required string FilePath { get; set; }
1427

1528
[Name("StartLine")]
1629
public int StartLine { get; set; }
1730

31+
[Name("IncomingDegree")]
32+
public int IncomingDegree { get; set; }
33+
34+
[Name("OutgoingDegree")]
35+
public int OutgoingDegree { get; set; }
36+
37+
1838
public override bool Equals(object? obj)
1939
{
2040
if (obj is not DependencyRelation other)
2141
return false;
2242

2343
return SourceClass == other.SourceClass &&
44+
SourceNamespace == other.SourceNamespace &&
45+
SourceAssembly == other.SourceAssembly &&
2446
TargetClass == other.TargetClass &&
47+
TargetNamespace == other.TargetNamespace &&
48+
TargetAssembly == other.TargetAssembly &&
2549
FilePath == other.FilePath &&
2650
StartLine == other.StartLine;
2751
}
2852

2953
public override int GetHashCode()
3054
{
31-
return HashCode.Combine(SourceClass, TargetClass, FilePath, StartLine);
55+
return HashCode.Combine(
56+
SourceClass,
57+
SourceNamespace,
58+
SourceAssembly,
59+
TargetClass,
60+
TargetNamespace,
61+
TargetAssembly,
62+
FilePath,
63+
StartLine);
3264
}
3365
}
34-
}
66+
}

CodeLineCounter/Services/DependencyAnalyzer.cs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ private static string GetFullTypeName(ClassDeclarationSyntax classDeclaration)
7474
return classDeclaration.Identifier.Text;
7575
}
7676

77+
private static string GetSimpleTypeName(ClassDeclarationSyntax classDeclaration)
78+
{
79+
return classDeclaration.Identifier.Text;
80+
}
81+
7782
public static void AnalyzeProjects(IEnumerable<string> projectFiles)
7883
{
7984
foreach (var projectFile in projectFiles)
@@ -104,15 +109,19 @@ public static void AnalyzeFile(string filePath, string sourceCode)
104109

105110
Parallel.ForEach(classes, classDeclaration =>
106111
{
107-
var className = GetFullTypeName(classDeclaration);
112+
var className = GetSimpleTypeName(classDeclaration);
108113
var dependencies = ExtractDependencies(classDeclaration);
109114

110115
foreach (var dependency in dependencies)
111116
{
112117
var relation = new DependencyRelation
113118
{
114-
SourceClass = GetFullTypeName(classDeclaration),
115-
TargetClass = dependency,
119+
SourceClass = GetSimpleTypeName(classDeclaration),
120+
SourceNamespace = classDeclaration.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault()?.Name.ToString() ?? "",
121+
SourceAssembly = classDeclaration.SyntaxTree.GetRoot().DescendantNodes().OfType<CompilationUnitSyntax>().FirstOrDefault()?.Usings.FirstOrDefault()?.Name.ToString() ?? "",
122+
TargetClass = dependency.Split('.')[(dependency.Split('.').Length - 1)],
123+
TargetNamespace = dependency.Contains(".") ? dependency.Substring(0, dependency.LastIndexOf('.')) : "",
124+
TargetAssembly = dependency.Contains(".") ? dependency.Substring(0, dependency.LastIndexOf('.')) : "",
116125
FilePath = filePath,
117126
StartLine = classDeclaration.GetLocation().GetLineSpan().StartLinePosition.Line
118127
};
@@ -140,7 +149,7 @@ private static IEnumerable<string> ExtractDependencies(ClassDeclarationSyntax cl
140149
{
141150
var dependencies = new HashSet<string>();
142151
var usings = GetUsingsWithCurrentNamespace(classDeclaration);
143-
152+
144153

145154
AnalyzeInheritance(classDeclaration, usings, dependencies);
146155
AnalyzeClassMembers(classDeclaration, usings, dependencies);
@@ -335,13 +344,47 @@ private static List<string> SplitGenericType(string typeName)
335344

336345
public static List<DependencyRelation> GetDependencies()
337346
{
338-
return _dependencyMap.SelectMany(kvp => kvp.Value).ToList() ?? new List<DependencyRelation>();
347+
var allDependencies = _dependencyMap.SelectMany(kvp => kvp.Value).ToList();
348+
CalculateDegrees(allDependencies);
349+
return allDependencies;
350+
}
351+
352+
private static void CalculateDegrees(List<DependencyRelation> dependencies)
353+
{
354+
var incomingDegrees = new ConcurrentDictionary<string, int>();
355+
var outgoingDegrees = new ConcurrentDictionary<string, int>();
356+
357+
// Calculate degrees
358+
foreach (var dep in dependencies)
359+
{
360+
// Outgoing degree
361+
outgoingDegrees.AddOrUpdate(
362+
dep.SourceClass,
363+
1,
364+
(key, count) => count + 1
365+
);
366+
367+
// Incoming degree
368+
incomingDegrees.AddOrUpdate(
369+
dep.TargetClass,
370+
1,
371+
(key, count) => count + 1
372+
);
373+
}
374+
375+
// Update relations with calculated degrees
376+
foreach (var dep in dependencies)
377+
{
378+
dep.OutgoingDegree = outgoingDegrees.GetValueOrDefault(dep.SourceClass);
379+
dep.IncomingDegree = incomingDegrees.GetValueOrDefault(dep.TargetClass);
380+
}
339381
}
340382

341383
public static void Clear()
342384
{
343385
_dependencyMap.Clear();
344386
_solutionClasses.Clear();
345387
}
388+
346389
}
347390
}

0 commit comments

Comments
 (0)