MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../NamingConventionPlugin.cs

931 lines
32 KiB
C#
Executable File

using MarketAlly.AIPlugin;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Refactoring.Plugins
{
[AIPlugin("NamingConvention", "Analyzes and suggests improvements for variable, method, and class naming")]
public class NamingConventionPlugin : IAIPlugin
{
// Compiled regex patterns for performance
private static readonly Regex SingleLetterPattern = new Regex(@"^[a-zA-Z]\d*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex GenericNamesPattern = new Regex(@"^(temp|tmp|val|var|obj|item|data|info|str|num|cnt|idx|len)\d*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ShortAbbrevPattern = new Regex(@"^[a-zA-Z]{1,2}$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex HungarianPattern = new Regex(@"^(str|int|bool|obj|lst|arr|dict)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ConsecutiveUnderscoresPattern = new Regex(@"_{2,}", RegexOptions.Compiled);
private static readonly Regex ConsecutiveCapitalsPattern = new Regex(@"[A-Z]{3,}", RegexOptions.Compiled);
private static readonly Regex NonAlphaNumericPattern = new Regex(@"[^a-zA-Z0-9_]", RegexOptions.Compiled);
private static readonly Regex DigitStartPattern = new Regex(@"^\d", RegexOptions.Compiled);
private static readonly Regex ExcessiveWordsPattern = new Regex(@"(\b\w+\b.*){8,}", RegexOptions.Compiled);
private static readonly Regex ReservedKeywordsPattern = new Regex(@"^(abstract|as|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
[AIParameter("Full path to the file to analyze", required: true)]
public string FilePath { get; set; }
[AIParameter("Naming convention: pascal, camel, snake, kebab", required: false)]
public string Convention { get; set; } = "pascal";
[AIParameter("Check for meaningful names", required: false)]
public bool CheckMeaningfulness { get; set; } = true;
[AIParameter("Suggest better names using AI", required: false)]
public bool AISuggestions { get; set; } = true;
[AIParameter("Apply naming changes to file", required: false)]
public bool ApplyChanges { get; set; } = false;
[AIParameter("Minimum length for meaningful names", required: false)]
public int MinimumNameLength { get; set; } = 3;
[AIParameter("Check abbreviations and acronyms", required: false)]
public bool CheckAbbreviations { get; set; } = true;
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["filePath"] = typeof(string),
["filepath"] = typeof(string), // Allow lowercase
["convention"] = typeof(string),
["checkMeaningfulness"] = typeof(bool),
["checkmeaningfulness"] = typeof(bool), // Allow lowercase
["aiSuggestions"] = typeof(bool),
["aisuggestions"] = typeof(bool), // Allow lowercase
["applyChanges"] = typeof(bool),
["applychanges"] = typeof(bool), // Allow lowercase
["minimumNameLength"] = typeof(int),
["minimumnamelength"] = typeof(int), // Allow lowercase
["checkAbbreviations"] = typeof(bool),
["checkabbreviations"] = typeof(bool) // Allow lowercase
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
{
try
{
// Extract parameters with case-insensitive handling
string filePath = GetParameterValue(parameters, "filePath", "filepath")?.ToString();
string convention = GetParameterValue(parameters, "convention")?.ToString()?.ToLower() ?? "pascal";
bool checkMeaningfulness = GetBoolParameter(parameters, "checkMeaningfulness", "checkmeaningfulness", true);
bool aiSuggestions = GetBoolParameter(parameters, "aiSuggestions", "aisuggestions", true);
bool applyChanges = GetBoolParameter(parameters, "applyChanges", "applychanges", false);
int minimumNameLength = GetIntParameter(parameters, "minimumNameLength", "minimumnamelength", 3);
bool checkAbbreviations = GetBoolParameter(parameters, "checkAbbreviations", "checkabbreviations", true);
// Validate file exists
if (!File.Exists(filePath))
{
return new AIPluginResult(
new FileNotFoundException($"File not found: {filePath}"),
"File not found"
);
}
// Read and parse the file
var sourceCode = await File.ReadAllTextAsync(filePath);
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var root = syntaxTree.GetRoot();
// Analyze naming conventions
var namingAnalysis = await AnalyzeNamingConventions(root, filePath, convention,
checkMeaningfulness, checkAbbreviations, minimumNameLength);
// Generate AI-powered suggestions if requested
if (aiSuggestions && namingAnalysis.Issues.Any())
{
await GenerateAISuggestions(namingAnalysis);
}
// Apply changes if requested
if (applyChanges && namingAnalysis.Suggestions.Any(s => s.ShouldApply))
{
var modifiedContent = await ApplyNamingChanges(sourceCode, namingAnalysis);
// Create backup
var backupPath = $"{filePath}.{DateTime.Now:yyyyMMdd_HHmmss}.bak";
File.Copy(filePath, backupPath);
// Write modified content
await File.WriteAllTextAsync(filePath, modifiedContent);
return new AIPluginResult(new
{
Message = $"Applied {namingAnalysis.Suggestions.Count(s => s.ShouldApply)} naming improvements",
FilePath = filePath,
BackupPath = backupPath,
Convention = convention,
ChangesApplied = true,
Analysis = namingAnalysis,
ModifiedContent = modifiedContent,
Timestamp = DateTime.UtcNow
});
}
else
{
return new AIPluginResult(new
{
Message = $"Found {namingAnalysis.Issues.Count} naming issues with {namingAnalysis.Suggestions.Count} suggestions",
FilePath = filePath,
Convention = convention,
ChangesApplied = false,
Analysis = namingAnalysis,
Timestamp = DateTime.UtcNow
});
}
}
catch (Exception ex)
{
return new AIPluginResult(ex, $"Naming convention analysis failed: {ex.Message}");
}
}
private async Task<NamingAnalysis> AnalyzeNamingConventions(SyntaxNode root, string filePath,
string convention, bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var analysis = new NamingAnalysis
{
FilePath = filePath,
Convention = convention,
Issues = new List<NamingIssue>(),
Suggestions = new List<NamingSuggestion>()
};
// Analyze different types of identifiers
await AnalyzeClasses(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeInterfaces(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeMethods(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeProperties(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeFields(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeParameters(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
await AnalyzeLocalVariables(root, analysis, convention, checkMeaningfulness, checkAbbreviations, minimumNameLength);
// Calculate statistics
analysis.Statistics = CalculateNamingStatistics(analysis);
return analysis;
}
private async Task AnalyzeClasses(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var cls in classes)
{
var name = cls.Identifier.ValueText;
var lineNumber = cls.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
await AnalyzeIdentifier(analysis, "Class", name, lineNumber, "PascalCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
private async Task AnalyzeInterfaces(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var interfaces = root.DescendantNodes().OfType<InterfaceDeclarationSyntax>();
foreach (var iface in interfaces)
{
var name = iface.Identifier.ValueText;
var lineNumber = iface.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Interfaces should start with 'I' and use PascalCase
var issues = new List<string>();
if (!name.StartsWith("I") || !char.IsUpper(name, 1))
{
issues.Add("Interface names should start with 'I' followed by PascalCase");
}
await AnalyzeIdentifier(analysis, "Interface", name, lineNumber, "IPascalCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength, issues);
}
}
private async Task AnalyzeMethods(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var name = method.Identifier.ValueText;
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
await AnalyzeIdentifier(analysis, "Method", name, lineNumber, "PascalCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
private async Task AnalyzeProperties(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>();
foreach (var property in properties)
{
var name = property.Identifier.ValueText;
var lineNumber = property.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
await AnalyzeIdentifier(analysis, "Property", name, lineNumber, "PascalCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
private async Task AnalyzeFields(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var fields = root.DescendantNodes().OfType<FieldDeclarationSyntax>();
foreach (var field in fields)
{
foreach (var variable in field.Declaration.Variables)
{
var name = variable.Identifier.ValueText;
var lineNumber = field.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Check if it's a private field (should use camelCase or _camelCase)
var isPrivate = field.Modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword)) ||
!field.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword) ||
m.IsKind(SyntaxKind.ProtectedKeyword) ||
m.IsKind(SyntaxKind.InternalKeyword));
var expectedConvention = isPrivate ? "camelCase" : "PascalCase";
await AnalyzeIdentifier(analysis, "Field", name, lineNumber, expectedConvention,
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
}
private async Task AnalyzeParameters(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
foreach (var parameter in method.ParameterList.Parameters)
{
var name = parameter.Identifier.ValueText;
var lineNumber = parameter.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
await AnalyzeIdentifier(analysis, "Parameter", name, lineNumber, "camelCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
}
private async Task AnalyzeLocalVariables(SyntaxNode root, NamingAnalysis analysis, string convention,
bool checkMeaningfulness, bool checkAbbreviations, int minimumNameLength)
{
var variableDeclarations = root.DescendantNodes().OfType<VariableDeclarationSyntax>();
foreach (var declaration in variableDeclarations)
{
// Skip field declarations (already handled)
if (declaration.Parent is FieldDeclarationSyntax)
continue;
foreach (var variable in declaration.Variables)
{
var name = variable.Identifier.ValueText;
var lineNumber = declaration.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
await AnalyzeIdentifier(analysis, "Variable", name, lineNumber, "camelCase",
checkMeaningfulness, checkAbbreviations, minimumNameLength);
}
}
}
private async Task AnalyzeIdentifier(NamingAnalysis analysis, string identifierType, string name,
int lineNumber, string expectedConvention, bool checkMeaningfulness, bool checkAbbreviations,
int minimumNameLength, List<string> additionalIssues = null)
{
var issues = new List<string>();
if (additionalIssues != null)
issues.AddRange(additionalIssues);
// Check naming convention
if (!IsValidConvention(name, expectedConvention))
{
issues.Add($"Should follow {expectedConvention} convention");
}
// Check meaningful names
if (checkMeaningfulness)
{
var meaningfulnessIssues = CheckMeaningfulnessMethod(name, identifierType, minimumNameLength);
issues.AddRange(meaningfulnessIssues);
}
// Check abbreviations
if (checkAbbreviations)
{
var abbreviationIssues = CheckAbbreviationsMethod(name, identifierType);
issues.AddRange(abbreviationIssues);
}
// Create issue if any problems found
if (issues.Any())
{
var issue = new NamingIssue
{
IdentifierType = identifierType,
Name = name,
LineNumber = lineNumber,
Issues = issues,
Severity = CalculateSeverity(issues)
};
analysis.Issues.Add(issue);
// Generate suggestion
var suggestion = await GenerateNamingSuggestion(issue, expectedConvention);
if (suggestion != null)
{
analysis.Suggestions.Add(suggestion);
}
}
}
private bool IsValidConvention(string name, string convention)
{
switch (convention.ToLower())
{
case "pascalcase":
return IsPascalCase(name);
case "camelcase":
return IsCamelCase(name);
case "ipascalcase": // Interface naming
return name.StartsWith("I") && name.Length > 1 && IsPascalCase(name.Substring(1));
case "snake":
case "snake_case":
return IsSnakeCase(name);
case "kebab":
case "kebab-case":
return IsKebabCase(name);
default:
return true; // Unknown convention, assume valid
}
}
private bool IsPascalCase(string name)
{
if (string.IsNullOrEmpty(name))
return false;
return char.IsUpper(name[0]) && !name.Contains('_') && !name.Contains('-');
}
private bool IsCamelCase(string name)
{
if (string.IsNullOrEmpty(name))
return false;
// Allow underscore prefix for private fields
if (name.StartsWith("_"))
name = name.Substring(1);
return char.IsLower(name[0]) && !name.Contains('_') && !name.Contains('-');
}
private bool IsSnakeCase(string name)
{
return !string.IsNullOrEmpty(name) &&
name.All(c => char.IsLower(c) || char.IsDigit(c) || c == '_') &&
!name.StartsWith("_") && !name.EndsWith("_");
}
private bool IsKebabCase(string name)
{
return !string.IsNullOrEmpty(name) &&
name.All(c => char.IsLower(c) || char.IsDigit(c) || c == '-') &&
!name.StartsWith("-") && !name.EndsWith("-");
}
private List<string> CheckMeaningfulnessMethod(string name, string identifierType, int minimumLength)
{
var issues = new List<string>();
// Check length
if (name.Length < minimumLength)
{
issues.Add($"Name is too short (minimum {minimumLength} characters)");
}
// Check for non-meaningful names using compiled patterns
if (SingleLetterPattern.IsMatch(name))
{
issues.Add("Name is not descriptive enough");
}
else if (GenericNamesPattern.IsMatch(name))
{
issues.Add("Name is not descriptive enough");
}
else if (ShortAbbrevPattern.IsMatch(name))
{
issues.Add("Name is not descriptive enough");
}
// Check for Hungarian notation (discouraged in modern C#) using compiled pattern
if (HungarianPattern.IsMatch(name))
{
issues.Add("Avoid Hungarian notation in modern C#");
}
return issues;
}
private List<string> CheckAbbreviationsMethod(string name, string identifierType)
{
var issues = new List<string>();
// Common abbreviations that should be spelled out
var discouragedAbbreviations = new Dictionary<string, string>
{
["btn"] = "button",
["lbl"] = "label",
["txt"] = "text",
["img"] = "image",
["pic"] = "picture",
["doc"] = "document",
["docs"] = "documents",
["config"] = "configuration",
["info"] = "information",
["admin"] = "administrator",
["auth"] = "authentication",
["repo"] = "repository",
["util"] = "utility",
["mgr"] = "manager",
["svc"] = "service",
["ctx"] = "context",
["args"] = "arguments",
["params"] = "parameters",
["req"] = "request",
["res"] = "response",
["resp"] = "response"
};
var lowerName = name.ToLower();
foreach (var abbrev in discouragedAbbreviations)
{
if (lowerName.Contains(abbrev.Key))
{
issues.Add($"Consider spelling out abbreviation '{abbrev.Key}' as '{abbrev.Value}'");
break;
}
}
// Check for excessive acronyms
var acronymCount = Regex.Matches(name, @"[A-Z]{2,}").Count;
if (acronymCount > 1)
{
issues.Add("Too many acronyms, consider more descriptive naming");
}
return issues;
}
private string CalculateSeverity(List<string> issues)
{
if (issues.Any(i => i.Contains("not descriptive") || i.Contains("too short")))
return "High";
else if (issues.Any(i => i.Contains("convention") || i.Contains("Hungarian")))
return "Medium";
else
return "Low";
}
private async Task<NamingSuggestion> GenerateNamingSuggestion(NamingIssue issue, string expectedConvention)
{
var suggestion = new NamingSuggestion
{
OriginalName = issue.Name,
IdentifierType = issue.IdentifierType,
LineNumber = issue.LineNumber,
Issues = issue.Issues,
SuggestedNames = new List<string>(),
Confidence = 0.0,
Reasoning = new List<string>()
};
// Generate suggestions based on the issues
await GenerateConventionSuggestions(suggestion, expectedConvention);
await GenerateMeaningfulnessSuggestions(suggestion);
await GenerateAbbreviationSuggestions(suggestion);
// Calculate overall confidence
suggestion.Confidence = CalculateSuggestionConfidence(suggestion);
suggestion.ShouldApply = suggestion.Confidence > 0.8 && suggestion.SuggestedNames.Any();
return suggestion.SuggestedNames.Any() ? suggestion : null;
}
private async Task GenerateConventionSuggestions(NamingSuggestion suggestion, string expectedConvention)
{
var name = suggestion.OriginalName;
if (suggestion.Issues.Any(i => i.Contains("convention")))
{
var convertedName = ConvertToConvention(name, expectedConvention);
if (convertedName != name)
{
suggestion.SuggestedNames.Add(convertedName);
suggestion.Reasoning.Add($"Converted to {expectedConvention} convention");
}
}
await Task.CompletedTask;
}
private async Task GenerateMeaningfulnessSuggestions(NamingSuggestion suggestion)
{
var name = suggestion.OriginalName;
if (suggestion.Issues.Any(i => i.Contains("not descriptive") || i.Contains("too short")))
{
var meaningfulSuggestions = GenerateMeaningfulAlternatives(name, suggestion.IdentifierType);
suggestion.SuggestedNames.AddRange(meaningfulSuggestions);
if (meaningfulSuggestions.Any())
{
suggestion.Reasoning.Add("Generated more descriptive alternatives");
}
}
await Task.CompletedTask;
}
private async Task GenerateAbbreviationSuggestions(NamingSuggestion suggestion)
{
var name = suggestion.OriginalName;
if (suggestion.Issues.Any(i => i.Contains("abbreviation")))
{
var expandedName = ExpandAbbreviations(name);
if (expandedName != name)
{
suggestion.SuggestedNames.Add(expandedName);
suggestion.Reasoning.Add("Expanded abbreviations for clarity");
}
}
await Task.CompletedTask;
}
private string ConvertToConvention(string name, string convention)
{
switch (convention.ToLower())
{
case "pascalcase":
return ToPascalCase(name);
case "camelcase":
return ToCamelCase(name);
case "ipascalcase":
return name.StartsWith("I") ? name : "I" + ToPascalCase(name);
case "snake_case":
return ToSnakeCase(name);
case "kebab-case":
return ToKebabCase(name);
default:
return name;
}
}
private string ToPascalCase(string name)
{
if (string.IsNullOrEmpty(name)) return name;
// Handle underscore-separated names
if (name.Contains('_'))
{
var parts = name.Split('_', StringSplitOptions.RemoveEmptyEntries);
return string.Join("", parts.Select(p => char.ToUpper(p[0]) + p.Substring(1).ToLower()));
}
// Handle kebab-case
if (name.Contains('-'))
{
var parts = name.Split('-', StringSplitOptions.RemoveEmptyEntries);
return string.Join("", parts.Select(p => char.ToUpper(p[0]) + p.Substring(1).ToLower()));
}
// Convert first character to uppercase
return char.ToUpper(name[0]) + name.Substring(1);
}
private string ToCamelCase(string name)
{
// Remove leading underscore if present
if (name.StartsWith("_"))
name = name.Substring(1);
var pascalCase = ToPascalCase(name);
return char.ToLower(pascalCase[0]) + pascalCase.Substring(1);
}
private string ToSnakeCase(string name)
{
// Convert PascalCase/camelCase to snake_case
var result = Regex.Replace(name, @"([a-z])([A-Z])", "$1_$2").ToLower();
return result.Replace("-", "_");
}
private string ToKebabCase(string name)
{
// Convert PascalCase/camelCase to kebab-case
var result = Regex.Replace(name, @"([a-z])([A-Z])", "$1-$2").ToLower();
return result.Replace("_", "-");
}
private List<string> GenerateMeaningfulAlternatives(string name, string identifierType)
{
var alternatives = new List<string>();
// Context-based suggestions
var contextSuggestions = GetContextualSuggestions(name, identifierType);
alternatives.AddRange(contextSuggestions);
// Pattern-based expansions
var patternSuggestions = GetPatternBasedSuggestions(name, identifierType);
alternatives.AddRange(patternSuggestions);
return alternatives.Distinct().ToList();
}
private List<string> GetContextualSuggestions(string name, string identifierType)
{
var suggestions = new List<string>();
// Common replacements for generic names
var replacements = new Dictionary<string, string[]>
{
["i"] = new[] { "index", "iterator", "itemCount" },
["j"] = new[] { "innerIndex", "columnIndex", "secondIndex" },
["k"] = new[] { "keyIndex", "thirdIndex" },
["x"] = new[] { "xCoordinate", "horizontalPosition", "width" },
["y"] = new[] { "yCoordinate", "verticalPosition", "height" },
["temp"] = new[] { "temporaryValue", "intermediateResult", "buffer" },
["tmp"] = new[] { "temporary", "temporaryData", "tempResult" },
["val"] = new[] { "value", "currentValue", "inputValue" },
["var"] = new[] { "variable", "currentVariable", "localVariable" },
["obj"] = new[] { "object", "instance", "entity" },
["item"] = new[] { "currentItem", "selectedItem", "dataItem" },
["data"] = new[] { "inputData", "userData", "responseData" },
["info"] = new[] { "information", "details", "metadata" },
["str"] = new[] { "text", "message", "content" },
["num"] = new[] { "number", "count", "quantity" },
["cnt"] = new[] { "count", "counter", "total" },
["idx"] = new[] { "index", "position", "location" },
["len"] = new[] { "length", "size", "count" }
};
var lowerName = name.ToLower();
if (replacements.ContainsKey(lowerName))
{
suggestions.AddRange(replacements[lowerName]);
}
return suggestions;
}
private List<string> GetPatternBasedSuggestions(string name, string identifierType)
{
var suggestions = new List<string>();
switch (identifierType.ToLower())
{
case "method":
if (name.Length <= 3)
{
suggestions.AddRange(new[] { "Execute", "Process", "Handle", "Perform", "Calculate" });
}
break;
case "property":
if (name.Length <= 3)
{
suggestions.AddRange(new[] { "Value", "Name", "Title", "Description", "Status" });
}
break;
case "variable":
case "parameter":
if (name.Length <= 3)
{
suggestions.AddRange(new[] { "input", "output", "result", "value", "data" });
}
break;
}
return suggestions;
}
private string ExpandAbbreviations(string name)
{
var expansions = new Dictionary<string, string>
{
["btn"] = "Button",
["lbl"] = "Label",
["txt"] = "Text",
["img"] = "Image",
["pic"] = "Picture",
["doc"] = "Document",
["config"] = "Configuration",
["info"] = "Information",
["admin"] = "Administrator",
["auth"] = "Authentication",
["repo"] = "Repository",
["util"] = "Utility",
["mgr"] = "Manager",
["svc"] = "Service",
["ctx"] = "Context",
["req"] = "Request",
["res"] = "Response",
["resp"] = "Response"
};
var result = name;
foreach (var expansion in expansions)
{
// Case-insensitive replacement while preserving original casing pattern
var pattern = $@"\b{Regex.Escape(expansion.Key)}\b";
result = Regex.Replace(result, pattern, expansion.Value, RegexOptions.IgnoreCase);
}
return result;
}
private double CalculateSuggestionConfidence(NamingSuggestion suggestion)
{
double confidence = 0.5; // Base confidence
// Higher confidence for clear convention fixes
if (suggestion.Issues.Any(i => i.Contains("convention")))
confidence += 0.3;
// Lower confidence for subjective meaningfulness issues
if (suggestion.Issues.Any(i => i.Contains("not descriptive")))
confidence += 0.1;
// Higher confidence for abbreviation expansions
if (suggestion.Issues.Any(i => i.Contains("abbreviation")))
confidence += 0.2;
// Adjust based on number of suggestions
if (suggestion.SuggestedNames.Count == 1)
confidence += 0.1;
else if (suggestion.SuggestedNames.Count > 3)
confidence -= 0.1;
return Math.Min(1.0, confidence);
}
private async Task GenerateAISuggestions(NamingAnalysis analysis)
{
// This would integrate with an AI service to generate more sophisticated suggestions
// For now, we'll enhance the existing suggestions with better reasoning
foreach (var suggestion in analysis.Suggestions)
{
// Add AI-powered reasoning (simulated)
if (suggestion.IdentifierType == "Method" && suggestion.OriginalName.Length <= 3)
{
suggestion.Reasoning.Add("AI Suggestion: Method names should describe the action being performed");
suggestion.SuggestedNames.Add($"Execute{suggestion.OriginalName.ToUpper()}Operation");
}
if (suggestion.IdentifierType == "Class" && suggestion.OriginalName.Contains("_"))
{
suggestion.Reasoning.Add("AI Suggestion: Class names should use PascalCase without underscores");
}
}
await Task.CompletedTask;
}
private async Task<string> ApplyNamingChanges(string sourceCode, NamingAnalysis analysis)
{
var modifiedContent = sourceCode;
// Apply suggestions in reverse order of line numbers to maintain positions
var applicableSuggestions = analysis.Suggestions
.Where(s => s.ShouldApply && s.SuggestedNames.Any())
.OrderByDescending(s => s.LineNumber);
foreach (var suggestion in applicableSuggestions)
{
var bestSuggestion = suggestion.SuggestedNames.First();
// Simple text replacement (in a real implementation, you'd use Roslyn for accurate renaming)
modifiedContent = modifiedContent.Replace(suggestion.OriginalName, bestSuggestion);
}
return await Task.FromResult(modifiedContent);
}
private NamingStatistics CalculateNamingStatistics(NamingAnalysis analysis)
{
var stats = new NamingStatistics();
stats.TotalIdentifiers = analysis.Issues.Count;
stats.ConventionViolations = analysis.Issues.Count(i => i.Issues.Any(iss => iss.Contains("convention")));
stats.MeaningfulnessIssues = analysis.Issues.Count(i => i.Issues.Any(iss => iss.Contains("descriptive") || iss.Contains("short")));
stats.AbbreviationIssues = analysis.Issues.Count(i => i.Issues.Any(iss => iss.Contains("abbreviation")));
stats.SeverityBreakdown = analysis.Issues
.GroupBy(i => i.Severity)
.ToDictionary(g => g.Key, g => g.Count());
stats.TypeBreakdown = analysis.Issues
.GroupBy(i => i.IdentifierType)
.ToDictionary(g => g.Key, g => g.Count());
stats.QualityScore = CalculateNamingQualityScore(analysis);
return stats;
}
private double CalculateNamingQualityScore(NamingAnalysis analysis)
{
if (!analysis.Issues.Any()) return 100.0;
double score = 100.0;
// Penalize based on severity
score -= analysis.Issues.Count(i => i.Severity == "High") * 10;
score -= analysis.Issues.Count(i => i.Severity == "Medium") * 5;
score -= analysis.Issues.Count(i => i.Severity == "Low") * 2;
return Math.Max(0, score);
}
// 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;
}
private int GetIntParameter(IReadOnlyDictionary<string, object> parameters, string key1, string key2, int defaultValue = 0)
{
var value = GetParameterValue(parameters, key1, key2);
return value != null ? Convert.ToInt32(value) : defaultValue;
}
}
// Supporting classes for naming analysis
public class NamingAnalysis
{
public string FilePath { get; set; }
public string Convention { get; set; }
public List<NamingIssue> Issues { get; set; } = new List<NamingIssue>();
public List<NamingSuggestion> Suggestions { get; set; } = new List<NamingSuggestion>();
public NamingStatistics Statistics { get; set; }
}
public class NamingIssue
{
public string IdentifierType { get; set; }
public string Name { get; set; }
public int LineNumber { get; set; }
public List<string> Issues { get; set; } = new List<string>();
public string Severity { get; set; }
}
public class NamingSuggestion
{
public string OriginalName { get; set; }
public string IdentifierType { get; set; }
public int LineNumber { get; set; }
public List<string> Issues { get; set; } = new List<string>();
public List<string> SuggestedNames { get; set; } = new List<string>();
public List<string> Reasoning { get; set; } = new List<string>();
public double Confidence { get; set; }
public bool ShouldApply { get; set; }
}
public class NamingStatistics
{
public int TotalIdentifiers { get; set; }
public int ConventionViolations { get; set; }
public int MeaningfulnessIssues { get; set; }
public int AbbreviationIssues { get; set; }
public Dictionary<string, int> SeverityBreakdown { get; set; } = new Dictionary<string, int>();
public Dictionary<string, int> TypeBreakdown { get; set; } = new Dictionary<string, int>();
public double QualityScore { get; set; }
}
}