using MarketAlly.AIPlugin; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using Formatter = Microsoft.CodeAnalysis.Formatting.Formatter; namespace MarketAlly.AIPlugin.Refactoring.Plugins { [AIPlugin("CodeFormatter", "Auto-formats code and enforces coding standards")] public class CodeFormatterPlugin : IAIPlugin { [AIParameter("Full path to the file or directory to format", required: true)] public string Path { get; set; } [AIParameter("Formatting style: microsoft, google, allman, k&r", required: false)] public string FormattingStyle { get; set; } = "microsoft"; [AIParameter("Fix indentation issues", required: false)] public bool FixIndentation { get; set; } = true; [AIParameter("Organize using statements", required: false)] public bool OrganizeUsings { get; set; } = true; [AIParameter("Remove unnecessary code", required: false)] public bool RemoveUnnecessary { get; set; } = true; [AIParameter("Apply changes to files", required: false)] public bool ApplyChanges { get; set; } = false; [AIParameter("Include file backup when applying changes", required: false)] public bool CreateBackup { get; set; } = true; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["path"] = typeof(string), ["formattingStyle"] = typeof(string), ["formattingstyle"] = typeof(string), // Allow lowercase ["fixIndentation"] = typeof(bool), ["fixindentation"] = typeof(bool), // Allow lowercase ["organizeUsings"] = typeof(bool), ["organizeusings"] = typeof(bool), // Allow lowercase ["removeUnnecessary"] = typeof(bool), ["removeunnecessary"] = typeof(bool), // Allow lowercase ["applyChanges"] = typeof(bool), ["applychanges"] = typeof(bool), // Allow lowercase ["createBackup"] = typeof(bool), ["createbackup"] = typeof(bool) // Allow lowercase }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters with case-insensitive handling string path = parameters["path"].ToString(); string formattingStyle = GetParameterValue(parameters, "formattingStyle", "formattingstyle")?.ToString()?.ToLower() ?? "microsoft"; bool fixIndentation = GetBoolParameter(parameters, "fixIndentation", "fixindentation", true); bool organizeUsings = GetBoolParameter(parameters, "organizeUsings", "organizeusings", true); bool removeUnnecessary = GetBoolParameter(parameters, "removeUnnecessary", "removeunnecessary", true); bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false); bool createBackup = GetBoolParameter(parameters, "createBackup", "createbackup", true); // Validate path if (!File.Exists(path) && !Directory.Exists(path)) { return new AIPluginResult( new FileNotFoundException($"Path not found: {path}"), "Invalid path" ); } var formattingResults = new List(); if (File.Exists(path)) { // Format single file var result = await FormatFileAsync(path, formattingStyle, fixIndentation, organizeUsings, removeUnnecessary, applyChanges, createBackup); if (result != null) formattingResults.Add(result); } else { // Format directory var csharpFiles = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories) .Where(f => !ShouldExcludeFile(f)) .ToList(); foreach (var file in csharpFiles) { var result = await FormatFileAsync(file, formattingStyle, fixIndentation, organizeUsings, removeUnnecessary, applyChanges, createBackup); if (result != null) formattingResults.Add(result); } } // Generate summary var summary = GenerateFormattingSummary(formattingResults, applyChanges); return new AIPluginResult(new { Message = $"Code formatting completed for {formattingResults.Count} file(s)", Path = path, FormattingStyle = formattingStyle, ChangesApplied = applyChanges, Summary = summary, DetailedResults = formattingResults, Timestamp = DateTime.UtcNow }); } catch (Exception ex) { return new AIPluginResult(ex, $"Code formatting failed: {ex.Message}"); } } private async Task FormatFileAsync(string filePath, string formattingStyle, bool fixIndentation, bool organizeUsings, bool removeUnnecessary, bool applyChanges, bool createBackup) { try { var originalContent = await File.ReadAllTextAsync(filePath); var syntaxTree = CSharpSyntaxTree.ParseText(originalContent); var root = syntaxTree.GetRoot(); var result = new FormattingResult { FilePath = filePath, FileName = System.IO.Path.GetFileName(filePath), OriginalLineCount = originalContent.Split('\n').Length, Timestamp = DateTime.UtcNow }; // Apply various formatting operations var formattedRoot = root; // 1. Organize using statements if (organizeUsings) { formattedRoot = OrganizeUsingStatements(formattedRoot); result.UsingsOrganized = true; } // 2. Remove unnecessary code if (removeUnnecessary) { formattedRoot = await RemoveUnnecessaryCodeAsync(formattedRoot); result.UnnecessaryCodeRemoved = true; } // 3. Apply formatting style if (fixIndentation) { var workspace = new AdhocWorkspace(); var formattingOptions = GetFormattingOptions(formattingStyle, workspace); formattedRoot = Formatter.Format(formattedRoot, workspace, formattingOptions); result.IndentationFixed = true; } // 4. Apply additional style-specific formatting formattedRoot = await ApplyStyleSpecificFormatting(formattedRoot, formattingStyle); var formattedContent = formattedRoot.ToFullString(); result.FormattedLineCount = formattedContent.Split('\n').Length; result.FormattedContent = formattedContent; // Calculate changes result.Changes = CalculateFormattingChanges(originalContent, formattedContent); // Apply changes if requested if (applyChanges && result.Changes.TotalChanges > 0) { if (createBackup) { var backupPath = $"{filePath}.{DateTime.Now:yyyyMMdd_HHmmss}.bak"; File.Copy(filePath, backupPath); result.BackupPath = backupPath; } await File.WriteAllTextAsync(filePath, formattedContent, Encoding.UTF8); result.ChangesApplied = true; } return result; } catch (Exception ex) { return new FormattingResult { FilePath = filePath, FileName = System.IO.Path.GetFileName(filePath), Error = ex.Message, Timestamp = DateTime.UtcNow }; } } private SyntaxNode OrganizeUsingStatements(SyntaxNode root) { var compilationUnit = root as CompilationUnitSyntax; if (compilationUnit == null) return root; var usings = compilationUnit.Usings; if (!usings.Any()) return root; // Group and sort using statements var systemUsings = new List(); var thirdPartyUsings = new List(); var projectUsings = new List(); foreach (var usingDirective in usings) { var namespaceName = usingDirective.Name.ToString(); if (namespaceName.StartsWith("System")) { systemUsings.Add(usingDirective); } else if (IsThirdPartyNamespace(namespaceName)) { thirdPartyUsings.Add(usingDirective); } else { projectUsings.Add(usingDirective); } } // Sort each group alphabetically systemUsings = systemUsings.OrderBy(u => u.Name.ToString()).ToList(); thirdPartyUsings = thirdPartyUsings.OrderBy(u => u.Name.ToString()).ToList(); projectUsings = projectUsings.OrderBy(u => u.Name.ToString()).ToList(); // Combine groups with blank lines between them var organizedUsings = new List(); organizedUsings.AddRange(systemUsings); if (systemUsings.Any() && (thirdPartyUsings.Any() || projectUsings.Any())) { // Add blank line after system usings var lastSystemUsing = organizedUsings.Last(); organizedUsings[organizedUsings.Count - 1] = lastSystemUsing.WithTrailingTrivia( lastSystemUsing.GetTrailingTrivia().Add(SyntaxFactory.CarriageReturnLineFeed)); } organizedUsings.AddRange(thirdPartyUsings); if (thirdPartyUsings.Any() && projectUsings.Any()) { // Add blank line after third-party usings var lastThirdPartyUsing = organizedUsings.Last(); organizedUsings[organizedUsings.Count - 1] = lastThirdPartyUsing.WithTrailingTrivia( lastThirdPartyUsing.GetTrailingTrivia().Add(SyntaxFactory.CarriageReturnLineFeed)); } organizedUsings.AddRange(projectUsings); // Replace the using statements var newCompilationUnit = compilationUnit.WithUsings(SyntaxFactory.List(organizedUsings)); return newCompilationUnit; } private async Task RemoveUnnecessaryCodeAsync(SyntaxNode root) { var newRoot = root; // Remove empty statements var emptyStatements = root.DescendantNodes().OfType().ToList(); newRoot = newRoot.RemoveNodes(emptyStatements, SyntaxRemoveOptions.KeepNoTrivia); // Remove redundant else statements newRoot = await RemoveRedundantElseStatements(newRoot); // Remove unnecessary parentheses newRoot = await RemoveUnnecessaryParentheses(newRoot); // Remove unused using statements (simplified version) newRoot = await RemoveUnusedUsings(newRoot); return newRoot; } private async Task RemoveRedundantElseStatements(SyntaxNode root) { var ifStatements = root.DescendantNodes().OfType().ToList(); var newRoot = root; foreach (var ifStatement in ifStatements) { if (ifStatement.Else != null && IsRedundantElse(ifStatement)) { var newIfStatement = ifStatement.WithElse(null); newRoot = newRoot.ReplaceNode(ifStatement, newIfStatement); } } return await Task.FromResult(newRoot); } private bool IsRedundantElse(IfStatementSyntax ifStatement) { // Check if the if statement ends with a return, throw, or break var statement = ifStatement.Statement; if (statement is BlockSyntax block) { var lastStatement = block.Statements.LastOrDefault(); return lastStatement is ReturnStatementSyntax || lastStatement is ThrowStatementSyntax || lastStatement is BreakStatementSyntax || lastStatement is ContinueStatementSyntax; } return statement is ReturnStatementSyntax || statement is ThrowStatementSyntax || statement is BreakStatementSyntax || statement is ContinueStatementSyntax; } private async Task RemoveUnnecessaryParentheses(SyntaxNode root) { var parenthesizedExpressions = root.DescendantNodes().OfType().ToList(); var newRoot = root; foreach (var parenthesized in parenthesizedExpressions) { if (IsUnnecessaryParentheses(parenthesized)) { newRoot = newRoot.ReplaceNode(parenthesized, parenthesized.Expression); } } return await Task.FromResult(newRoot); } private bool IsUnnecessaryParentheses(ParenthesizedExpressionSyntax parenthesized) { // Simplified check - remove parentheses around single identifiers or literals return parenthesized.Expression is IdentifierNameSyntax || parenthesized.Expression is LiteralExpressionSyntax || parenthesized.Expression is ThisExpressionSyntax; } private async Task RemoveUnusedUsings(SyntaxNode root) { var compilationUnit = root as CompilationUnitSyntax; if (compilationUnit == null) return root; var usedNamespaces = new HashSet(); // Collect all type references in the code var typeReferences = root.DescendantNodes() .Where(n => n is IdentifierNameSyntax || n is QualifiedNameSyntax) .Select(n => n.ToString()) .ToHashSet(); // Check which using statements are actually used (simplified approach) var usingsToKeep = new List(); foreach (var usingDirective in compilationUnit.Usings) { var namespaceName = usingDirective.Name.ToString(); var namespaceParts = namespaceName.Split('.'); // Keep using if any type reference could come from this namespace bool isUsed = typeReferences.Any(typeRef => namespaceParts.Any(part => typeRef.Contains(part))) || IsEssentialNamespace(namespaceName); if (isUsed) { usingsToKeep.Add(usingDirective); } } return await Task.FromResult(compilationUnit.WithUsings(SyntaxFactory.List(usingsToKeep))); } private bool IsEssentialNamespace(string namespaceName) { // Keep essential namespaces that are commonly used but might not be detected var essentialNamespaces = new[] { "System", "System.Collections.Generic", "System.Linq", "System.Threading.Tasks" }; return essentialNamespaces.Contains(namespaceName); } private async Task ApplyStyleSpecificFormatting(SyntaxNode root, string formattingStyle) { var newRoot = root; switch (formattingStyle.ToLower()) { case "allman": newRoot = await ApplyAllmanStyle(newRoot); break; case "kr": case "k&r": newRoot = await ApplyKRStyle(newRoot); break; case "google": newRoot = await ApplyGoogleStyle(newRoot); break; case "microsoft": default: // Microsoft style is the default for Roslyn formatter break; } return newRoot; } private async Task ApplyAllmanStyle(SyntaxNode root) { // Allman style: opening braces on new lines var newRoot = root; // This is a simplified implementation // In practice, you'd need more sophisticated brace positioning var blocks = root.DescendantNodes().OfType().ToList(); foreach (var block in blocks) { var newBlock = block.WithOpenBraceToken( block.OpenBraceToken.WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed) ); newRoot = newRoot.ReplaceNode(block, newBlock); } return await Task.FromResult(newRoot); } private async Task ApplyKRStyle(SyntaxNode root) { // K&R style: opening braces on same line var newRoot = root; var blocks = root.DescendantNodes().OfType().ToList(); foreach (var block in blocks) { var newBlock = block.WithOpenBraceToken( block.OpenBraceToken.WithLeadingTrivia(SyntaxFactory.Space) ); newRoot = newRoot.ReplaceNode(block, newBlock); } return await Task.FromResult(newRoot); } private async Task ApplyGoogleStyle(SyntaxNode root) { // Google C# style guide formatting var newRoot = root; // Apply specific Google style rules // This is a simplified implementation return await Task.FromResult(newRoot); } private OptionSet GetFormattingOptions(string formattingStyle, Workspace workspace) { var options = workspace.Options; // Configure indentation options = options.WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, 4); options = options.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, 4); options = options.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, false); // Configure spacing options = options.WithChangedOption(FormattingOptions.SmartIndent, LanguageNames.CSharp, FormattingOptions.IndentStyle.Smart); // Style-specific options switch (formattingStyle.ToLower()) { case "allman": // Allman style preferences break; case "kr": case "k&r": // K&R style preferences break; case "google": // Google style preferences options = options.WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, 2); options = options.WithChangedOption(FormattingOptions.TabSize, LanguageNames.CSharp, 2); break; case "microsoft": default: // Microsoft style (default) break; } return options; } private FormattingChanges CalculateFormattingChanges(string originalContent, string formattedContent) { var originalLines = originalContent.Split('\n'); var formattedLines = formattedContent.Split('\n'); var changes = new FormattingChanges(); // Simple diff calculation changes.LinesChanged = 0; changes.WhitespaceChanges = 0; changes.StructuralChanges = 0; int maxLines = Math.Max(originalLines.Length, formattedLines.Length); for (int i = 0; i < maxLines; i++) { var originalLine = i < originalLines.Length ? originalLines[i] : ""; var formattedLine = i < formattedLines.Length ? formattedLines[i] : ""; if (originalLine != formattedLine) { changes.LinesChanged++; // Check if it's just whitespace changes if (originalLine.Trim() == formattedLine.Trim()) { changes.WhitespaceChanges++; } else { changes.StructuralChanges++; } } } changes.TotalChanges = changes.LinesChanged; changes.ChangePercentage = originalLines.Length > 0 ? Math.Round((double)changes.LinesChanged / originalLines.Length * 100, 1) : 0; return changes; } private bool IsThirdPartyNamespace(string namespaceName) { // Common third-party namespace patterns var thirdPartyPrefixes = new[] { "Microsoft.Extensions", "Microsoft.AspNetCore", "Microsoft.EntityFrameworkCore", "Newtonsoft", "AutoMapper", "Serilog", "NLog", "FluentValidation", "MediatR", "Moq", "NUnit", "Xunit" }; return thirdPartyPrefixes.Any(prefix => namespaceName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); } private bool ShouldExcludeFile(string filePath) { var fileName = System.IO.Path.GetFileName(filePath); var excludePatterns = new[] { ".Designer.cs", ".generated.cs", ".g.cs", "AssemblyInfo.cs", "GlobalAssemblyInfo.cs", "TemporaryGeneratedFile_", ".AssemblyAttributes.cs" }; return excludePatterns.Any(pattern => fileName.Contains(pattern, StringComparison.OrdinalIgnoreCase)); } private object GenerateFormattingSummary(List results, bool changesApplied) { if (!results.Any()) { return new { Message = "No files processed" }; } var successfulResults = results.Where(r => string.IsNullOrEmpty(r.Error)).ToList(); var failedResults = results.Where(r => !string.IsNullOrEmpty(r.Error)).ToList(); var totalFilesProcessed = results.Count; var totalLinesProcessed = successfulResults.Sum(r => r.OriginalLineCount); var totalChanges = successfulResults.Sum(r => r.Changes?.TotalChanges ?? 0); var averageChangePercentage = successfulResults.Any() ? successfulResults.Average(r => r.Changes?.ChangePercentage ?? 0) : 0; var formattingActions = new { UsingsOrganized = successfulResults.Count(r => r.UsingsOrganized), IndentationFixed = successfulResults.Count(r => r.IndentationFixed), UnnecessaryCodeRemoved = successfulResults.Count(r => r.UnnecessaryCodeRemoved) }; return new { TotalFilesProcessed = totalFilesProcessed, SuccessfulFiles = successfulResults.Count, FailedFiles = failedResults.Count, TotalLinesProcessed = totalLinesProcessed, TotalChanges = totalChanges, AverageChangePercentage = Math.Round(averageChangePercentage, 1), ChangesApplied = changesApplied, FormattingActions = formattingActions, FailedFileDetails = failedResults.Select(r => new { r.FilePath, r.Error }).ToList() }; } // Helper methods for parameter extraction private object GetParameterValue(IReadOnlyDictionary parameters, params string[] keys) { foreach (var key in keys) { if (parameters.TryGetValue(key, out var value)) return value; } return null; } private bool GetBoolParameter(IReadOnlyDictionary parameters, string key1, string key2, bool defaultValue = false) { var value = GetParameterValue(parameters, key1, key2); return value != null ? Convert.ToBoolean(value) : defaultValue; } } // Supporting classes for code formatting public class FormattingResult { public string FilePath { get; set; } public string FileName { get; set; } public int OriginalLineCount { get; set; } public int FormattedLineCount { get; set; } public string FormattedContent { get; set; } public FormattingChanges Changes { get; set; } public bool UsingsOrganized { get; set; } public bool IndentationFixed { get; set; } public bool UnnecessaryCodeRemoved { get; set; } public bool ChangesApplied { get; set; } public string BackupPath { get; set; } public string Error { get; set; } public DateTime Timestamp { get; set; } } public class FormattingChanges { public int LinesChanged { get; set; } public int WhitespaceChanges { get; set; } public int StructuralChanges { get; set; } public int TotalChanges { get; set; } public double ChangePercentage { get; set; } } }