Skip to content
Merged
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
52 changes: 40 additions & 12 deletions src/ProjGraph.Lib/Services/ClassAnalysis/WorkspaceTypeDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ namespace ProjGraph.Lib.Services.ClassAnalysis;
/// </summary>
public static class WorkspaceTypeDiscovery
{
/// <summary>
/// Directories to skip during recursive file search for better performance.
/// </summary>
private static readonly HashSet<string> ExcludedDirectories = new(StringComparer.OrdinalIgnoreCase)
{
"bin", "obj", ".git", "node_modules"
};
/// <summary>
/// Finds the file containing the definition of a specific type within a given directory or its subdirectories.
/// The method first attempts to search in common subdirectories for better performance, and if not found,
Expand Down Expand Up @@ -58,20 +65,25 @@ public static class WorkspaceTypeDiscovery
/// </returns>
private static async Task<string?> SearchDirectoryForTypeAsync(string directory, string typeName)
{
var options = new EnumerationOptions { RecurseSubdirectories = true, IgnoreInaccessible = true };
return await SearchDirectoryRecursiveAsync(directory, typeName);
}

foreach (var file in Directory.EnumerateFiles(directory, "*.cs", options))
/// <summary>
/// Recursively searches a directory and its subdirectories for a C# file containing a specific type definition.
/// This method manually handles recursion to avoid descending into common non-source directories for better performance.
/// </summary>
/// <param name="directory">The path of the directory to search.</param>
/// <param name="typeName">The name of the type to search for.</param>
/// <returns>
/// The full path of the file containing the type definition if found; otherwise, null.
/// </returns>
private static async Task<string?> SearchDirectoryRecursiveAsync(string directory, string typeName)
{
// Search files in the current directory
foreach (var file in Directory.EnumerateFiles(directory, "*.cs", new EnumerationOptions { IgnoreInaccessible = true }))
{
var fullPath = Path.GetFullPath(file);
// Skip common non-source directories
var pathSegments = fullPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if (pathSegments.Any(s => s is "bin" or "obj" or ".git" or "node_modules"))
{
continue;
}

// Simple string check first for performance
var content = await File.ReadAllTextAsync(fullPath);
var content = await File.ReadAllTextAsync(file);
if (!content.Contains($"class {typeName}") &&
!content.Contains($"interface {typeName}") &&
!content.Contains($"struct {typeName}") &&
Expand All @@ -90,7 +102,23 @@ public static class WorkspaceTypeDiscovery

if (hasType)
{
return fullPath;
return file;
}
}

// Recursively search subdirectories, skipping excluded directories
foreach (var subDir in Directory.EnumerateDirectories(directory, "*", new EnumerationOptions { IgnoreInaccessible = true }))
{
var dirName = Path.GetFileName(subDir);
if (ExcludedDirectories.Contains(dirName))
{
continue;
}

var result = await SearchDirectoryRecursiveAsync(subDir, typeName);
if (result != null)
{
return result;
}
}

Expand Down
Loading