using NetDocsForLLM.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; namespace NetDocsForLLM.Services { public class ReflectionAnalyzerService : IDocFxService { private readonly string _workingDirectory; private TextWriter _logWriter; private XDocument _xmlDoc; // Variable de clase para acceder al XML desde cualquier método public ReflectionAnalyzerService() { // Crear un directorio temporal para trabajar _workingDirectory = Path.Combine(Path.GetTempPath(), "NetDocsForLLM_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_workingDirectory); } public async Task GenerateMetadataAsync(IEnumerable assemblies) { // Crear directorio para metadatos var metadataPath = Path.Combine(_workingDirectory, "metadata"); Directory.CreateDirectory(metadataPath); // Crear archivo de log var logPath = Path.Combine(metadataPath, "analysis_log.txt"); using (_logWriter = new StreamWriter(logPath, false)) { _logWriter.WriteLine($"Iniciando análisis simplificado para LLMs: {DateTime.Now}"); // Para cada ensamblado, generar archivo de metadatos foreach (var assembly in assemblies) { await Task.Run(() => ProcessAssembly(assembly, metadataPath)); } } return metadataPath; } private void ProcessAssembly(AssemblyModel assemblyModel, string outputPath) { try { _logWriter.WriteLine($"\n=== Procesando ensamblado: {assemblyModel.Name} ==="); Assembly assembly = assemblyModel.LoadedAssembly; if (assembly == null) { _logWriter.WriteLine("ERROR: El ensamblado no está cargado"); return; } // Cargar documentación XML si existe _xmlDoc = null; if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath)) { try { _xmlDoc = XDocument.Load(assemblyModel.XmlDocPath); _logWriter.WriteLine($"XML cargado: {assemblyModel.XmlDocPath}"); } catch (Exception ex) { _logWriter.WriteLine($"Error cargando XML: {ex.Message}"); } } // Procesar todos los tipos exportados var exportedTypes = assembly.GetExportedTypes() .Where(t => t.IsPublic && !t.IsSpecialName) // Solo tipos públicos y no especiales .ToList(); _logWriter.WriteLine($"Tipos exportados encontrados: {exportedTypes.Count}"); // Agrupar tipos por namespace var namespaces = new List(); foreach (var nsGroup in exportedTypes.GroupBy(t => t.Namespace ?? "Global")) { string namespaceName = nsGroup.Key; var types = new List(); _logWriter.WriteLine($"\nProcesando namespace: {namespaceName}"); foreach (var type in nsGroup) { // Solo incluir tipos que no sean anidados o que sean útiles if (!type.IsNested || type.IsNestedPublic) { types.Add(ExtractSimplifiedTypeInfo(type)); } } if (types.Count > 0) { namespaces.Add(new { Name = namespaceName, Types = types.ToArray() }); } } // Guardar toda la documentación en un solo archivo var docStructure = new { AssemblyName = assembly.GetName().Name, Version = assembly.GetName().Version.ToString(), Namespaces = namespaces.ToArray() }; string outputFile = Path.Combine(outputPath, $"assembly_{assemblyModel.Name}.json"); File.WriteAllText(outputFile, JsonConvert.SerializeObject(docStructure, Formatting.Indented)); _logWriter.WriteLine($"Documentación guardada en: {outputFile}"); } catch (Exception ex) { _logWriter.WriteLine($"ERROR procesando ensamblado: {ex.Message}"); _logWriter.WriteLine(ex.StackTrace); } } private object ExtractSimplifiedTypeInfo(Type type) { try { _logWriter.WriteLine($" Documentando tipo: {type.Name}"); // Obtener comentario XML del tipo string description = GetXmlSummary(type); // Obtener miembros var members = new List(); // Métodos públicos (no especiales como getters/setters) foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly) .Where(m => !m.IsSpecialName)) { var memberObject = DocumentMethod(method); if (memberObject != null) members.Add(memberObject); } // Propiedades públicas foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { var memberObject = DocumentProperty(property); if (memberObject != null) members.Add(memberObject); } // Campos públicos foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { var memberObject = DocumentField(field); if (memberObject != null) members.Add(memberObject); } // Eventos públicos foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { var memberObject = DocumentEvent(eventInfo); if (memberObject != null) members.Add(memberObject); } _logWriter.WriteLine($" Miembros documentados: {members.Count}"); // Crear objeto de información de tipo var typeInfo = new { Name = type.Name, FullName = type.FullName, Kind = GetTypeKind(type), Description = description, // Solo incluir interfaces si hay alguna Interfaces = type.GetInterfaces().Length > 0 ? type.GetInterfaces().Select(i => i.Name).ToArray() : null, // Solo incluir base si no es Object BaseType = (type.BaseType != null && type.BaseType != typeof(object)) ? type.BaseType.Name : null, // Solo incluir miembros si hay alguno Members = members.Count > 0 ? members.ToArray() : null }; return typeInfo; } catch (Exception ex) { _logWriter.WriteLine($" ERROR documentando tipo {type.Name}: {ex.Message}"); // Devolver información mínima return new { Name = type.Name, FullName = type.FullName, Kind = GetTypeKind(type), Error = "Error al extraer información" }; } } private object DocumentMethod(MethodInfo method) { try { string description = GetXmlSummary(method); // Parametros var parameters = method.GetParameters(); List paramDocs = new List(); if (parameters.Length > 0) { foreach (var param in parameters) { string paramDesc = GetXmlParamDescription(method, param.Name); paramDocs.Add(new { Name = param.Name, Type = GetSimpleTypeName(param.ParameterType), Description = !string.IsNullOrEmpty(paramDesc) ? paramDesc : null }); } } // Información de retorno string returnDesc = GetXmlReturnDescription(method); return new { Name = method.Name, Type = "Method", Description = description, Returns = GetSimpleTypeName(method.ReturnType), ReturnDescription = !string.IsNullOrEmpty(returnDesc) ? returnDesc : null, Parameters = paramDocs.Count > 0 ? paramDocs.ToArray() : null, IsStatic = method.IsStatic }; } catch { return null; // Si hay error, simplemente no incluir este método } } private object DocumentProperty(PropertyInfo property) { try { string description = GetXmlSummary(property); return new { Name = property.Name, Type = "Property", Description = description, PropertyType = GetSimpleTypeName(property.PropertyType), CanRead = property.CanRead, CanWrite = property.CanWrite, IsStatic = property.GetAccessors(true).FirstOrDefault()?.IsStatic ?? false }; } catch { return null; } } private object DocumentField(FieldInfo field) { try { string description = GetXmlSummary(field); return new { Name = field.Name, Type = "Field", Description = description, FieldType = GetSimpleTypeName(field.FieldType), IsStatic = field.IsStatic, IsConstant = field.IsLiteral }; } catch { return null; } } private object DocumentEvent(EventInfo eventInfo) { try { string description = GetXmlSummary(eventInfo); return new { Name = eventInfo.Name, Type = "Event", Description = description, EventType = GetSimpleTypeName(eventInfo.EventHandlerType), IsStatic = eventInfo.GetAddMethod()?.IsStatic ?? false }; } catch { return null; } } // Métodos auxiliares private string GetTypeKind(Type type) { if (type.IsEnum) return "Enum"; if (type.IsInterface) return "Interface"; if (type.IsValueType) return "Struct"; if (type.IsClass) { if (type.IsAbstract && type.IsSealed) return "Static Class"; if (type.IsAbstract) return "Abstract Class"; return "Class"; } return "Type"; } private string GetSimpleTypeName(Type type) { if (type == null) return "void"; // Tipos primitivos if (type == typeof(void)) return "void"; if (type == typeof(int)) return "int"; if (type == typeof(string)) return "string"; if (type == typeof(bool)) return "bool"; if (type == typeof(double)) return "double"; if (type == typeof(float)) return "float"; if (type == typeof(object)) return "object"; // Arrays if (type.IsArray) { return $"{GetSimpleTypeName(type.GetElementType())}[]"; } // Genéricos if (type.IsGenericType) { string baseName = type.Name; int tickIndex = baseName.IndexOf('`'); if (tickIndex > 0) { baseName = baseName.Substring(0, tickIndex); } var argTypes = type.GetGenericArguments() .Select(GetSimpleTypeName) .ToArray(); return $"{baseName}<{string.Join(", ", argTypes)}>"; } // Para tipos normales, usar solo el nombre simple return type.Name; } // Métodos para extraer documentación XML private string GetXmlSummary(MemberInfo member) { if (_xmlDoc == null) return null; try { string xmlId = GetXmlDocId(member); var docNode = _xmlDoc.Descendants("member") .FirstOrDefault(m => m.Attribute("name")?.Value == xmlId); if (docNode != null) { var summaryNode = docNode.Element("summary"); if (summaryNode != null) { return CleanXmlComment(summaryNode.Value); } } } catch { /* Ignorar errores */ } return null; } private string GetXmlParamDescription(MethodInfo method, string paramName) { if (_xmlDoc == null) return null; try { string xmlId = GetXmlDocId(method); var docNode = _xmlDoc.Descendants("member") .FirstOrDefault(m => m.Attribute("name")?.Value == xmlId); if (docNode != null) { var paramNode = docNode.Elements("param") .FirstOrDefault(p => p.Attribute("name")?.Value == paramName); if (paramNode != null) { return CleanXmlComment(paramNode.Value); } } } catch { /* Ignorar errores */ } return null; } private string GetXmlReturnDescription(MethodInfo method) { if (_xmlDoc == null) return null; try { string xmlId = GetXmlDocId(method); var docNode = _xmlDoc.Descendants("member") .FirstOrDefault(m => m.Attribute("name")?.Value == xmlId); if (docNode != null) { var returnNode = docNode.Element("returns"); if (returnNode != null) { return CleanXmlComment(returnNode.Value); } } } catch { /* Ignorar errores */ } return null; } private string GetXmlDocId(MemberInfo member) { string prefix; if (member is Type) prefix = "T:"; else if (member is MethodInfo) prefix = "M:"; else if (member is PropertyInfo) prefix = "P:"; else if (member is EventInfo) prefix = "E:"; else if (member is FieldInfo) prefix = "F:"; else return null; // Para tipos if (member is Type type) return $"{prefix}{type.FullName}"; // Para miembros string declaringType = member.DeclaringType?.FullName ?? "Unknown"; return $"{prefix}{declaringType}.{member.Name}"; } private string CleanXmlComment(string comment) { if (string.IsNullOrEmpty(comment)) return null; // Eliminar espacios en blanco extra comment = comment.Trim(); // Convertir saltos de línea y tabulaciones a espacios simples comment = comment.Replace("\r", " ") .Replace("\n", " ") .Replace("\t", " "); // Reemplazar múltiples espacios con uno solo while (comment.Contains(" ")) comment = comment.Replace(" ", " "); return comment; } } }