595 lines
19 KiB
C#
Executable File
595 lines
19 KiB
C#
Executable File
using System;
|
|
using System.Linq;
|
|
|
|
namespace MarketAlly.ProjectDetector.Detectors;
|
|
|
|
public class TestProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
// Test framework packages are the strongest indicator
|
|
if (analysis.HasTestFramework)
|
|
{
|
|
confidence += 60;
|
|
reasons.Add("Has test framework packages");
|
|
}
|
|
|
|
// More specific test file patterns
|
|
var testFilePatterns = new[] { ".Test.", ".Tests.", ".Spec.", ".Specs.", "_Test.", "_Tests.", "_Spec.", "_Specs." };
|
|
if (analysis.Files.Any(f => testFilePatterns.Any(pattern => f.Contains(pattern, StringComparison.OrdinalIgnoreCase))))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains test files with standard naming");
|
|
}
|
|
|
|
// Test folder patterns - must be more specific
|
|
var testFolderPatterns = new[] { "Tests", "Test", "Specs", "Spec", "UnitTests", "IntegrationTests", "E2ETests" };
|
|
if (analysis.Folders.Any(f => testFolderPatterns.Any(pattern => f.Equals(pattern, StringComparison.OrdinalIgnoreCase) ||
|
|
f.EndsWith($"/{pattern}", StringComparison.OrdinalIgnoreCase) ||
|
|
f.EndsWith($"\\{pattern}", StringComparison.OrdinalIgnoreCase))))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Contains dedicated test folders");
|
|
}
|
|
|
|
// Project name ending with .Tests or .Test is a strong indicator
|
|
if (analysis.ProjectName != null && (analysis.ProjectName.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase) ||
|
|
analysis.ProjectName.EndsWith(".Test", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Project name indicates test project");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 50,
|
|
ProjectType = ProjectType.Tests,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class DatabaseProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
// SQL Server Data Tools project
|
|
if (analysis.Files.Any(f => f.EndsWith(".sqlproj", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 80;
|
|
reasons.Add("SQL Server Data Tools project");
|
|
}
|
|
|
|
// Entity Framework or database packages
|
|
var dbPackages = new[]
|
|
{
|
|
"Microsoft.EntityFrameworkCore", "EntityFramework", "Dapper",
|
|
"Microsoft.Data.SqlClient", "Npgsql", "MySql.Data",
|
|
"MongoDB.Driver", "RavenDB.Client"
|
|
};
|
|
|
|
if (dbPackages.Any(pkg => analysis.PackageReferences.Any(p => p.Contains(pkg))))
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Has database packages");
|
|
}
|
|
|
|
// Migration files
|
|
if (analysis.Folders.Any(f => f.Contains("Migrations")))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Contains Migrations folder");
|
|
}
|
|
|
|
// Database-related naming
|
|
if (analysis.Files.Any(f => f.Contains("Migration") || f.Contains("DbContext")))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains database-related files");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 50,
|
|
ProjectType = ProjectType.Database,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class MauiProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.HasMauiSdk || analysis.HasMaui)
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Has MAUI properties or packages");
|
|
}
|
|
|
|
if (analysis.Folders.Any(f => f.StartsWith("Platforms")))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Contains Platforms folder");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Equals("MauiProgram.cs", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Contains MauiProgram.cs");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Equals("App.xaml", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 15;
|
|
reasons.Add("Contains App.xaml");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Contains("MauiIcon") || f.Contains("MauiSplashScreen")))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains MAUI app resources");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Contains("ApplicationTitle") || f.Contains("ApplicationId")))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Contains application metadata");
|
|
}
|
|
|
|
// Distinguish between MAUI app and MAUI library
|
|
var projectType = ProjectType.MobileApp;
|
|
|
|
// Enhanced library detection
|
|
var isLibrary = IsMauiLibrary(analysis);
|
|
|
|
if (isLibrary)
|
|
{
|
|
projectType = ProjectType.Library;
|
|
reasons.Add("MAUI class library");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 40,
|
|
ProjectType = projectType,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
|
|
private bool IsMauiLibrary(ProjectAnalysis analysis)
|
|
{
|
|
// PRIORITY 1: If it's explicitly executable, it's NOT a library
|
|
if (analysis.IsExecutable || analysis.OutputType.Equals("Exe", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false; // Executable = App, not Library
|
|
}
|
|
|
|
// PRIORITY 2: If it's explicitly a library, it IS a library
|
|
if (analysis.IsLibrary || analysis.OutputType.Equals("Library", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// PRIORITY 3: Check for NuGet package generation (strong indicator of library)
|
|
if (analysis.PackageReferences.Any(p => p.Contains("GeneratePackageOnBuild")))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// PRIORITY 4: Check for package metadata in the file content
|
|
var packageMetadataKeywords = new[] { "PackageId", "Authors", "Description", "GeneratePackageOnBuild" };
|
|
if (packageMetadataKeywords.Any(keyword =>
|
|
analysis.DetectionEvidence.Any(evidence => evidence.Contains(keyword))))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// PRIORITY 5: Check for application-specific metadata (indicates app)
|
|
var appMetadataKeywords = new[] { "ApplicationTitle", "ApplicationId", "ApplicationVersion" };
|
|
if (appMetadataKeywords.Any(keyword =>
|
|
analysis.DetectionEvidence.Any(evidence => evidence.Contains(keyword))))
|
|
{
|
|
return false; // Has app metadata = App, not Library
|
|
}
|
|
|
|
// Default: If no clear indicators, assume it's an app (since it has MAUI)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public class BlazorProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.HasBlazor)
|
|
{
|
|
confidence += 50;
|
|
reasons.Add("Has Blazor packages");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Contains Razor components");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Equals("_Imports.razor", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Contains _Imports.razor");
|
|
}
|
|
|
|
if (analysis.Folders.Any(f => f.Equals("wwwroot", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 15;
|
|
reasons.Add("Contains wwwroot folder");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 50,
|
|
ProjectType = ProjectType.WebApp,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class WebApiProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.HasWebSdk)
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Uses Microsoft.NET.Sdk.Web");
|
|
}
|
|
|
|
if (analysis.HasAspNetCore)
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Has ASP.NET Core packages");
|
|
}
|
|
|
|
// API-specific indicators
|
|
var apiPackages = new[]
|
|
{
|
|
"Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.OpenApi",
|
|
"Swashbuckle", "Microsoft.AspNetCore.Mvc.Api",
|
|
"Microsoft.AspNetCore.Mvc.WebApiCompatShim"
|
|
};
|
|
|
|
if (apiPackages.Any(pkg => analysis.PackageReferences.Any(p => p.Contains(pkg))))
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Has API-specific packages");
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.Contains("Controller") && f.EndsWith(".cs")))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Contains API controllers");
|
|
}
|
|
|
|
if (analysis.Folders.Any(f => f.Equals("Controllers", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains Controllers folder");
|
|
}
|
|
|
|
// Exclude if it has UI components
|
|
if (analysis.Files.Any(f => f.EndsWith(".razor") || f.EndsWith(".cshtml")))
|
|
{
|
|
confidence -= 20;
|
|
reasons.Add("Contains UI components (likely WebApp)");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 50,
|
|
ProjectType = ProjectType.API,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class WebAppProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.HasWebSdk)
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Uses Microsoft.NET.Sdk.Web");
|
|
}
|
|
|
|
if (analysis.HasAspNetCore)
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Has ASP.NET Core packages");
|
|
}
|
|
|
|
// UI components
|
|
if (analysis.Files.Any(f => f.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 35;
|
|
reasons.Add("Contains Razor views");
|
|
}
|
|
|
|
if (analysis.Folders.Any(f => f.Equals("Views", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains Views folder");
|
|
}
|
|
|
|
if (analysis.Folders.Any(f => f.Equals("wwwroot", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Contains wwwroot folder");
|
|
}
|
|
|
|
// MVC patterns
|
|
if (analysis.Folders.Any(f => f.Equals("Models", StringComparison.OrdinalIgnoreCase)) &&
|
|
analysis.Folders.Any(f => f.Equals("Controllers", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 15;
|
|
reasons.Add("Has MVC structure");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 40,
|
|
ProjectType = ProjectType.WebApp,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class DesktopAppProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
var projectType = ProjectType.Other;
|
|
|
|
// WPF
|
|
if (analysis.HasWpf)
|
|
{
|
|
confidence += 50;
|
|
reasons.Add("Has WPF enabled");
|
|
projectType = ProjectType.WpfApp;
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) && !analysis.HasMaui))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Contains XAML files (non-MAUI)");
|
|
projectType = ProjectType.WpfApp;
|
|
}
|
|
|
|
// WinForms
|
|
if (analysis.HasWinForms)
|
|
{
|
|
confidence += 50;
|
|
reasons.Add("Has WinForms enabled");
|
|
projectType = ProjectType.WinFormsApp;
|
|
}
|
|
|
|
if (analysis.Files.Any(f => f.EndsWith(".Designer.cs", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains WinForms designer files");
|
|
projectType = ProjectType.WinFormsApp;
|
|
}
|
|
|
|
// Avalonia
|
|
var avaloniaPackages = new[] { "Avalonia", "Avalonia.Desktop", "Avalonia.Themes.Fluent" };
|
|
if (avaloniaPackages.Any(pkg => analysis.PackageReferences.Any(p => p.Contains(pkg))))
|
|
{
|
|
confidence += 50;
|
|
reasons.Add("Has Avalonia packages");
|
|
projectType = ProjectType.AvaloniaApp;
|
|
}
|
|
|
|
// Desktop SDK
|
|
if (analysis.HasDesktopSdk)
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Uses Windows Desktop SDK");
|
|
}
|
|
|
|
// Windows executable
|
|
if (analysis.OutputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
confidence += 15;
|
|
reasons.Add("Windows executable output");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 40,
|
|
ProjectType = projectType,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class WorkerServiceProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.HasWorkerSdk)
|
|
{
|
|
confidence += 60;
|
|
reasons.Add("Uses Microsoft.NET.Sdk.Worker");
|
|
}
|
|
|
|
if (analysis.HasWorkerService)
|
|
{
|
|
confidence += 50;
|
|
reasons.Add("Has Worker Service configuration");
|
|
}
|
|
|
|
// Worker service packages
|
|
var workerPackages = new[]
|
|
{
|
|
"Microsoft.Extensions.Hosting", "Microsoft.Extensions.Hosting.WindowsServices",
|
|
"Microsoft.Extensions.Hosting.Systemd"
|
|
};
|
|
|
|
if (workerPackages.Any(pkg => analysis.PackageReferences.Any(p => p.Contains(pkg))))
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Has hosting packages");
|
|
}
|
|
|
|
// Worker patterns - including monitoring services
|
|
if (analysis.Files.Any(f => f.Contains("Worker") || f.Contains("BackgroundService") ||
|
|
f.Contains("Monitor") || f.Contains("Service")))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Contains service-related files");
|
|
}
|
|
|
|
// Project name patterns for services
|
|
if (analysis.ProjectName != null &&
|
|
(analysis.ProjectName.Contains("monitor", StringComparison.OrdinalIgnoreCase) ||
|
|
analysis.ProjectName.Contains("service", StringComparison.OrdinalIgnoreCase) ||
|
|
analysis.ProjectName.Contains("worker", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("Project name indicates service");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 50,
|
|
ProjectType = ProjectType.Service,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class LibraryProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.IsLibrary)
|
|
{
|
|
confidence += 40;
|
|
reasons.Add("Library output type");
|
|
}
|
|
|
|
// NuGet package metadata
|
|
if (analysis.DetectionEvidence.Any(e => e.Contains("GeneratePackageOnBuild")))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Generates NuGet package");
|
|
}
|
|
|
|
if (analysis.DetectionEvidence.Any(e => e.Contains("PackageId") || e.Contains("Authors")))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Has NuGet metadata");
|
|
}
|
|
|
|
// No executable markers
|
|
if (!analysis.IsExecutable && string.IsNullOrEmpty(analysis.OutputType))
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("No executable output");
|
|
}
|
|
|
|
// Common library patterns
|
|
if (analysis.Files.Any(f => f.Contains(".Core.") || f.Contains(".Common.") || f.Contains(".Shared.")))
|
|
{
|
|
confidence += 15;
|
|
reasons.Add("Common library naming patterns");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 40,
|
|
ProjectType = ProjectType.Library,
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
}
|
|
|
|
public class ConsoleAppProjectDetector : IProjectTypeDetector
|
|
{
|
|
public ProjectTypeDetectionResult Detect(ProjectAnalysis analysis)
|
|
{
|
|
var confidence = 0;
|
|
var reasons = new List<string>();
|
|
|
|
if (analysis.OutputType.Equals("Exe", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Console executable output");
|
|
}
|
|
|
|
// Check for Program.cs with Main method pattern
|
|
if (analysis.Files.Any(f => f.Equals("Program.cs", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
confidence += 25;
|
|
reasons.Add("Contains Program.cs");
|
|
}
|
|
|
|
// No UI frameworks
|
|
if (!analysis.HasWpf && !analysis.HasWinForms && !analysis.HasMaui && !analysis.HasBlazor)
|
|
{
|
|
confidence += 20;
|
|
reasons.Add("No UI framework detected");
|
|
}
|
|
|
|
// Console-specific packages
|
|
if (analysis.HasCommandLine)
|
|
{
|
|
confidence += 30;
|
|
reasons.Add("Has command line packages");
|
|
}
|
|
|
|
return new ProjectTypeDetectionResult
|
|
{
|
|
IsMatch = confidence >= 40,
|
|
ProjectType = ProjectType.Other, // Console apps typically fall under "Other"
|
|
Confidence = Math.Min(confidence, 100),
|
|
Reason = string.Join(", ", reasons)
|
|
};
|
|
}
|
|
} |