931 lines
32 KiB
C#
Executable File
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; }
|
|
}
|
|
} |