1353 lines
46 KiB
C#
1353 lines
46 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using System.Text.RegularExpressions;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using Microsoft.CodeAnalysis;
|
|||
|
using Microsoft.CodeAnalysis.CSharp;
|
|||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clase para realizar análisis y merge de código C# utilizando Roslyn
|
|||
|
/// </summary>
|
|||
|
public class cCodeMerger
|
|||
|
{
|
|||
|
#region Constantes
|
|||
|
|
|||
|
// Constantes para namespace y clase virtual
|
|||
|
private const string VIRTUAL_NAMESPACE = "__VirtualNamespace__";
|
|||
|
private const string VIRTUAL_CLASS = "__VirtualClass__";
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Propiedades
|
|||
|
|
|||
|
// Configuración para namespace y clase por defecto
|
|||
|
public string DefaultNamespace { get; set; } = VIRTUAL_NAMESPACE;
|
|||
|
public string DefaultClass { get; set; } = VIRTUAL_CLASS;
|
|||
|
|
|||
|
// Log de operaciones
|
|||
|
private StringBuilder _log = new StringBuilder();
|
|||
|
|
|||
|
// Diccionarios para búsqueda rápida
|
|||
|
private Dictionary<string, CodeElement> _methodsBySignature = new Dictionary<string, CodeElement>();
|
|||
|
private Dictionary<string, CodeElement> _propertiesByName = new Dictionary<string, CodeElement>();
|
|||
|
private Dictionary<string, CodeElement> _fieldsByName = new Dictionary<string, CodeElement>();
|
|||
|
private Dictionary<string, CodeElement> _classesByName = new Dictionary<string, CodeElement>();
|
|||
|
private Dictionary<string, CodeElement> _namespacesByName = new Dictionary<string, CodeElement>();
|
|||
|
private Dictionary<string, CodeElement> _usingsByName = new Dictionary<string, CodeElement>();
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Métodos Públicos
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Constructor de la clase CodeMerger
|
|||
|
/// </summary>
|
|||
|
/// <param name="defaultNamespace">Namespace por defecto a usar cuando no se especifique</param>
|
|||
|
/// <param name="defaultClass">Clase por defecto a usar cuando no se especifique</param>
|
|||
|
public cCodeMerger(string defaultNamespace = null, string defaultClass = null)
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(defaultNamespace))
|
|||
|
DefaultNamespace = defaultNamespace;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(defaultClass))
|
|||
|
DefaultClass = defaultClass;
|
|||
|
|
|||
|
LogInfo("CodeMerger initialized");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Verifica rápidamente si un código contiene un namespace específico
|
|||
|
/// </summary>
|
|||
|
public bool ContainsNamespace(string code, string namespaceName)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(namespaceName))
|
|||
|
return false;
|
|||
|
|
|||
|
string pattern = $@"namespace\s+{Regex.Escape(namespaceName)}\s*{{";
|
|||
|
return Regex.IsMatch(code, pattern);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Verifica rápidamente si un código contiene una clase específica
|
|||
|
/// </summary>
|
|||
|
public bool ContainsClass(string code, string className)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(className))
|
|||
|
return false;
|
|||
|
|
|||
|
string pattern = $@"class\s+{Regex.Escape(className)}\s*{{";
|
|||
|
return Regex.IsMatch(code, pattern);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Analiza el código C# y genera un árbol de CodeElement
|
|||
|
/// </summary>
|
|||
|
/// <param name="code">Código C# a analizar</param>
|
|||
|
/// <returns>Elemento raíz del árbol generado</returns>
|
|||
|
public CodeElement AnalyzeCode(string code)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(code))
|
|||
|
{
|
|||
|
LogError("Empty code provided for analysis");
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
LogInfo("Starting code analysis");
|
|||
|
|
|||
|
// Preparar código asegurando que tenga namespace y clase si es necesario
|
|||
|
string preparedCode = PrepareCodeForParsing(code);
|
|||
|
|
|||
|
// Parsear código con Roslyn
|
|||
|
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(preparedCode);
|
|||
|
CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot();
|
|||
|
|
|||
|
// Construir árbol de CodeElement
|
|||
|
CodeElement rootElement = BuildCodeElementTree(root);
|
|||
|
|
|||
|
// Construir índices para búsqueda rápida
|
|||
|
BuildSearchIndices(rootElement);
|
|||
|
|
|||
|
LogInfo($"Code analysis completed. Root element: {rootElement.Type} {rootElement.Name}");
|
|||
|
|
|||
|
return rootElement;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
LogError($"Error analyzing code: {ex.Message}");
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Realiza el merge de código snippet dentro de código master
|
|||
|
/// </summary>
|
|||
|
/// <param name="masterCode">Código principal que recibirá las modificaciones</param>
|
|||
|
/// <param name="snippetCode">Código a integrar en el master</param>
|
|||
|
/// <returns>Código resultante del merge</returns>
|
|||
|
public string MergeCode(string masterCode, string snippetCode)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
LogInfo("Starting code merge operation");
|
|||
|
|
|||
|
// Analizar ambos códigos
|
|||
|
CodeElement masterRoot = AnalyzeCode(masterCode);
|
|||
|
CodeElement snippetRoot = AnalyzeCode(snippetCode);
|
|||
|
|
|||
|
if (masterRoot == null || snippetRoot == null)
|
|||
|
{
|
|||
|
LogError("Could not analyze one or both code inputs");
|
|||
|
return masterCode;
|
|||
|
}
|
|||
|
|
|||
|
// Realizar el merge
|
|||
|
CodeElement mergedRoot = MergeElements(masterRoot, snippetRoot);
|
|||
|
|
|||
|
// Reconstruir el código resultante
|
|||
|
string result = ReconstructCode(mergedRoot);
|
|||
|
|
|||
|
LogInfo("Code merge completed successfully");
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
LogError($"Error during code merge: {ex.Message}");
|
|||
|
return masterCode; // En caso de error, devolvemos el código master sin cambios
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Obtiene el log de operaciones realizadas
|
|||
|
/// </summary>
|
|||
|
public string GetLog()
|
|||
|
{
|
|||
|
return _log.ToString();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Limpia el log de operaciones
|
|||
|
/// </summary>
|
|||
|
public void ClearLog()
|
|||
|
{
|
|||
|
_log.Clear();
|
|||
|
LogInfo("Log cleared");
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Métodos Privados para Análisis de Código
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Prepara el código para análisis, añadiendo namespace y clase si es necesario
|
|||
|
/// </summary>
|
|||
|
private string PrepareCodeForParsing(string code)
|
|||
|
{
|
|||
|
bool hasNamespace = ContainsNamespace(code, ".*?");
|
|||
|
bool hasClass = ContainsClass(code, ".*?");
|
|||
|
|
|||
|
if (hasNamespace && hasClass)
|
|||
|
return code;
|
|||
|
|
|||
|
if (!hasNamespace && !hasClass)
|
|||
|
{
|
|||
|
// Ni namespace ni clase: envolver en ambos
|
|||
|
LogInfo($"Adding virtual namespace '{DefaultNamespace}' and class '{DefaultClass}'");
|
|||
|
return $"namespace {DefaultNamespace} {{ class {DefaultClass} {{ {code} }} }}";
|
|||
|
}
|
|||
|
else if (hasNamespace && !hasClass)
|
|||
|
{
|
|||
|
// Tiene namespace pero no clase
|
|||
|
LogInfo($"Adding virtual class '{DefaultClass}'");
|
|||
|
// Necesitamos insertar la clase dentro del namespace existente
|
|||
|
Match namespaceMatch = Regex.Match(code, @"namespace\s+([^\s{]+)\s*{");
|
|||
|
if (namespaceMatch.Success)
|
|||
|
{
|
|||
|
int openBracePos = code.IndexOf('{', namespaceMatch.Index);
|
|||
|
if (openBracePos >= 0)
|
|||
|
{
|
|||
|
return code.Substring(0, openBracePos + 1) +
|
|||
|
$" class {DefaultClass} {{ " +
|
|||
|
code.Substring(openBracePos + 1) +
|
|||
|
" } ";
|
|||
|
}
|
|||
|
}
|
|||
|
return $"namespace {DefaultNamespace} {{ class {DefaultClass} {{ {code} }} }}";
|
|||
|
}
|
|||
|
else // hasClass && !hasNamespace
|
|||
|
{
|
|||
|
// Tiene clase pero no namespace
|
|||
|
LogInfo($"Adding virtual namespace '{DefaultNamespace}'");
|
|||
|
return $"namespace {DefaultNamespace} {{ {code} }}";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Construye el árbol de CodeElement a partir del árbol sintáctico de Roslyn
|
|||
|
/// </summary>
|
|||
|
private CodeElement BuildCodeElementTree(CompilationUnitSyntax compilationUnit)
|
|||
|
{
|
|||
|
// Crear elemento raíz para representar el archivo completo
|
|||
|
var rootElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Other,
|
|||
|
Name = "Root",
|
|||
|
OriginalText = compilationUnit.ToString(),
|
|||
|
StartPosition = 0,
|
|||
|
Length = compilationUnit.ToString().Length
|
|||
|
};
|
|||
|
|
|||
|
// Procesar todas las directivas using
|
|||
|
foreach (var usingDirective in compilationUnit.Usings)
|
|||
|
{
|
|||
|
var usingElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Using,
|
|||
|
Name = usingDirective.Name.ToString(),
|
|||
|
OriginalText = usingDirective.ToString(),
|
|||
|
StartPosition = usingDirective.SpanStart,
|
|||
|
Length = usingDirective.Span.Length
|
|||
|
};
|
|||
|
|
|||
|
rootElement.AddChild(usingElement);
|
|||
|
LogInfo($"Found using: {usingElement.Name}");
|
|||
|
}
|
|||
|
|
|||
|
// Procesar todos los namespaces
|
|||
|
foreach (var namespaceDeclaration in compilationUnit.Members.OfType<NamespaceDeclarationSyntax>())
|
|||
|
{
|
|||
|
ProcessNamespace(namespaceDeclaration, rootElement);
|
|||
|
}
|
|||
|
|
|||
|
// Procesar clases que estén directamente en el root (sin namespace)
|
|||
|
foreach (var typeDeclaration in compilationUnit.Members.OfType<TypeDeclarationSyntax>())
|
|||
|
{
|
|||
|
if (typeDeclaration is ClassDeclarationSyntax classDeclaration)
|
|||
|
{
|
|||
|
ProcessClass(classDeclaration, rootElement);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return rootElement;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa un namespace y sus miembros
|
|||
|
/// </summary>
|
|||
|
private void ProcessNamespace(NamespaceDeclarationSyntax namespaceDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
var namespaceElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Namespace,
|
|||
|
Name = namespaceDeclaration.Name.ToString(),
|
|||
|
OriginalText = namespaceDeclaration.ToString(),
|
|||
|
StartPosition = namespaceDeclaration.SpanStart,
|
|||
|
Length = namespaceDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(namespaceDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(namespaceDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(namespaceDeclaration)
|
|||
|
};
|
|||
|
|
|||
|
parent.AddChild(namespaceElement);
|
|||
|
LogInfo($"Found namespace: {namespaceElement.Name}");
|
|||
|
|
|||
|
// Procesar todas las clases dentro del namespace
|
|||
|
foreach (var member in namespaceDeclaration.Members)
|
|||
|
{
|
|||
|
if (member is ClassDeclarationSyntax classDeclaration)
|
|||
|
{
|
|||
|
ProcessClass(classDeclaration, namespaceElement);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa una clase y sus miembros
|
|||
|
/// </summary>
|
|||
|
private void ProcessClass(ClassDeclarationSyntax classDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
var classElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Class,
|
|||
|
Name = classDeclaration.Identifier.ToString(),
|
|||
|
OriginalText = classDeclaration.ToString(),
|
|||
|
StartPosition = classDeclaration.SpanStart,
|
|||
|
Length = classDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(classDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(classDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(classDeclaration),
|
|||
|
NamespaceName = parent.Type == CodeElement.ElementType.Namespace ? parent.Name : VIRTUAL_NAMESPACE
|
|||
|
};
|
|||
|
|
|||
|
// Extraer información de herencia e interfaces
|
|||
|
if (classDeclaration.BaseList != null)
|
|||
|
{
|
|||
|
foreach (var baseType in classDeclaration.BaseList.Types)
|
|||
|
{
|
|||
|
string baseTypeName = baseType.Type.ToString();
|
|||
|
if (baseType == classDeclaration.BaseList.Types.First())
|
|||
|
{
|
|||
|
classElement.BaseClass = baseTypeName;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
classElement.ImplementedInterfaces.Add(baseTypeName);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Extraer modificadores
|
|||
|
classElement.Modifiers = classDeclaration.Modifiers
|
|||
|
.Select(m => m.ToString())
|
|||
|
.ToArray();
|
|||
|
|
|||
|
// Extraer atributos
|
|||
|
if (classDeclaration.AttributeLists.Any())
|
|||
|
{
|
|||
|
classElement.Attributes = classDeclaration.AttributeLists
|
|||
|
.SelectMany(a => a.Attributes)
|
|||
|
.Select(a => a.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
parent.AddChild(classElement);
|
|||
|
LogInfo($"Found class: {classElement.Name}");
|
|||
|
|
|||
|
// Procesar todos los miembros de la clase
|
|||
|
foreach (var member in classDeclaration.Members)
|
|||
|
{
|
|||
|
if (member is FieldDeclarationSyntax fieldDeclaration)
|
|||
|
{
|
|||
|
ProcessField(fieldDeclaration, classElement);
|
|||
|
}
|
|||
|
else if (member is PropertyDeclarationSyntax propertyDeclaration)
|
|||
|
{
|
|||
|
ProcessProperty(propertyDeclaration, classElement);
|
|||
|
}
|
|||
|
else if (member is MethodDeclarationSyntax methodDeclaration)
|
|||
|
{
|
|||
|
ProcessMethod(methodDeclaration, classElement);
|
|||
|
}
|
|||
|
else if (member is ConstructorDeclarationSyntax constructorDeclaration)
|
|||
|
{
|
|||
|
ProcessConstructor(constructorDeclaration, classElement);
|
|||
|
}
|
|||
|
else if (member is ClassDeclarationSyntax nestedClassDeclaration)
|
|||
|
{
|
|||
|
// Clase anidada: procesamos recursivamente
|
|||
|
ProcessClass(nestedClassDeclaration, classElement);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa un campo (field) de clase
|
|||
|
/// </summary>
|
|||
|
private void ProcessField(FieldDeclarationSyntax fieldDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
foreach (var variable in fieldDeclaration.Declaration.Variables)
|
|||
|
{
|
|||
|
var fieldElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Field,
|
|||
|
Name = variable.Identifier.ToString(),
|
|||
|
ReturnType = fieldDeclaration.Declaration.Type.ToString(),
|
|||
|
OriginalText = fieldDeclaration.ToString(),
|
|||
|
StartPosition = fieldDeclaration.SpanStart,
|
|||
|
Length = fieldDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(fieldDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(fieldDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(fieldDeclaration),
|
|||
|
NamespaceName = GetNamespaceFromParent(parent),
|
|||
|
ClassName = parent.Name
|
|||
|
};
|
|||
|
|
|||
|
// Extraer modificadores
|
|||
|
fieldElement.Modifiers = fieldDeclaration.Modifiers
|
|||
|
.Select(m => m.ToString())
|
|||
|
.ToArray();
|
|||
|
|
|||
|
// Extraer inicializador si existe
|
|||
|
if (variable.Initializer != null)
|
|||
|
{
|
|||
|
fieldElement.Initializer = variable.Initializer.Value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
// Extraer atributos
|
|||
|
if (fieldDeclaration.AttributeLists.Any())
|
|||
|
{
|
|||
|
fieldElement.Attributes = fieldDeclaration.AttributeLists
|
|||
|
.SelectMany(a => a.Attributes)
|
|||
|
.Select(a => a.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
// Generar firma normalizada
|
|||
|
fieldElement.NormalizedSignature = $"{fieldElement.ReturnType} {fieldElement.Name}";
|
|||
|
|
|||
|
parent.AddChild(fieldElement);
|
|||
|
LogInfo($"Found field: {fieldElement.Name} ({fieldElement.ReturnType})");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa una propiedad de clase
|
|||
|
/// </summary>
|
|||
|
private void ProcessProperty(PropertyDeclarationSyntax propertyDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
var propertyElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Property,
|
|||
|
Name = propertyDeclaration.Identifier.ToString(),
|
|||
|
ReturnType = propertyDeclaration.Type.ToString(),
|
|||
|
OriginalText = propertyDeclaration.ToString(),
|
|||
|
StartPosition = propertyDeclaration.SpanStart,
|
|||
|
Length = propertyDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(propertyDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(propertyDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(propertyDeclaration),
|
|||
|
NamespaceName = GetNamespaceFromParent(parent),
|
|||
|
ClassName = parent.Name
|
|||
|
};
|
|||
|
|
|||
|
// Extraer modificadores
|
|||
|
propertyElement.Modifiers = propertyDeclaration.Modifiers
|
|||
|
.Select(m => m.ToString())
|
|||
|
.ToArray();
|
|||
|
|
|||
|
// Extraer accessors (get/set)
|
|||
|
if (propertyDeclaration.AccessorList != null)
|
|||
|
{
|
|||
|
foreach (var accessor in propertyDeclaration.AccessorList.Accessors)
|
|||
|
{
|
|||
|
if (accessor.Keyword.Text == "get")
|
|||
|
{
|
|||
|
propertyElement.HasGetter = true;
|
|||
|
propertyElement.GetterAccessibility = accessor.Modifiers.Any()
|
|||
|
? accessor.Modifiers.First().ToString()
|
|||
|
: "";
|
|||
|
}
|
|||
|
else if (accessor.Keyword.Text == "set")
|
|||
|
{
|
|||
|
propertyElement.HasSetter = true;
|
|||
|
propertyElement.SetterAccessibility = accessor.Modifiers.Any()
|
|||
|
? accessor.Modifiers.First().ToString()
|
|||
|
: "";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Extraer inicializador de propiedad (para propiedades con => o { get; set; } = value)
|
|||
|
if (propertyDeclaration.Initializer != null)
|
|||
|
{
|
|||
|
propertyElement.Initializer = propertyDeclaration.Initializer.Value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
// Extraer atributos
|
|||
|
if (propertyDeclaration.AttributeLists.Any())
|
|||
|
{
|
|||
|
propertyElement.Attributes = propertyDeclaration.AttributeLists
|
|||
|
.SelectMany(a => a.Attributes)
|
|||
|
.Select(a => a.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
// Generar firma normalizada
|
|||
|
propertyElement.NormalizedSignature = $"{propertyElement.ReturnType} {propertyElement.Name}";
|
|||
|
|
|||
|
parent.AddChild(propertyElement);
|
|||
|
LogInfo($"Found property: {propertyElement.Name} ({propertyElement.ReturnType})");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa un método de clase
|
|||
|
/// </summary>
|
|||
|
private void ProcessMethod(MethodDeclarationSyntax methodDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
var methodElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Method,
|
|||
|
Name = methodDeclaration.Identifier.ToString(),
|
|||
|
ReturnType = methodDeclaration.ReturnType.ToString(),
|
|||
|
OriginalText = methodDeclaration.ToString(),
|
|||
|
StartPosition = methodDeclaration.SpanStart,
|
|||
|
Length = methodDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(methodDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(methodDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(methodDeclaration),
|
|||
|
NamespaceName = GetNamespaceFromParent(parent),
|
|||
|
ClassName = parent.Name
|
|||
|
};
|
|||
|
|
|||
|
// Extraer modificadores
|
|||
|
methodElement.Modifiers = methodDeclaration.Modifiers
|
|||
|
.Select(m => m.ToString())
|
|||
|
.ToArray();
|
|||
|
|
|||
|
// Extraer parámetros
|
|||
|
methodElement.Parameters = ExtractParametersFromMethod(methodDeclaration);
|
|||
|
|
|||
|
// Extraer parámetros de tipo para métodos genéricos
|
|||
|
if (methodDeclaration.TypeParameterList != null)
|
|||
|
{
|
|||
|
methodElement.TypeParameters = methodDeclaration.TypeParameterList.Parameters
|
|||
|
.Select(p => p.Identifier.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
// Extraer atributos
|
|||
|
if (methodDeclaration.AttributeLists.Any())
|
|||
|
{
|
|||
|
methodElement.Attributes = methodDeclaration.AttributeLists
|
|||
|
.SelectMany(a => a.Attributes)
|
|||
|
.Select(a => a.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
// Generar firma normalizada según requerimiento estricto
|
|||
|
GenerateStrictMethodSignature(methodElement);
|
|||
|
|
|||
|
parent.AddChild(methodElement);
|
|||
|
LogInfo($"Found method: {methodElement.Name} ({methodElement.NormalizedSignature})");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa un constructor de clase
|
|||
|
/// </summary>
|
|||
|
private void ProcessConstructor(ConstructorDeclarationSyntax constructorDeclaration, CodeElement parent)
|
|||
|
{
|
|||
|
var constructorElement = new CodeElement
|
|||
|
{
|
|||
|
Type = CodeElement.ElementType.Constructor,
|
|||
|
Name = constructorDeclaration.Identifier.ToString(),
|
|||
|
OriginalText = constructorDeclaration.ToString(),
|
|||
|
StartPosition = constructorDeclaration.SpanStart,
|
|||
|
Length = constructorDeclaration.Span.Length,
|
|||
|
Indentation = ExtractIndentation(constructorDeclaration),
|
|||
|
LeadingComments = ExtractLeadingComments(constructorDeclaration),
|
|||
|
TrailingComments = ExtractTrailingComments(constructorDeclaration),
|
|||
|
NamespaceName = GetNamespaceFromParent(parent),
|
|||
|
ClassName = parent.Name
|
|||
|
};
|
|||
|
|
|||
|
// Extraer modificadores
|
|||
|
constructorElement.Modifiers = constructorDeclaration.Modifiers
|
|||
|
.Select(m => m.ToString())
|
|||
|
.ToArray();
|
|||
|
|
|||
|
// Extraer parámetros
|
|||
|
constructorElement.Parameters = ExtractParametersFromConstructor(constructorDeclaration);
|
|||
|
|
|||
|
// Extraer atributos
|
|||
|
if (constructorDeclaration.AttributeLists.Any())
|
|||
|
{
|
|||
|
constructorElement.Attributes = constructorDeclaration.AttributeLists
|
|||
|
.SelectMany(a => a.Attributes)
|
|||
|
.Select(a => a.ToString())
|
|||
|
.ToList();
|
|||
|
}
|
|||
|
|
|||
|
// Generar firma normalizada
|
|||
|
GenerateStrictConstructorSignature(constructorElement);
|
|||
|
|
|||
|
parent.AddChild(constructorElement);
|
|||
|
LogInfo($"Found constructor: {constructorElement.NormalizedSignature}");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Extrae los parámetros de un método
|
|||
|
/// </summary>
|
|||
|
private List<ParameterInfo> ExtractParametersFromMethod(MethodDeclarationSyntax methodDeclaration)
|
|||
|
{
|
|||
|
var parameters = new List<ParameterInfo>();
|
|||
|
|
|||
|
if (methodDeclaration.ParameterList != null)
|
|||
|
{
|
|||
|
foreach (var param in methodDeclaration.ParameterList.Parameters)
|
|||
|
{
|
|||
|
var paramInfo = new ParameterInfo
|
|||
|
{
|
|||
|
Type = param.Type?.ToString() ?? "var",
|
|||
|
Name = param.Identifier.ToString(),
|
|||
|
HasDefaultValue = param.Default != null
|
|||
|
};
|
|||
|
|
|||
|
// Capturar modificadores exactos (ref, out, in, params)
|
|||
|
if (param.Modifiers.Any())
|
|||
|
{
|
|||
|
paramInfo.Modifier = string.Join(" ", param.Modifiers) + " ";
|
|||
|
}
|
|||
|
|
|||
|
// Capturar valores por defecto si existen
|
|||
|
if (param.Default != null)
|
|||
|
{
|
|||
|
paramInfo.DefaultValue = param.Default.Value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
parameters.Add(paramInfo);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return parameters;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Extrae los parámetros de un constructor
|
|||
|
/// </summary>
|
|||
|
private List<ParameterInfo> ExtractParametersFromConstructor(ConstructorDeclarationSyntax constructorDeclaration)
|
|||
|
{
|
|||
|
var parameters = new List<ParameterInfo>();
|
|||
|
|
|||
|
if (constructorDeclaration.ParameterList != null)
|
|||
|
{
|
|||
|
foreach (var param in constructorDeclaration.ParameterList.Parameters)
|
|||
|
{
|
|||
|
var paramInfo = new ParameterInfo
|
|||
|
{
|
|||
|
Type = param.Type?.ToString() ?? "var",
|
|||
|
Name = param.Identifier.ToString(),
|
|||
|
HasDefaultValue = param.Default != null
|
|||
|
};
|
|||
|
|
|||
|
// Capturar modificadores exactos (ref, out, in, params)
|
|||
|
if (param.Modifiers.Any())
|
|||
|
{
|
|||
|
paramInfo.Modifier = string.Join(" ", param.Modifiers) + " ";
|
|||
|
}
|
|||
|
|
|||
|
// Capturar valores por defecto si existen
|
|||
|
if (param.Default != null)
|
|||
|
{
|
|||
|
paramInfo.DefaultValue = param.Default.Value.ToString();
|
|||
|
}
|
|||
|
|
|||
|
parameters.Add(paramInfo);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return parameters;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Genera una firma normalizada estricta para un método
|
|||
|
/// </summary>
|
|||
|
private void GenerateStrictMethodSignature(CodeElement methodElement)
|
|||
|
{
|
|||
|
// Construir lista de parámetros con tipo y nombre
|
|||
|
var paramList = methodElement.Parameters.Select(p => $"{p.Modifier}{p.Type} {p.Name}").ToList();
|
|||
|
string paramString = string.Join(", ", paramList);
|
|||
|
|
|||
|
// Incluir tipo genérico si existe
|
|||
|
string typeParams = "";
|
|||
|
if (methodElement.TypeParameters != null && methodElement.TypeParameters.Count > 0)
|
|||
|
{
|
|||
|
typeParams = $"<{string.Join(", ", methodElement.TypeParameters)}>";
|
|||
|
}
|
|||
|
|
|||
|
// Incluir todo: tipo de retorno, nombre del método, parámetros de tipo y firma completa de parámetros
|
|||
|
methodElement.NormalizedSignature = $"{methodElement.ReturnType} {methodElement.Name}{typeParams}({paramString})";
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Genera una firma normalizada estricta para un constructor
|
|||
|
/// </summary>
|
|||
|
private void GenerateStrictConstructorSignature(CodeElement constructorElement)
|
|||
|
{
|
|||
|
// Construir lista de parámetros con tipo y nombre
|
|||
|
var paramList = constructorElement.Parameters.Select(p => $"{p.Modifier}{p.Type} {p.Name}").ToList();
|
|||
|
string paramString = string.Join(", ", paramList);
|
|||
|
|
|||
|
// Para constructores: nombre de clase + parámetros completos
|
|||
|
constructorElement.NormalizedSignature = $"{constructorElement.Name}({paramString})";
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Extrae la indentación de un nodo sintáctico
|
|||
|
/// </summary>
|
|||
|
private string ExtractIndentation(SyntaxNode node)
|
|||
|
{
|
|||
|
var leadingTrivia = node.GetLeadingTrivia();
|
|||
|
var whitespaceTrivia = leadingTrivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
|
|||
|
return whitespaceTrivia.ToString();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Extrae comentarios que preceden a un nodo
|
|||
|
/// </summary>
|
|||
|
private string ExtractLeadingComments(SyntaxNode node)
|
|||
|
{
|
|||
|
var leadingTrivia = node.GetLeadingTrivia();
|
|||
|
var comments = leadingTrivia.Where(t =>
|
|||
|
t.IsKind(SyntaxKind.SingleLineCommentTrivia) ||
|
|||
|
t.IsKind(SyntaxKind.MultiLineCommentTrivia) ||
|
|||
|
t.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia));
|
|||
|
|
|||
|
return string.Join(Environment.NewLine, comments);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Extrae comentarios que siguen a un nodo
|
|||
|
/// </summary>
|
|||
|
private string ExtractTrailingComments(SyntaxNode node)
|
|||
|
{
|
|||
|
var trailingTrivia = node.GetTrailingTrivia();
|
|||
|
var comments = trailingTrivia.Where(t =>
|
|||
|
t.IsKind(SyntaxKind.SingleLineCommentTrivia) ||
|
|||
|
t.IsKind(SyntaxKind.MultiLineCommentTrivia));
|
|||
|
|
|||
|
return string.Join(Environment.NewLine, comments);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Obtiene el nombre del namespace desde el padre de un elemento
|
|||
|
/// </summary>
|
|||
|
private string GetNamespaceFromParent(CodeElement parent)
|
|||
|
{
|
|||
|
if (parent.Type == CodeElement.ElementType.Namespace)
|
|||
|
return parent.Name;
|
|||
|
|
|||
|
if (parent.NamespaceName != null)
|
|||
|
return parent.NamespaceName;
|
|||
|
|
|||
|
return VIRTUAL_NAMESPACE;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Construye índices para búsqueda rápida de elementos
|
|||
|
/// </summary>
|
|||
|
private void BuildSearchIndices(CodeElement root)
|
|||
|
{
|
|||
|
// Limpiar índices existentes
|
|||
|
_methodsBySignature.Clear();
|
|||
|
_propertiesByName.Clear();
|
|||
|
_fieldsByName.Clear();
|
|||
|
_classesByName.Clear();
|
|||
|
_namespacesByName.Clear();
|
|||
|
_usingsByName.Clear();
|
|||
|
|
|||
|
// Recorrer árbol y poblar índices
|
|||
|
BuildIndicesRecursive(root);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Construye índices de búsqueda recursivamente
|
|||
|
/// </summary>
|
|||
|
private void BuildIndicesRecursive(CodeElement element)
|
|||
|
{
|
|||
|
// Agregar elemento al índice apropiado
|
|||
|
switch (element.Type)
|
|||
|
{
|
|||
|
case CodeElement.ElementType.Method:
|
|||
|
_methodsBySignature[element.NormalizedSignature] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Constructor:
|
|||
|
_methodsBySignature[element.NormalizedSignature] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Property:
|
|||
|
_propertiesByName[element.Name] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Field:
|
|||
|
_fieldsByName[element.Name] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Class:
|
|||
|
_classesByName[element.Name] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Namespace:
|
|||
|
_namespacesByName[element.Name] = element;
|
|||
|
break;
|
|||
|
case CodeElement.ElementType.Using:
|
|||
|
_usingsByName[element.Name] = element;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// Procesar recursivamente hijos
|
|||
|
foreach (var child in element.Children)
|
|||
|
{
|
|||
|
BuildIndicesRecursive(child);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Métodos Privados para Merge de Código
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Realiza el merge de dos elementos de código y sus hijos
|
|||
|
/// </summary>
|
|||
|
private CodeElement MergeElements(CodeElement master, CodeElement snippet)
|
|||
|
{
|
|||
|
LogInfo($"Merging elements: {master.Type} and {snippet.Type}");
|
|||
|
|
|||
|
// El resultado base será una copia del elemento master
|
|||
|
var result = master;
|
|||
|
|
|||
|
// Merge de usings
|
|||
|
MergeUsings(result, snippet);
|
|||
|
|
|||
|
// Merge de namespaces
|
|||
|
MergeNamespaces(result, snippet);
|
|||
|
|
|||
|
// Merge de clases
|
|||
|
MergeClasses(result, snippet);
|
|||
|
|
|||
|
LogInfo("Element merge completed");
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Realiza el merge de directivas using
|
|||
|
/// </summary>
|
|||
|
private void MergeUsings(CodeElement master, CodeElement snippet)
|
|||
|
{
|
|||
|
var masterUsings = master.GetAllDescendantsOfType(CodeElement.ElementType.Using)
|
|||
|
.Select(u => u.Name)
|
|||
|
.ToHashSet();
|
|||
|
|
|||
|
foreach (var usingElement in snippet.GetAllDescendantsOfType(CodeElement.ElementType.Using))
|
|||
|
{
|
|||
|
if (!masterUsings.Contains(usingElement.Name))
|
|||
|
{
|
|||
|
// Nuevo using: agregar al master
|
|||
|
var rootOrNamespace = master.Children.FirstOrDefault(c => c.Type == CodeElement.ElementType.Namespace) ?? master;
|
|||
|
rootOrNamespace.AddChild(usingElement);
|
|||
|
LogAddition(usingElement);
|
|||
|
masterUsings.Add(usingElement.Name);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Realiza el merge de namespaces
|
|||
|
/// </summary>
|
|||
|
private void MergeNamespaces(CodeElement master, CodeElement snippet)
|
|||
|
{
|
|||
|
var masterNamespaces = master.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Namespace)
|
|||
|
.ToDictionary(n => n.Name);
|
|||
|
|
|||
|
foreach (var snippetNamespace in snippet.Children.Where(c => c.Type == CodeElement.ElementType.Namespace))
|
|||
|
{
|
|||
|
if (snippetNamespace.IsVirtualNamespace)
|
|||
|
{
|
|||
|
// Para namespace virtual, solo procesamos su contenido
|
|||
|
foreach (var snippetClass in snippetNamespace.Children.Where(c => c.Type == CodeElement.ElementType.Class))
|
|||
|
{
|
|||
|
ProcessSnippetClass(master, snippetClass);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (masterNamespaces.TryGetValue(snippetNamespace.Name, out var masterNamespace))
|
|||
|
{
|
|||
|
// Namespace existente: merge de su contenido
|
|||
|
foreach (var snippetClass in snippetNamespace.Children.Where(c => c.Type == CodeElement.ElementType.Class))
|
|||
|
{
|
|||
|
ProcessSnippetClass(masterNamespace, snippetClass);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Nuevo namespace: agregar al master
|
|||
|
master.AddChild(snippetNamespace);
|
|||
|
LogAddition(snippetNamespace);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Procesa una clase del snippet, ya sea agregándola o mergeando con una existente
|
|||
|
/// </summary>
|
|||
|
private void ProcessSnippetClass(CodeElement parent, CodeElement snippetClass)
|
|||
|
{
|
|||
|
// Buscar clase existente en el master
|
|||
|
var masterClass = parent.Children
|
|||
|
.FirstOrDefault(c => c.Type == CodeElement.ElementType.Class && c.Name == snippetClass.Name);
|
|||
|
|
|||
|
if (masterClass == null)
|
|||
|
{
|
|||
|
// Clase no existe: agregarla completa
|
|||
|
if (!snippetClass.IsVirtualClass)
|
|||
|
{
|
|||
|
parent.AddChild(snippetClass);
|
|||
|
LogAddition(snippetClass);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Para clase virtual, solo procesamos su contenido directamente en el parent
|
|||
|
MergeClassMembers(parent, snippetClass);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Clase ya existe: hacer merge de miembros
|
|||
|
MergeClassMembers(masterClass, snippetClass);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de miembros de una clase (fields, properties, methods, constructors)
|
|||
|
/// </summary>
|
|||
|
private void MergeClassMembers(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
// Merge de fields
|
|||
|
MergeFields(masterClass, snippetClass);
|
|||
|
|
|||
|
// Merge de propiedades
|
|||
|
MergeProperties(masterClass, snippetClass);
|
|||
|
|
|||
|
// Merge de métodos
|
|||
|
MergeMethods(masterClass, snippetClass);
|
|||
|
|
|||
|
// Merge de constructores
|
|||
|
MergeConstructors(masterClass, snippetClass);
|
|||
|
|
|||
|
// Clases anidadas
|
|||
|
MergeNestedClasses(masterClass, snippetClass);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de campos (fields) de clase
|
|||
|
/// </summary>
|
|||
|
private void MergeFields(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
var masterFields = masterClass.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Field)
|
|||
|
.ToDictionary(f => f.Name);
|
|||
|
|
|||
|
foreach (var snippetField in snippetClass.Children.Where(c => c.Type == CodeElement.ElementType.Field))
|
|||
|
{
|
|||
|
if (!masterFields.ContainsKey(snippetField.Name))
|
|||
|
{
|
|||
|
// Field no existe: agregarlo
|
|||
|
masterClass.AddChild(snippetField);
|
|||
|
LogAddition(snippetField);
|
|||
|
}
|
|||
|
// No reemplazamos fields existentes
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de propiedades de clase
|
|||
|
/// </summary>
|
|||
|
private void MergeProperties(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
var masterProperties = masterClass.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Property)
|
|||
|
.ToDictionary(p => p.Name);
|
|||
|
|
|||
|
foreach (var snippetProperty in snippetClass.Children.Where(c => c.Type == CodeElement.ElementType.Property))
|
|||
|
{
|
|||
|
if (!masterProperties.ContainsKey(snippetProperty.Name))
|
|||
|
{
|
|||
|
// Propiedad no existe: agregarla
|
|||
|
masterClass.AddChild(snippetProperty);
|
|||
|
LogAddition(snippetProperty);
|
|||
|
}
|
|||
|
// No reemplazamos propiedades existentes
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de métodos de clase
|
|||
|
/// </summary>
|
|||
|
private void MergeMethods(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
var masterMethods = masterClass.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Method)
|
|||
|
.ToDictionary(m => m.NormalizedSignature);
|
|||
|
|
|||
|
foreach (var snippetMethod in snippetClass.Children.Where(c => c.Type == CodeElement.ElementType.Method))
|
|||
|
{
|
|||
|
if (masterMethods.TryGetValue(snippetMethod.NormalizedSignature, out var existingMethod))
|
|||
|
{
|
|||
|
// Método existe: reemplazarlo
|
|||
|
masterClass.Children.Remove(existingMethod);
|
|||
|
masterClass.AddChild(snippetMethod);
|
|||
|
LogReplacement(existingMethod, snippetMethod);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Método no existe: agregarlo
|
|||
|
masterClass.AddChild(snippetMethod);
|
|||
|
LogAddition(snippetMethod);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de constructores de clase
|
|||
|
/// </summary>
|
|||
|
private void MergeConstructors(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
var masterConstructors = masterClass.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Constructor)
|
|||
|
.ToDictionary(m => m.NormalizedSignature);
|
|||
|
|
|||
|
foreach (var snippetConstructor in snippetClass.Children.Where(c => c.Type == CodeElement.ElementType.Constructor))
|
|||
|
{
|
|||
|
if (masterConstructors.TryGetValue(snippetConstructor.NormalizedSignature, out var existingConstructor))
|
|||
|
{
|
|||
|
// Constructor existe: reemplazarlo
|
|||
|
masterClass.Children.Remove(existingConstructor);
|
|||
|
masterClass.AddChild(snippetConstructor);
|
|||
|
LogReplacement(existingConstructor, snippetConstructor);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Constructor no existe: agregarlo
|
|||
|
masterClass.AddChild(snippetConstructor);
|
|||
|
LogAddition(snippetConstructor);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Merge de clases anidadas
|
|||
|
/// </summary>
|
|||
|
private void MergeNestedClasses(CodeElement masterClass, CodeElement snippetClass)
|
|||
|
{
|
|||
|
var masterNestedClasses = masterClass.Children
|
|||
|
.Where(c => c.Type == CodeElement.ElementType.Class)
|
|||
|
.ToDictionary(c => c.Name);
|
|||
|
|
|||
|
foreach (var snippetNestedClass in snippetClass.Children.Where(c => c.Type == CodeElement.ElementType.Class))
|
|||
|
{
|
|||
|
if (masterNestedClasses.TryGetValue(snippetNestedClass.Name, out var existingClass))
|
|||
|
{
|
|||
|
// Clase anidada existe: merge recursivo
|
|||
|
MergeClassMembers(existingClass, snippetNestedClass);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Clase anidada no existe: agregarla
|
|||
|
masterClass.AddChild(snippetNestedClass);
|
|||
|
LogAddition(snippetNestedClass);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reconstruye el código a partir de un árbol de CodeElement
|
|||
|
/// </summary>
|
|||
|
private string ReconstructCode(CodeElement root)
|
|||
|
{
|
|||
|
var sb = new StringBuilder();
|
|||
|
|
|||
|
// Reconstruir usando desde el root
|
|||
|
foreach (var usingElement in root.Children.Where(c => c.Type == CodeElement.ElementType.Using))
|
|||
|
{
|
|||
|
sb.AppendLine(usingElement.OriginalText);
|
|||
|
}
|
|||
|
|
|||
|
// Reconstruir namespaces y su contenido
|
|||
|
foreach (var namespaceElement in root.Children.Where(c => c.Type == CodeElement.ElementType.Namespace))
|
|||
|
{
|
|||
|
// Skip virtual namespace
|
|||
|
if (namespaceElement.IsVirtualNamespace)
|
|||
|
{
|
|||
|
// Reconstruir directamente el contenido del namespace virtual
|
|||
|
foreach (var classElement in namespaceElement.Children)
|
|||
|
{
|
|||
|
if (classElement.IsVirtualClass)
|
|||
|
{
|
|||
|
// Si es clase virtual, solo añadir su contenido
|
|||
|
foreach (var member in classElement.Children)
|
|||
|
{
|
|||
|
sb.AppendLine(member.OriginalText);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
sb.AppendLine(classElement.OriginalText);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Namespace normal - reconstruir completo
|
|||
|
sb.AppendLine(namespaceElement.OriginalText);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Reconstruir clases que estén directamente en el root
|
|||
|
foreach (var classElement in root.Children.Where(c => c.Type == CodeElement.ElementType.Class))
|
|||
|
{
|
|||
|
if (!classElement.IsVirtualClass)
|
|||
|
{
|
|||
|
sb.AppendLine(classElement.OriginalText);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return sb.ToString();
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Métodos para Logging
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Registra una operación de adición
|
|||
|
/// </summary>
|
|||
|
private void LogAddition(CodeElement element)
|
|||
|
{
|
|||
|
_log.AppendLine($"ADDED: {element.Type} {element.Name}");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Registra una operación de reemplazo
|
|||
|
/// </summary>
|
|||
|
private void LogReplacement(CodeElement oldElement, CodeElement newElement)
|
|||
|
{
|
|||
|
_log.AppendLine($"REPLACED: {oldElement.Type} {oldElement.Name} with new implementation");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Registra un mensaje informativo
|
|||
|
/// </summary>
|
|||
|
private void LogInfo(string message)
|
|||
|
{
|
|||
|
_log.AppendLine($"INFO: {message}");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Registra un mensaje de error
|
|||
|
/// </summary>
|
|||
|
private void LogError(string message)
|
|||
|
{
|
|||
|
_log.AppendLine($"ERROR: {message}");
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Elemento de código analizado
|
|||
|
/// </summary>
|
|||
|
public class CodeElement
|
|||
|
{
|
|||
|
public enum ElementType
|
|||
|
{
|
|||
|
Using,
|
|||
|
Namespace,
|
|||
|
Class,
|
|||
|
Method,
|
|||
|
Property,
|
|||
|
Field,
|
|||
|
Constructor,
|
|||
|
Other
|
|||
|
}
|
|||
|
|
|||
|
#region Propiedades Básicas
|
|||
|
|
|||
|
// Propiedades originales
|
|||
|
public ElementType Type { get; set; }
|
|||
|
public string Name { get; set; }
|
|||
|
public string NamespaceName { get; set; }
|
|||
|
public string ClassName { get; set; }
|
|||
|
public string NormalizedSignature { get; set; }
|
|||
|
public string OriginalText { get; set; }
|
|||
|
public int StartPosition { get; set; }
|
|||
|
public int Length { get; set; }
|
|||
|
|
|||
|
// Constantes para namespace y clase virtual
|
|||
|
private const string VIRTUAL_NAMESPACE = "__VirtualNamespace__";
|
|||
|
private const string VIRTUAL_CLASS = "__VirtualClass__";
|
|||
|
|
|||
|
public bool IsVirtualNamespace => NamespaceName == VIRTUAL_NAMESPACE;
|
|||
|
public bool IsVirtualClass => ClassName == VIRTUAL_CLASS;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Relaciones Jerárquicas
|
|||
|
|
|||
|
// Relaciones jerárquicas
|
|||
|
public CodeElement Parent { get; set; }
|
|||
|
public List<CodeElement> Children { get; set; } = new List<CodeElement>();
|
|||
|
|
|||
|
// Nivel de anidamiento (útil para visualización)
|
|||
|
public int NestingLevel => Parent == null ? 0 : Parent.NestingLevel + 1;
|
|||
|
|
|||
|
// Path completo en la jerarquía
|
|||
|
public string FullPath
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (Parent == null) return Name;
|
|||
|
return $"{Parent.FullPath}.{Name}";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Metadata para Reconstrucción
|
|||
|
|
|||
|
// Metadata para reconstrucción
|
|||
|
public string[] Modifiers { get; set; }
|
|||
|
public List<string> Attributes { get; set; } = new List<string>();
|
|||
|
|
|||
|
// Comentarios y trivia
|
|||
|
public string LeadingComments { get; set; }
|
|||
|
public string TrailingComments { get; set; }
|
|||
|
public string Indentation { get; set; }
|
|||
|
|
|||
|
// Parámetros de tipo para genéricos
|
|||
|
public List<string> TypeParameters { get; set; } = new List<string>();
|
|||
|
|
|||
|
// Para clases: información de herencia e interfaces
|
|||
|
public string BaseClass { get; set; }
|
|||
|
public List<string> ImplementedInterfaces { get; set; } = new List<string>();
|
|||
|
|
|||
|
// Para métodos y propiedades: tipo de retorno específico
|
|||
|
public string ReturnType { get; set; }
|
|||
|
|
|||
|
// Para propiedades: accessors
|
|||
|
public bool HasGetter { get; set; }
|
|||
|
public bool HasSetter { get; set; }
|
|||
|
public string GetterAccessibility { get; set; }
|
|||
|
public string SetterAccessibility { get; set; }
|
|||
|
|
|||
|
// Para campos: inicializador
|
|||
|
public string Initializer { get; set; }
|
|||
|
|
|||
|
// Para métodos y constructores: parámetros
|
|||
|
public List<ParameterInfo> Parameters { get; set; } = new List<ParameterInfo>();
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Métodos
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Agrega un elemento hijo con manejo automático de relación Parent
|
|||
|
/// </summary>
|
|||
|
public void AddChild(CodeElement child)
|
|||
|
{
|
|||
|
Children.Add(child);
|
|||
|
child.Parent = this;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Busca un elemento hijo por nombre y tipo
|
|||
|
/// </summary>
|
|||
|
public CodeElement FindChild(string name, ElementType type)
|
|||
|
{
|
|||
|
return Children.FirstOrDefault(c => c.Name == name && c.Type == type);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Encuentra todos los elementos hijos de un tipo específico
|
|||
|
/// </summary>
|
|||
|
public IEnumerable<CodeElement> FindAllChildren(ElementType type)
|
|||
|
{
|
|||
|
return Children.Where(c => c.Type == type);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Busca en toda la jerarquía del elemento según un predicado
|
|||
|
/// </summary>
|
|||
|
public CodeElement FindInHierarchy(Func<CodeElement, bool> predicate)
|
|||
|
{
|
|||
|
if (predicate(this)) return this;
|
|||
|
|
|||
|
foreach (var child in Children)
|
|||
|
{
|
|||
|
var result = child.FindInHierarchy(predicate);
|
|||
|
if (result != null) return result;
|
|||
|
}
|
|||
|
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Obtiene todos los elementos descendientes de un tipo específico
|
|||
|
/// </summary>
|
|||
|
public IEnumerable<CodeElement> GetAllDescendantsOfType(ElementType type)
|
|||
|
{
|
|||
|
var result = new List<CodeElement>();
|
|||
|
CollectDescendantsOfType(this, type, result);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Método auxiliar para recopilar descendientes recursivamente
|
|||
|
/// </summary>
|
|||
|
private void CollectDescendantsOfType(CodeElement element, ElementType type, List<CodeElement> result)
|
|||
|
{
|
|||
|
foreach (var child in element.Children)
|
|||
|
{
|
|||
|
if (child.Type == type)
|
|||
|
result.Add(child);
|
|||
|
|
|||
|
CollectDescendantsOfType(child, type, result);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Representación en cadena del elemento
|
|||
|
/// </summary>
|
|||
|
public override string ToString()
|
|||
|
{
|
|||
|
return $"{Type}: {NormalizedSignature ?? Name}";
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Información de un parámetro de método o constructor
|
|||
|
/// </summary>
|
|||
|
public class ParameterInfo
|
|||
|
{
|
|||
|
public string Type { get; set; }
|
|||
|
public string Name { get; set; }
|
|||
|
public string Modifier { get; set; } = ""; // ref, out, in, params
|
|||
|
public bool HasDefaultValue { get; set; }
|
|||
|
public string DefaultValue { get; set; }
|
|||
|
|
|||
|
public override string ToString()
|
|||
|
{
|
|||
|
return $"{Modifier}{Type} {Name}{(HasDefaultValue ? $" = {DefaultValue}" : "")}";
|
|||
|
}
|
|||
|
}
|