using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; using MarketAlly.ProjectDetector; namespace MarketAlly.AIPlugin.Refactoring.SolutionConsole; public class MauiAwareSolutionScanner { public async Task AnalyzeSolution(string solutionPath) { var structure = new SolutionStructure { SolutionPath = solutionPath, SolutionName = Path.GetFileName(solutionPath) }; // Find .sln files var solutionFiles = Directory.GetFiles(solutionPath, "*.sln", SearchOption.TopDirectoryOnly); if (solutionFiles.Any()) { structure.SolutionFile = solutionFiles.First(); await ParseSolutionFile(structure); } // Discover all project files await DiscoverProjects(structure); // Analyze MAUI-specific structure await AnalyzeMauiProjects(structure); return structure; } private async Task ParseSolutionFile(SolutionStructure structure) { try { var solutionContent = await File.ReadAllTextAsync(structure.SolutionFile); var lines = solutionContent.Split('\n'); foreach (var line in lines) { // Parse project entries: Project("{GUID}") = "ProjectName", "ProjectPath", "{ProjectGUID}" var projectMatch = Regex.Match(line, @"Project\(""([^""]+)""\)\s*=\s*""([^""]+)"",\s*""([^""]+)"",\s*""([^""]+)"""); if (projectMatch.Success) { var projectTypeGuid = projectMatch.Groups[1].Value; var projectName = projectMatch.Groups[2].Value; var projectPath = projectMatch.Groups[3].Value; var projectGuid = projectMatch.Groups[4].Value; var solutionProject = new SolutionProject { Name = projectName, RelativePath = projectPath, FullPath = Path.Combine(Path.GetDirectoryName(structure.SolutionFile)!, projectPath), ProjectTypeGuid = projectTypeGuid, ProjectGuid = projectGuid, ProjectType = DetermineProjectType(projectTypeGuid, projectPath) }; structure.SolutionProjects.Add(solutionProject); } } Console.WriteLine($"[SOLUTION ANALYSIS] Found {structure.SolutionProjects.Count} projects in solution file"); } catch (Exception ex) { Console.WriteLine($"[WARNING] Failed to parse solution file: {ex.Message}"); } } private async Task DiscoverProjects(SolutionStructure structure) { // Find all .csproj files in the directory tree var allProjectFiles = Directory.GetFiles(structure.SolutionPath, "*.csproj", SearchOption.AllDirectories); foreach (var projectFile in allProjectFiles) { // Check if this project is already in the solution file var existingProject = structure.SolutionProjects.FirstOrDefault(p => Path.GetFullPath(p.FullPath).Equals(Path.GetFullPath(projectFile), StringComparison.OrdinalIgnoreCase)); if (existingProject != null) { // Enhance existing project with file system info await AnalyzeProjectFile(existingProject); } else { // This is a project not listed in the solution file (orphaned) var orphanedProject = new SolutionProject { Name = Path.GetFileNameWithoutExtension(projectFile), FullPath = projectFile, RelativePath = Path.GetRelativePath(structure.SolutionPath, projectFile), ProjectType = ProjectType.Other, IsOrphaned = true }; await AnalyzeProjectFile(orphanedProject); structure.OrphanedProjects.Add(orphanedProject); } } Console.WriteLine($"[DISCOVERY] Found {allProjectFiles.Length} total .csproj files"); Console.WriteLine($"[DISCOVERY] {structure.OrphanedProjects.Count} orphaned projects (not in .sln)"); } private async Task AnalyzeProjectFile(SolutionProject project) { try { if (!File.Exists(project.FullPath)) { project.HasIssues = true; project.Issues.Add($"Project file not found: {project.FullPath}"); return; } var projectContent = await File.ReadAllTextAsync(project.FullPath); var projectXml = XDocument.Parse(projectContent); // Analyze project properties var properties = projectXml.Descendants("PropertyGroup").Elements().ToList(); foreach (var prop in properties) { project.Properties[prop.Name.LocalName] = prop.Value; } // Determine if it's a MAUI project await AnalyzeMauiSpecificProperties(project, projectXml); // Find C# files var projectDir = Path.GetDirectoryName(project.FullPath)!; var csFiles = Directory.GetFiles(projectDir, "*.cs", SearchOption.AllDirectories); project.CSharpFiles = csFiles.Where(f => !ShouldExcludeFile(f, project)).ToList(); project.TotalFiles = csFiles.Length; project.ProcessableFiles = project.CSharpFiles.Count; // Check for MAUI platform folders await AnalyzeMauiPlatformStructure(project, projectDir); } catch (Exception ex) { project.HasIssues = true; project.Issues.Add($"Failed to analyze project: {ex.Message}"); } } private async Task AnalyzeMauiSpecificProperties(SolutionProject project, XDocument projectXml) { // Check for MAUI-specific properties var isMaui = project.Properties.ContainsKey("UseMaui") && project.Properties["UseMaui"].Equals("true", StringComparison.OrdinalIgnoreCase); if (isMaui) { project.ProjectType = ProjectType.MobileApp; project.MauiInfo = new MauiProjectInfo(); // Analyze MAUI-specific settings if (project.Properties.ContainsKey("TargetFrameworks")) { project.MauiInfo.TargetFrameworks = project.Properties["TargetFrameworks"] .Split(';', StringSplitOptions.RemoveEmptyEntries) .Select(tf => tf.Trim()) .ToList(); } // Check for platform-specific configurations var itemGroups = projectXml.Descendants("ItemGroup"); foreach (var itemGroup in itemGroups) { var condition = itemGroup.Attribute("Condition")?.Value; if (!string.IsNullOrEmpty(condition)) { project.MauiInfo.PlatformSpecificConfigurations.Add(condition); } } // Check for MAUI dependencies var packageReferences = projectXml.Descendants("PackageReference"); foreach (var package in packageReferences) { var packageName = package.Attribute("Include")?.Value; if (packageName != null && IsMauiRelatedPackage(packageName)) { project.MauiInfo.MauiPackages.Add(packageName); } } Console.WriteLine($"[MAUI] Detected MAUI project: {project.Name}"); Console.WriteLine($" Target Frameworks: {string.Join(", ", project.MauiInfo.TargetFrameworks)}"); } await Task.CompletedTask; } private async Task AnalyzeMauiPlatformStructure(SolutionProject project, string projectDir) { if (project.ProjectType != ProjectType.MobileApp) return; // Check for MAUI platform folders var platformFolders = new[] { "Platforms", "Platform" }; var specificPlatforms = new[] { "Android", "iOS", "MacCatalyst", "Windows", "Tizen" }; foreach (var platformFolder in platformFolders) { var platformPath = Path.Combine(projectDir, platformFolder); if (Directory.Exists(platformPath)) { project.MauiInfo!.HasPlatformsFolder = true; // Check for specific platform folders foreach (var platform in specificPlatforms) { var specificPlatformPath = Path.Combine(platformPath, platform); if (Directory.Exists(specificPlatformPath)) { var platformFiles = Directory.GetFiles(specificPlatformPath, "*.cs", SearchOption.AllDirectories); project.MauiInfo.PlatformSpecificFiles[platform] = platformFiles.ToList(); Console.WriteLine($"[MAUI] Found {platform} platform code: {platformFiles.Length} files"); } } } } await Task.CompletedTask; } private async Task AnalyzeMauiProjects(SolutionStructure structure) { var mauiProjects = structure.SolutionProjects .Where(p => p.ProjectType == ProjectType.MobileApp) .ToList(); if (!mauiProjects.Any()) return; Console.WriteLine($"\n[MAUI ANALYSIS] Found {mauiProjects.Count} MAUI project(s)"); foreach (var mauiProject in mauiProjects) { Console.WriteLine($"\n[MAUI PROJECT] {mauiProject.Name}"); if (mauiProject.MauiInfo != null) { Console.WriteLine($" Target Frameworks: {string.Join(", ", mauiProject.MauiInfo.TargetFrameworks)}"); Console.WriteLine($" MAUI Packages: {mauiProject.MauiInfo.MauiPackages.Count}"); Console.WriteLine($" Platform Folders: {(mauiProject.MauiInfo.HasPlatformsFolder ? "✅ Yes" : "❌ No")}"); if (mauiProject.MauiInfo.PlatformSpecificFiles.Any()) { Console.WriteLine(" Platform-Specific Code:"); foreach (var platform in mauiProject.MauiInfo.PlatformSpecificFiles) { Console.WriteLine($" {platform.Key}: {platform.Value.Count} files"); } } // Analyze refactoring recommendations for MAUI await GenerateMauiRefactoringRecommendations(mauiProject); } } await Task.CompletedTask; } private async Task GenerateMauiRefactoringRecommendations(SolutionProject mauiProject) { mauiProject.MauiInfo!.RefactoringRecommendations = new List(); // Check for common MAUI anti-patterns if (mauiProject.MauiInfo.TargetFrameworks.Count > 3) { mauiProject.MauiInfo.RefactoringRecommendations.Add( "Consider reducing target frameworks - too many platforms can complicate maintenance"); } if (!mauiProject.MauiInfo.HasPlatformsFolder && mauiProject.MauiInfo.TargetFrameworks.Count > 1) { mauiProject.MauiInfo.RefactoringRecommendations.Add( "Consider using Platforms folder structure for platform-specific code organization"); } // Check for large shared code files that might need platform-specific splitting var sharedFiles = mauiProject.CSharpFiles .Where(f => !IsInPlatformFolder(f)) .ToList(); var largeSharedFiles = new List(); foreach (var file in sharedFiles) { try { var content = await File.ReadAllTextAsync(file); var lineCount = content.Split('\n').Length; if (lineCount > 200) { largeSharedFiles.Add(Path.GetFileName(file)); } } catch { // Skip files that can't be read } } if (largeSharedFiles.Any()) { mauiProject.MauiInfo.RefactoringRecommendations.Add( $"Large shared files detected ({string.Join(", ", largeSharedFiles)}) - consider extracting platform-specific logic"); } // Check for dependency injection setup var mainFiles = mauiProject.CSharpFiles .Where(f => Path.GetFileName(f).Contains("Main", StringComparison.OrdinalIgnoreCase) || Path.GetFileName(f).Contains("App", StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (var mainFile in mainFiles) { try { var content = await File.ReadAllTextAsync(mainFile); if (content.Contains("ConfigureServices") && content.Length > 500) { mauiProject.MauiInfo.RefactoringRecommendations.Add( $"Large service configuration in {Path.GetFileName(mainFile)} - consider extracting to separate configuration class"); } } catch { // Skip files that can't be read } } if (mauiProject.MauiInfo.RefactoringRecommendations.Any()) { Console.WriteLine(" 🔧 Refactoring Recommendations:"); foreach (var recommendation in mauiProject.MauiInfo.RefactoringRecommendations) { Console.WriteLine($" • {recommendation}"); } } else { Console.WriteLine(" ✅ No specific MAUI refactoring issues detected"); } } private bool IsInPlatformFolder(string filePath) { var pathParts = filePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); return pathParts.Any(part => part.Equals("Platforms", StringComparison.OrdinalIgnoreCase) || part.Equals("Platform", StringComparison.OrdinalIgnoreCase) || part.Equals("Android", StringComparison.OrdinalIgnoreCase) || part.Equals("iOS", StringComparison.OrdinalIgnoreCase) || part.Equals("MacCatalyst", StringComparison.OrdinalIgnoreCase) || part.Equals("Windows", StringComparison.OrdinalIgnoreCase) || part.Equals("Tizen", StringComparison.OrdinalIgnoreCase)); } private bool IsMauiRelatedPackage(string packageName) { var mauiPackages = new[] { "Microsoft.Maui", "Microsoft.Maui.Controls", "Microsoft.Maui.Graphics", "Microsoft.Maui.Essentials", "Microsoft.Extensions.Logging.Debug", "CommunityToolkit.Maui" }; return mauiPackages.Any(mp => packageName.StartsWith(mp, StringComparison.OrdinalIgnoreCase)); } private ProjectType DetermineProjectType(string projectTypeGuid, string projectPath) { // Standard Visual Studio project type GUIDs return projectTypeGuid.ToUpper() switch { "{9A19103F-16F7-4668-BE54-9A1E7A4F7556}" => ProjectType.Library, // .NET SDK-style project "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}" => ProjectType.Library, // Legacy C# project "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" => ProjectType.Solution, // Solution folder "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" => ProjectType.Other, // C++ project _ => DetermineProjectTypeFromPath(projectPath) }; } private ProjectType DetermineProjectTypeFromPath(string projectPath) { var fileName = Path.GetFileName(projectPath).ToLower(); if (fileName.Contains("test")) return ProjectType.Tests; else if (fileName.Contains("benchmark")) return ProjectType.Tests; // Benchmark projects are a type of test else if (fileName.Contains("tool")) return ProjectType.Tool; else if (fileName.Contains("maui")) return ProjectType.MobileApp; else return ProjectType.Library; } private bool ShouldExcludeFile(string filePath, SolutionProject project) { var fileName = Path.GetFileName(filePath); var excludePatterns = new[] { ".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs", "GlobalAssemblyInfo.cs", "Reference.cs", "TemporaryGeneratedFile", ".AssemblyAttributes.cs" }; if (excludePatterns.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase))) return true; // MAUI-specific exclusions if (project.ProjectType == ProjectType.MobileApp) { var mauiExclusions = new[] { "MauiProgram.cs", // Usually minimal and platform-specific "App.xaml.cs", // Usually minimal "AppShell.xaml.cs", // Usually minimal "MainPage.xaml.cs" // Usually minimal unless it's grown too large }; // Only exclude these if they're small (< 50 lines) if (mauiExclusions.Any(exclusion => fileName.Equals(exclusion, StringComparison.OrdinalIgnoreCase))) { try { var content = File.ReadAllText(filePath); var lineCount = content.Split('\n').Length; if (lineCount < 50) { return true; // Skip small MAUI infrastructure files } } catch { // If we can't read the file, don't exclude it } } } return false; } } // Supporting classes for MAUI-aware solution analysis public class SolutionStructure { public string SolutionPath { get; set; } = string.Empty; public string SolutionName { get; set; } = string.Empty; public string SolutionFile { get; set; } = string.Empty; public List SolutionProjects { get; set; } = new List(); public List OrphanedProjects { get; set; } = new List(); public Dictionary Metadata { get; set; } = new Dictionary(); } public class SolutionProject { public string Name { get; set; } = string.Empty; public string FullPath { get; set; } = string.Empty; public string RelativePath { get; set; } = string.Empty; public string ProjectTypeGuid { get; set; } = string.Empty; public string ProjectGuid { get; set; } = string.Empty; public ProjectType ProjectType { get; set; } public bool IsOrphaned { get; set; } public bool HasIssues { get; set; } public List Issues { get; set; } = new List(); public Dictionary Properties { get; set; } = new Dictionary(); public List CSharpFiles { get; set; } = new List(); public int TotalFiles { get; set; } public int ProcessableFiles { get; set; } public MauiProjectInfo? MauiInfo { get; set; } } public class MauiProjectInfo { public List TargetFrameworks { get; set; } = new List(); public List MauiPackages { get; set; } = new List(); public List PlatformSpecificConfigurations { get; set; } = new List(); public Dictionary> PlatformSpecificFiles { get; set; } = new Dictionary>(); public bool HasPlatformsFolder { get; set; } public List RefactoringRecommendations { get; set; } = new List(); } // Using ProjectType from MarketAlly.ProjectDetector