Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"name": "C#: CodeLineCounter - new",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net8.0/CodeLineCounter.dll",
"program": "${workspaceFolder}/CodeLineCounter/bin/Debug/net9.0/CodeLineCounter.dll",
"args": ["-d", "${workspaceFolder}"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
Expand Down
2 changes: 1 addition & 1 deletion CodeLineCounter.Tests/DataExporterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public void export_dependencies_creates_files_in_correct_formats()
// Arrange
var dependencies = new List<DependencyRelation>
{
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB", FilePath = "file1.cs", StartLine = 10 },
new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "file1.cs", StartLine = 10 },
};

var testFilePath = "test_export";
Expand Down
16 changes: 8 additions & 8 deletions CodeLineCounter.Tests/DependencyGraphGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ namespace CodeLineCounter.Tests
public class DependencyGraphGeneratorTests
{
[Fact]
public void generate_graph_with_valid_dependencies_creates_dot_file()
public async Task generate_graph_with_valid_dependencies_creates_dot_file()
{
// Arrange
var dependencies = new List<DependencyRelation>
{
new DependencyRelation { SourceClass = "ClassA", TargetClass = "ClassB" , FilePath = "path/to/file", StartLine = 1},
new DependencyRelation { SourceClass = "ClassB", TargetClass = "ClassC", FilePath = "path/to/file", StartLine = 1}
new DependencyRelation { SourceClass = "ClassA", SourceNamespace = "NamespaceA", SourceAssembly = "AssemblyA", TargetClass = "ClassB" , TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1},
new DependencyRelation { SourceClass = "ClassB", SourceNamespace = "NamespaceB", SourceAssembly = "AssemblyB", TargetClass = "ClassC", TargetNamespace = "NamespaceB", TargetAssembly = "AssemblyB", FilePath = "path/to/file", StartLine = 1}
};

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

// Act
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);

// Assert
Assert.True(File.Exists(outputPath));
string content = File.ReadAllText(outputPath);
string content = await File.ReadAllTextAsync(outputPath);
Assert.Contains("ClassA", content);
Assert.Contains("ClassB", content);
Assert.Contains("ClassC", content);
Expand All @@ -32,18 +32,18 @@ public void generate_graph_with_valid_dependencies_creates_dot_file()

// Empty dependencies list
[Fact]
public void generate_graph_with_empty_dependencies_creates_empty_graph()
public async Task generate_graph_with_empty_dependencies_creates_empty_graph()
{
// Arrange
var dependencies = new List<DependencyRelation>();
string outputPath = Path.Combine(Path.GetTempPath(), "empty_graph.dot");

// Act
DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);
await DependencyGraphGenerator.GenerateGraph(dependencies, outputPath);

// Assert
Assert.True(File.Exists(outputPath));
string content = File.ReadAllText(outputPath);
string content = await File.ReadAllTextAsync(outputPath);
Assert.Contains("digraph", content);
Assert.DoesNotContain("->", content);

Expand Down
2 changes: 2 additions & 0 deletions CodeLineCounter/CodeLineCounter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="DotNetGraph" Version="3.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageReference Include="QuikGraph" Version="2.5.0" />
<PackageReference Include="QuikGraph.Graphviz" Version="2.5.0" />
<PackageReference Include="Graphviz.NET" Version="1.0.0" />

Expand Down
36 changes: 34 additions & 2 deletions CodeLineCounter/Models/DependencyRelation.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,66 @@
using CsvHelper.Configuration.Attributes;

namespace CodeLineCounter.Models
{
public class DependencyRelation
{
[Name("SourceClass")]
public required string SourceClass { get; set; }

[Name("SourceNamespace")]
public required string SourceNamespace { get; set; }

[Name("SourceAssembly")]
public required string SourceAssembly { get; set; }

[Name("TargetClass")]
public required string TargetClass { get; set; }

[Name("TargetNamespace")]
public required string TargetNamespace { get; set; }

[Name("TargetAssembly")]
public required string TargetAssembly { get; set; }

[Name("FilePath")]
public required string FilePath { get; set; }

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

[Name("IncomingDegree")]
public int IncomingDegree { get; set; }

[Name("OutgoingDegree")]
public int OutgoingDegree { get; set; }


public override bool Equals(object? obj)
{
if (obj is not DependencyRelation other)
return false;

return SourceClass == other.SourceClass &&
SourceNamespace == other.SourceNamespace &&
SourceAssembly == other.SourceAssembly &&
TargetClass == other.TargetClass &&
TargetNamespace == other.TargetNamespace &&
TargetAssembly == other.TargetAssembly &&
FilePath == other.FilePath &&
StartLine == other.StartLine;
}

public override int GetHashCode()
{
return HashCode.Combine(SourceClass, TargetClass, FilePath, StartLine);
return HashCode.Combine(
SourceClass,
SourceNamespace,
SourceAssembly,
TargetClass,
TargetNamespace,
TargetAssembly,
FilePath,
StartLine);
}
}
}
}
53 changes: 48 additions & 5 deletions CodeLineCounter/Services/DependencyAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
return classDeclaration.Identifier.Text;
}

