From 7284f9b00eb332ddbdb9cc4600e26182bebff2dd Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 28 Feb 2025 14:43:44 +0100 Subject: [PATCH] Add project files. --- App.xaml | 9 + App.xaml.cs | 13 + AssemblyInfo.cs | 10 + CodeMerger.cs | 1353 ++++++++++++++++++++++++ CodeMergerNG.csproj | 18 + CodeMergerNG.sln | 25 + Controls/CodeStructureTreeView.xaml | 15 + Controls/CodeStructureTreeView.xaml.cs | 28 + Controls/SimpleMergeLogControl.xaml | 24 + Controls/SimpleMergeLogControl.xaml.cs | 114 ++ Controls/TestExamplesControl.xaml | 23 + Controls/TestExamplesControl.xaml.cs | 120 +++ MainWindow.xaml | 136 +++ MainWindow.xaml.cs | 110 ++ Settings.cs | 205 ++++ TestExamples/TestExampleCode.cs | 396 +++++++ ViewModels/MainWindowViewModel.cs | 597 +++++++++++ 17 files changed, 3196 insertions(+) create mode 100644 App.xaml create mode 100644 App.xaml.cs create mode 100644 AssemblyInfo.cs create mode 100644 CodeMerger.cs create mode 100644 CodeMergerNG.csproj create mode 100644 CodeMergerNG.sln create mode 100644 Controls/CodeStructureTreeView.xaml create mode 100644 Controls/CodeStructureTreeView.xaml.cs create mode 100644 Controls/SimpleMergeLogControl.xaml create mode 100644 Controls/SimpleMergeLogControl.xaml.cs create mode 100644 Controls/TestExamplesControl.xaml create mode 100644 Controls/TestExamplesControl.xaml.cs create mode 100644 MainWindow.xaml create mode 100644 MainWindow.xaml.cs create mode 100644 Settings.cs create mode 100644 TestExamples/TestExampleCode.cs create mode 100644 ViewModels/MainWindowViewModel.cs diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..3949fa7 --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..ea9a93d --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,13 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace CodeMergerNG; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ +} + diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs new file mode 100644 index 0000000..cc29e7f --- /dev/null +++ b/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/CodeMerger.cs b/CodeMerger.cs new file mode 100644 index 0000000..19e08ed --- /dev/null +++ b/CodeMerger.cs @@ -0,0 +1,1353 @@ +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; + +/// +/// Clase para realizar análisis y merge de código C# utilizando Roslyn +/// +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 _methodsBySignature = new Dictionary(); + private Dictionary _propertiesByName = new Dictionary(); + private Dictionary _fieldsByName = new Dictionary(); + private Dictionary _classesByName = new Dictionary(); + private Dictionary _namespacesByName = new Dictionary(); + private Dictionary _usingsByName = new Dictionary(); + + #endregion + + #region Métodos Públicos + + /// + /// Constructor de la clase CodeMerger + /// + /// Namespace por defecto a usar cuando no se especifique + /// Clase por defecto a usar cuando no se especifique + public cCodeMerger(string defaultNamespace = null, string defaultClass = null) + { + if (!string.IsNullOrEmpty(defaultNamespace)) + DefaultNamespace = defaultNamespace; + + if (!string.IsNullOrEmpty(defaultClass)) + DefaultClass = defaultClass; + + LogInfo("CodeMerger initialized"); + } + + /// + /// Verifica rápidamente si un código contiene un namespace específico + /// + 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); + } + + /// + /// Verifica rápidamente si un código contiene una clase específica + /// + 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); + } + + /// + /// Analiza el código C# y genera un árbol de CodeElement + /// + /// Código C# a analizar + /// Elemento raíz del árbol generado + 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; + } + } + + /// + /// Realiza el merge de código snippet dentro de código master + /// + /// Código principal que recibirá las modificaciones + /// Código a integrar en el master + /// Código resultante del merge + 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 + } + } + + /// + /// Obtiene el log de operaciones realizadas + /// + public string GetLog() + { + return _log.ToString(); + } + + /// + /// Limpia el log de operaciones + /// + public void ClearLog() + { + _log.Clear(); + LogInfo("Log cleared"); + } + + #endregion + + #region Métodos Privados para Análisis de Código + + /// + /// Prepara el código para análisis, añadiendo namespace y clase si es necesario + /// + 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} }}"; + } + } + + /// + /// Construye el árbol de CodeElement a partir del árbol sintáctico de Roslyn + /// + 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()) + { + ProcessNamespace(namespaceDeclaration, rootElement); + } + + // Procesar clases que estén directamente en el root (sin namespace) + foreach (var typeDeclaration in compilationUnit.Members.OfType()) + { + if (typeDeclaration is ClassDeclarationSyntax classDeclaration) + { + ProcessClass(classDeclaration, rootElement); + } + } + + return rootElement; + } + + /// + /// Procesa un namespace y sus miembros + /// + 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); + } + } + } + + /// + /// Procesa una clase y sus miembros + /// + 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); + } + } + } + + /// + /// Procesa un campo (field) de clase + /// + 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})"); + } + } + + /// + /// Procesa una propiedad de clase + /// + 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})"); + } + + /// + /// Procesa un método de clase + /// + 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})"); + } + + /// + /// Procesa un constructor de clase + /// + 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}"); + } + + /// + /// Extrae los parámetros de un método + /// + private List ExtractParametersFromMethod(MethodDeclarationSyntax methodDeclaration) + { + var parameters = new List(); + + 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; + } + + /// + /// Extrae los parámetros de un constructor + /// + private List ExtractParametersFromConstructor(ConstructorDeclarationSyntax constructorDeclaration) + { + var parameters = new List(); + + 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; + } + + /// + /// Genera una firma normalizada estricta para un método + /// + 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})"; + } + + /// + /// Genera una firma normalizada estricta para un constructor + /// + 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})"; + } + + /// + /// Extrae la indentación de un nodo sintáctico + /// + private string ExtractIndentation(SyntaxNode node) + { + var leadingTrivia = node.GetLeadingTrivia(); + var whitespaceTrivia = leadingTrivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia)); + return whitespaceTrivia.ToString(); + } + + /// + /// Extrae comentarios que preceden a un nodo + /// + 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); + } + + /// + /// Extrae comentarios que siguen a un nodo + /// + 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); + } + + /// + /// Obtiene el nombre del namespace desde el padre de un elemento + /// + private string GetNamespaceFromParent(CodeElement parent) + { + if (parent.Type == CodeElement.ElementType.Namespace) + return parent.Name; + + if (parent.NamespaceName != null) + return parent.NamespaceName; + + return VIRTUAL_NAMESPACE; + } + + /// + /// Construye índices para búsqueda rápida de elementos + /// + 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); + } + + /// + /// Construye índices de búsqueda recursivamente + /// + 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 + + /// + /// Realiza el merge de dos elementos de código y sus hijos + /// + 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; + } + + /// + /// Realiza el merge de directivas using + /// + 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); + } + } + } + + /// + /// Realiza el merge de namespaces + /// + 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); + } + } + } + + /// + /// Procesa una clase del snippet, ya sea agregándola o mergeando con una existente + /// + 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); + } + } + + /// + /// Merge de miembros de una clase (fields, properties, methods, constructors) + /// + 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); + } + + /// + /// Merge de campos (fields) de clase + /// + 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 + } + } + + /// + /// Merge de propiedades de clase + /// + 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 + } + } + + /// + /// Merge de métodos de clase + /// + 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); + } + } + } + + /// + /// Merge de constructores de clase + /// + 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); + } + } + } + + /// + /// Merge de clases anidadas + /// + 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); + } + } + } + + /// + /// Reconstruye el código a partir de un árbol de CodeElement + /// + 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 + + /// + /// Registra una operación de adición + /// + private void LogAddition(CodeElement element) + { + _log.AppendLine($"ADDED: {element.Type} {element.Name}"); + } + + /// + /// Registra una operación de reemplazo + /// + private void LogReplacement(CodeElement oldElement, CodeElement newElement) + { + _log.AppendLine($"REPLACED: {oldElement.Type} {oldElement.Name} with new implementation"); + } + + /// + /// Registra un mensaje informativo + /// + private void LogInfo(string message) + { + _log.AppendLine($"INFO: {message}"); + } + + /// + /// Registra un mensaje de error + /// + private void LogError(string message) + { + _log.AppendLine($"ERROR: {message}"); + } + + #endregion +} + +/// +/// Elemento de código analizado +/// +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 Children { get; set; } = new List(); + + // 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 Attributes { get; set; } = new List(); + + // 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 TypeParameters { get; set; } = new List(); + + // Para clases: información de herencia e interfaces + public string BaseClass { get; set; } + public List ImplementedInterfaces { get; set; } = new List(); + + // 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 Parameters { get; set; } = new List(); + + #endregion + + #region Métodos + + /// + /// Agrega un elemento hijo con manejo automático de relación Parent + /// + public void AddChild(CodeElement child) + { + Children.Add(child); + child.Parent = this; + } + + /// + /// Busca un elemento hijo por nombre y tipo + /// + public CodeElement FindChild(string name, ElementType type) + { + return Children.FirstOrDefault(c => c.Name == name && c.Type == type); + } + + /// + /// Encuentra todos los elementos hijos de un tipo específico + /// + public IEnumerable FindAllChildren(ElementType type) + { + return Children.Where(c => c.Type == type); + } + + /// + /// Busca en toda la jerarquía del elemento según un predicado + /// + public CodeElement FindInHierarchy(Func predicate) + { + if (predicate(this)) return this; + + foreach (var child in Children) + { + var result = child.FindInHierarchy(predicate); + if (result != null) return result; + } + + return null; + } + + /// + /// Obtiene todos los elementos descendientes de un tipo específico + /// + public IEnumerable GetAllDescendantsOfType(ElementType type) + { + var result = new List(); + CollectDescendantsOfType(this, type, result); + return result; + } + + /// + /// Método auxiliar para recopilar descendientes recursivamente + /// + private void CollectDescendantsOfType(CodeElement element, ElementType type, List result) + { + foreach (var child in element.Children) + { + if (child.Type == type) + result.Add(child); + + CollectDescendantsOfType(child, type, result); + } + } + + /// + /// Representación en cadena del elemento + /// + public override string ToString() + { + return $"{Type}: {NormalizedSignature ?? Name}"; + } + + #endregion +} + +/// +/// Información de un parámetro de método o constructor +/// +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}" : "")}"; + } +} \ No newline at end of file diff --git a/CodeMergerNG.csproj b/CodeMergerNG.csproj new file mode 100644 index 0000000..f876c82 --- /dev/null +++ b/CodeMergerNG.csproj @@ -0,0 +1,18 @@ + + + + WinExe + net8.0-windows + enable + enable + true + + + + + + + + + + diff --git a/CodeMergerNG.sln b/CodeMergerNG.sln new file mode 100644 index 0000000..021a1d2 --- /dev/null +++ b/CodeMergerNG.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35806.99 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeMergerNG", "CodeMergerNG.csproj", "{E34F5B22-68C1-454A-A5FB-6FE89352C3C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E34F5B22-68C1-454A-A5FB-6FE89352C3C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E34F5B22-68C1-454A-A5FB-6FE89352C3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E34F5B22-68C1-454A-A5FB-6FE89352C3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E34F5B22-68C1-454A-A5FB-6FE89352C3C9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4FB3A6C1-5305-4483-A6A9-2322D8361527} + EndGlobalSection +EndGlobal diff --git a/Controls/CodeStructureTreeView.xaml b/Controls/CodeStructureTreeView.xaml new file mode 100644 index 0000000..f0f9a3f --- /dev/null +++ b/Controls/CodeStructureTreeView.xaml @@ -0,0 +1,15 @@ + + + + +