678 lines
21 KiB
C#
Executable File
678 lines
21 KiB
C#
Executable File
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<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["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<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<FormattingResult>();
|
|
|
|
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<FormattingResult> 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<UsingDirectiveSyntax>();
|
|
var thirdPartyUsings = new List<UsingDirectiveSyntax>();
|
|
var projectUsings = new List<UsingDirectiveSyntax>();
|
|
|
|
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<UsingDirectiveSyntax>();
|
|
|
|
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<SyntaxNode> RemoveUnnecessaryCodeAsync(SyntaxNode root)
|
|
{
|
|
var newRoot = root;
|
|
|
|
// Remove empty statements
|
|
var emptyStatements = root.DescendantNodes().OfType<EmptyStatementSyntax>().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<SyntaxNode> RemoveRedundantElseStatements(SyntaxNode root)
|
|
{
|
|
var ifStatements = root.DescendantNodes().OfType<IfStatementSyntax>().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<SyntaxNode> RemoveUnnecessaryParentheses(SyntaxNode root)
|
|
{
|
|
var parenthesizedExpressions = root.DescendantNodes().OfType<ParenthesizedExpressionSyntax>().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<SyntaxNode> RemoveUnusedUsings(SyntaxNode root)
|
|
{
|
|
var compilationUnit = root as CompilationUnitSyntax;
|
|
if (compilationUnit == null)
|
|
return root;
|
|
|
|
var usedNamespaces = new HashSet<string>();
|
|
|
|
// 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<UsingDirectiveSyntax>();
|
|
|
|
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<SyntaxNode> 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<SyntaxNode> 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<BlockSyntax>().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<SyntaxNode> ApplyKRStyle(SyntaxNode root)
|
|
{
|
|
// K&R style: opening braces on same line
|
|
var newRoot = root;
|
|
|
|
var blocks = root.DescendantNodes().OfType<BlockSyntax>().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<SyntaxNode> 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<FormattingResult> 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<string, object> parameters, params string[] keys)
|
|
{
|
|
foreach (var key in keys)
|
|
{
|
|
if (parameters.TryGetValue(key, out var value))
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private bool GetBoolParameter(IReadOnlyDictionary<string, object> 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; }
|
|
}
|
|
} |