private static string GetSimpleTypeName(ClassDeclarationSyntax classDeclaration)
{
return classDeclaration.Identifier.Text;
}

public static void AnalyzeProjects(IEnumerable<string> projectFiles)
{
foreach (var projectFile in projectFiles)
Expand Down Expand Up @@ -104,15 +109,19 @@

Parallel.ForEach(classes, classDeclaration =>
{
var className = GetFullTypeName(classDeclaration);
var className = GetSimpleTypeName(classDeclaration);
var dependencies = ExtractDependencies(classDeclaration);

foreach (var dependency in dependencies)
{
var relation = new DependencyRelation
{
SourceClass = GetFullTypeName(classDeclaration),
TargetClass = dependency,
SourceClass = GetSimpleTypeName(classDeclaration),
SourceNamespace = classDeclaration.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault()?.Name.ToString() ?? "",
SourceAssembly = classDeclaration.SyntaxTree.GetRoot().DescendantNodes().OfType<CompilationUnitSyntax>().FirstOrDefault()?.Usings.FirstOrDefault()?.Name.ToString() ?? "",

Check warning on line 121 in CodeLineCounter/Services/DependencyAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 121 in CodeLineCounter/Services/DependencyAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
TargetClass = dependency.Split('.')[(dependency.Split('.').Length - 1)],
TargetNamespace = dependency.Contains(".") ? dependency.Substring(0, dependency.LastIndexOf('.')) : "",
TargetAssembly = dependency.Contains(".") ? dependency.Substring(0, dependency.LastIndexOf('.')) : "",
FilePath = filePath,
StartLine = classDeclaration.GetLocation().GetLineSpan().StartLinePosition.Line
};
Expand Down Expand Up @@ -140,7 +149,7 @@
{
var dependencies = new HashSet<string>();
var usings = GetUsingsWithCurrentNamespace(classDeclaration);


AnalyzeInheritance(classDeclaration, usings, dependencies);
AnalyzeClassMembers(classDeclaration, usings, dependencies);
Expand Down Expand Up @@ -335,13 +344,47 @@

public static List<DependencyRelation> GetDependencies()
{
return _dependencyMap.SelectMany(kvp => kvp.Value).ToList() ?? new List<DependencyRelation>();
var allDependencies = _dependencyMap.SelectMany(kvp => kvp.Value).ToList();
CalculateDegrees(allDependencies);
return allDependencies;
}

private static void CalculateDegrees(List<DependencyRelation> dependencies)
{
var incomingDegrees = new ConcurrentDictionary<string, int>();
var outgoingDegrees = new ConcurrentDictionary<string, int>();

// Calculate degrees
foreach (var dep in dependencies)
{
// Outgoing degree
outgoingDegrees.AddOrUpdate(
dep.SourceClass,
1,
(key, count) => count + 1
);

// Incoming degree
incomingDegrees.AddOrUpdate(
dep.TargetClass,
1,
(key, count) => count + 1
);
}

// Update relations with calculated degrees
foreach (var dep in dependencies)
{
dep.OutgoingDegree = outgoingDegrees.GetValueOrDefault(dep.SourceClass);
dep.IncomingDegree = incomingDegrees.GetValueOrDefault(dep.TargetClass);
}
}

public static void Clear()
{
_dependencyMap.Clear();
_solutionClasses.Clear();
}

}
}
Loading
Loading