MarketAlly.AIPlugin.Extensions/MarketAlly.ProjectDetector/ProjectAnalysis.cs

436 lines
16 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace MarketAlly.ProjectDetector;
public class ProjectAnalysis
{
// Project Information
public string ProjectName { get; set; } = string.Empty;
// Solution Information
public bool HasSolutionFile { get; set; }
// SDK Information
public string Sdk { get; set; } = string.Empty;
public bool HasMauiSdk { get; set; }
public bool HasWebSdk { get; set; }
public bool HasWorkerSdk { get; set; }
public bool HasDesktopSdk { get; set; }
// Output Information
public string OutputType { get; set; } = string.Empty;
public bool IsLibrary { get; set; }
public bool IsExecutable { get; set; }
public List<string> TargetFrameworks { get; set; } = new();
// Package Management Files
public List<string> PackageReferences { get; set; } = new();
public List<string> ProjectReferences { get; set; } = new();
// Node.js Analysis
public Dictionary<string, object> PackageJsonDependencies { get; set; } = new();
public Dictionary<string, object> PackageJsonDevDependencies { get; set; } = new();
public Dictionary<string, object> PackageJsonScripts { get; set; } = new();
// Python Analysis
public List<string> PythonRequirements { get; set; } = new();
public Dictionary<string, object> PyProjectConfig { get; set; } = new();
// Java Analysis
public Dictionary<string, object> MavenConfig { get; set; } = new();
public Dictionary<string, object> GradleConfig { get; set; } = new();
// Other Language Analysis
public Dictionary<string, object> GoModConfig { get; set; } = new();
public Dictionary<string, object> CargoConfig { get; set; } = new();
// Project Structure
public List<string> Folders { get; set; } = new();
public List<string> Files { get; set; } = new();
// Specific Technology Markers
public bool HasAspNetCore { get; set; }
public bool HasBlazor { get; set; }
public bool HasMaui { get; set; }
public bool HasWpf { get; set; }
public bool HasWinForms { get; set; }
public bool HasXamarin { get; set; }
public bool HasEntityFramework { get; set; }
public bool HasTestFramework { get; set; }
public bool HasWorkerService { get; set; }
public bool HasAzureFunctions { get; set; }
public bool HasCommandLine { get; set; }
public bool HasPluginFramework { get; set; }
public bool HasInfrastructure { get; set; }
// Analysis Results
public int ConfidenceScore { get; set; }
public string DetectionReason { get; set; } = string.Empty;
public List<string> DetectionEvidence { get; set; } = new();
public void AnalyzeProjectFile(string projectFilePath)
{
try
{
var extension = Path.GetExtension(projectFilePath).ToLowerInvariant();
switch (extension)
{
case ".csproj":
case ".vbproj":
case ".fsproj":
AnalyzeDotNetProject(projectFilePath);
break;
case ".json" when Path.GetFileName(projectFilePath).Equals("package.json", StringComparison.OrdinalIgnoreCase):
AnalyzePackageJson(projectFilePath);
break;
case ".xml" when Path.GetFileName(projectFilePath).Equals("pom.xml", StringComparison.OrdinalIgnoreCase):
AnalyzePomXml(projectFilePath);
break;
case ".gradle" when Path.GetFileName(projectFilePath).Equals("build.gradle", StringComparison.OrdinalIgnoreCase):
AnalyzeBuildGradle(projectFilePath);
break;
}
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error reading project file: {ex.Message}");
}
}
private void AnalyzeDotNetProject(string csprojPath)
{
try
{
var content = File.ReadAllText(csprojPath);
// Try XML parsing first for better accuracy
try
{
var doc = XDocument.Parse(content);
AnalyzeProjectXml(doc);
}
catch
{
// Fallback to string parsing
AnalyzeProjectString(content);
}
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error reading .NET project file: {ex.Message}");
}
}
private void AnalyzeProjectXml(XDocument doc)
{
var project = doc.Root;
// SDK Detection
Sdk = project?.Attribute("Sdk")?.Value ?? string.Empty;
HasMauiSdk = Sdk.Contains("Microsoft.NET.Sdk.Maui");
HasWebSdk = Sdk.Contains("Microsoft.NET.Sdk.Web");
HasWorkerSdk = Sdk.Contains("Microsoft.NET.Sdk.Worker");
HasDesktopSdk = Sdk.Contains("Microsoft.NET.Sdk.WindowsDesktop");
// Properties
var properties = project?.Descendants("PropertyGroup");
foreach (var propGroup in properties ?? Enumerable.Empty<XElement>())
{
OutputType = propGroup.Element("OutputType")?.Value ?? OutputType;
// Target frameworks
var targetFramework = propGroup.Element("TargetFramework")?.Value;
if (!string.IsNullOrEmpty(targetFramework))
TargetFrameworks.Add(targetFramework);
var targetFrameworks = propGroup.Element("TargetFrameworks")?.Value;
if (!string.IsNullOrEmpty(targetFrameworks))
TargetFrameworks.AddRange(targetFrameworks.Split(';'));
// Technology markers
HasMaui = HasMaui || propGroup.Element("UseMaui")?.Value == "true";
HasWpf = HasWpf || propGroup.Element("UseWPF")?.Value == "true";
HasWinForms = HasWinForms || propGroup.Element("UseWindowsForms")?.Value == "true";
// Worker service
HasWorkerService = HasWorkerService || propGroup.Element("Worker")?.Value == "true";
// Executable/Library detection
if (propGroup.Element("GeneratePackageOnBuild")?.Value == "true")
{
DetectionEvidence.Add("Has GeneratePackageOnBuild=true");
}
var appMetadata = new[] { "ApplicationTitle", "ApplicationId", "ApplicationVersion", "ApplicationDisplayVersion" };
foreach (var metadata in appMetadata)
{
if (propGroup.Element(metadata) != null)
{
DetectionEvidence.Add($"Has {metadata} (app metadata)");
}
}
// Check for package metadata
var packageMetadata = new[] { "PackageId", "Version", "Authors", "Description", "GeneratePackageOnBuild" };
foreach (var metadata in packageMetadata)
{
if (propGroup.Element(metadata) != null)
{
DetectionEvidence.Add($"Has {metadata} (NuGet package metadata)");
}
}
}
// Package References
var packageRefs = project?.Descendants("PackageReference");
foreach (var packageRef in packageRefs ?? Enumerable.Empty<XElement>())
{
var include = packageRef.Attribute("Include")?.Value;
if (!string.IsNullOrEmpty(include))
{
PackageReferences.Add(include);
}
}
// Project References
var projectRefs = project?.Descendants("ProjectReference");
foreach (var projectRef in projectRefs ?? Enumerable.Empty<XElement>())
{
var include = projectRef.Attribute("Include")?.Value;
if (!string.IsNullOrEmpty(include))
{
ProjectReferences.Add(include);
}
}
AnalyzePackageReferences();
IsLibrary = OutputType.Equals("Library", StringComparison.OrdinalIgnoreCase) ||
(string.IsNullOrEmpty(OutputType) && !IsExecutable);
IsExecutable = OutputType.Equals("Exe", StringComparison.OrdinalIgnoreCase) ||
OutputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase);
}
private void AnalyzeProjectString(string content)
{
// SDK Detection
var sdkMatch = Regex.Match(content, @"<Project\s+Sdk\s*=\s*""([^""]+)""");
if (sdkMatch.Success)
{
Sdk = sdkMatch.Groups[1].Value;
HasMauiSdk = Sdk.Contains("Microsoft.NET.Sdk.Maui");
HasWebSdk = Sdk.Contains("Microsoft.NET.Sdk.Web");
HasWorkerSdk = Sdk.Contains("Microsoft.NET.Sdk.Worker");
HasDesktopSdk = Sdk.Contains("Microsoft.NET.Sdk.WindowsDesktop");
}
// Output Type
var outputTypeMatch = Regex.Match(content, @"<OutputType>([^<]+)</OutputType>");
if (outputTypeMatch.Success)
{
OutputType = outputTypeMatch.Groups[1].Value;
}
// Package References
var packageMatches = Regex.Matches(content, @"<PackageReference\s+Include\s*=\s*""([^""]+)""");
foreach (Match match in packageMatches)
{
PackageReferences.Add(match.Groups[1].Value);
}
// Technology markers
HasMaui = HasMaui || content.Contains("<UseMaui>true</UseMaui>");
HasWpf = HasWpf || content.Contains("<UseWPF>true</UseWPF>");
HasWinForms = HasWinForms || content.Contains("<UseWindowsForms>true</UseWindowsForms>");
AnalyzePackageReferences();
IsLibrary = OutputType.Equals("Library", StringComparison.OrdinalIgnoreCase);
IsExecutable = OutputType.Equals("Exe", StringComparison.OrdinalIgnoreCase) ||
OutputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase);
}
private void AnalyzePackageReferences()
{
var packages = string.Join(" ", PackageReferences);
HasAspNetCore = PackageReferences.Any(p => p.Contains("Microsoft.AspNetCore"));
HasBlazor = PackageReferences.Any(p => p.Contains("Microsoft.AspNetCore.Components"));
HasEntityFramework = PackageReferences.Any(p => p.Contains("EntityFramework"));
HasXamarin = PackageReferences.Any(p => p.Contains("Xamarin"));
// Test frameworks
var testFrameworks = new[] { "xunit", "nunit", "mstest", "Microsoft.NET.Test.Sdk" };
HasTestFramework = PackageReferences.Any(p => testFrameworks.Any(tf => p.Contains(tf, StringComparison.OrdinalIgnoreCase)));
// Azure Functions
var azureFunctionsPackages = new[] { "Microsoft.NET.Sdk.Functions", "Microsoft.Azure.Functions", "Microsoft.Azure.WebJobs" };
HasAzureFunctions = PackageReferences.Any(p => azureFunctionsPackages.Any(af => p.Contains(af)));
// Command line packages
var commandLinePackages = new[] { "CommandLineParser", "System.CommandLine", "McMaster.Extensions.CommandLineUtils" };
HasCommandLine = PackageReferences.Any(p => commandLinePackages.Any(cl => p.Contains(cl)));
// Plugin frameworks
var pluginPackages = new[] { "Microsoft.Extensions.DependencyInjection.Abstractions", "System.Composition", "MEF" };
HasPluginFramework = PackageReferences.Any(p => pluginPackages.Any(pf => p.Contains(pf)));
// Infrastructure packages
var infraPackages = new[] { "Pulumi", "Terraform", "AWS.CDK", "Azure.ResourceManager" };
HasInfrastructure = PackageReferences.Any(p => infraPackages.Any(inf => p.Contains(inf)));
}
private void AnalyzePackageJson(string packageJsonPath)
{
try
{
var content = File.ReadAllText(packageJsonPath);
var packageJson = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(content);
if (packageJson != null)
{
// Dependencies
if (packageJson.TryGetValue("dependencies", out var deps) && deps.ValueKind == JsonValueKind.Object)
{
foreach (var dep in deps.EnumerateObject())
{
PackageJsonDependencies[dep.Name] = dep.Value.ToString();
}
}
// Dev Dependencies
if (packageJson.TryGetValue("devDependencies", out var devDeps) && devDeps.ValueKind == JsonValueKind.Object)
{
foreach (var dep in devDeps.EnumerateObject())
{
PackageJsonDevDependencies[dep.Name] = dep.Value.ToString();
}
}
// Scripts
if (packageJson.TryGetValue("scripts", out var scripts) && scripts.ValueKind == JsonValueKind.Object)
{
foreach (var script in scripts.EnumerateObject())
{
PackageJsonScripts[script.Name] = script.Value.ToString();
}
}
DetectionEvidence.Add("Found package.json (Node.js project)");
}
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error analyzing package.json: {ex.Message}");
}
}
private void AnalyzePomXml(string pomPath)
{
try
{
var content = File.ReadAllText(pomPath);
MavenConfig["content"] = content;
DetectionEvidence.Add("Found pom.xml (Maven project)");
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error analyzing pom.xml: {ex.Message}");
}
}
private void AnalyzeBuildGradle(string gradlePath)
{
try
{
var content = File.ReadAllText(gradlePath);
GradleConfig["content"] = content;
DetectionEvidence.Add("Found build.gradle (Gradle project)");
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error analyzing build.gradle: {ex.Message}");
}
}
public void AnalyzeProjectStructure(string projectPath)
{
try
{
// Get folders
Folders = Directory.GetDirectories(projectPath, "*", SearchOption.TopDirectoryOnly)
.Select(d => Path.GetFileName(d))
.Where(d => !d.StartsWith("."))
.ToList();
// Get files (top level only for performance)
Files = Directory.GetFiles(projectPath, "*", SearchOption.TopDirectoryOnly)
.Select(f => Path.GetFileName(f))
.Where(f => !f.StartsWith("."))
.ToList();
// Add source file detection from subdirectories
var sourceExtensions = new[] { "*.cs", "*.js", "*.ts", "*.py", "*.java", "*.go", "*.rs", "*.cpp", "*.c", "*.h" };
foreach (var ext in sourceExtensions)
{
var sourceFiles = Directory.GetFiles(projectPath, ext, SearchOption.AllDirectories)
.Select(f => Path.GetRelativePath(projectPath, f))
.ToList();
if (sourceFiles.Any())
{
Files.AddRange(sourceFiles);
}
}
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error analyzing project structure: {ex.Message}");
}
}
public void AnalyzeSourceFiles(string projectPath)
{
try
{
// Python-specific files
var requirementsPath = Path.Combine(projectPath, "requirements.txt");
if (File.Exists(requirementsPath))
{
PythonRequirements = File.ReadAllLines(requirementsPath)
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
.ToList();
DetectionEvidence.Add("Found requirements.txt");
}
// Go modules
var goModPath = Path.Combine(projectPath, "go.mod");
if (File.Exists(goModPath))
{
var content = File.ReadAllText(goModPath);
GoModConfig["content"] = content;
DetectionEvidence.Add("Found go.mod");
}
// Rust cargo
var cargoPath = Path.Combine(projectPath, "Cargo.toml");
if (File.Exists(cargoPath))
{
var content = File.ReadAllText(cargoPath);
CargoConfig["content"] = content;
DetectionEvidence.Add("Found Cargo.toml");
}
}
catch (Exception ex)
{
DetectionEvidence.Add($"Error analyzing source files: {ex.Message}");
}
}
}