using NetDocsForLLM.Models; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; namespace NetDocsForLLM.Services { /// /// Implementación del servicio de análisis de ensamblados basado en Reflection /// public class ReflectionAnalyzerService : IDocFxService { private readonly string _workingDirectory; 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); // 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 { var assembly = assemblyModel.LoadedAssembly; if (assembly == null) throw new ArgumentException("El ensamblado no está cargado", nameof(assemblyModel)); // Cargar comentarios XML si existen XDocument xmlDoc = null; if (assemblyModel.HasXmlDocumentation && File.Exists(assemblyModel.XmlDocPath)) { try { xmlDoc = XDocument.Load(assemblyModel.XmlDocPath); } catch (Exception) { // Si hay error al cargar XML, continuar sin comentarios } } // Procesar todos los tipos exportados foreach (var type in assembly.GetExportedTypes()) { try { // Generar archivo de metadatos para cada tipo var typeInfo = new { Name = type.Name, FullName = type.FullName, Namespace = type.Namespace, IsClass = type.IsClass, IsInterface = type.IsInterface, IsEnum = type.IsEnum, IsAbstract = type.IsAbstract, IsSealed = type.IsSealed, IsPublic = type.IsPublic, BaseType = type.BaseType?.FullName, Interfaces = type.GetInterfaces().Select(i => i.FullName).ToArray(), XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(type)), Members = GetMembers(type, xmlDoc) }; // Guardar en archivo JSON string typePath = Path.Combine(outputPath, $"{type.FullName.Replace('+', '_')}.json"); File.WriteAllText(typePath, JsonConvert.SerializeObject(typeInfo, Formatting.Indented)); } catch (Exception ex) { // Registrar error para este tipo pero continuar con otros File.WriteAllText( Path.Combine(outputPath, $"error_{type.FullName.Replace('+', '_')}.txt"), $"Error procesando tipo {type.FullName}: {ex.Message}\n{ex.StackTrace}"); } } } catch (Exception ex) { // Registrar el error pero continuar con otros ensamblados File.WriteAllText( Path.Combine(outputPath, $"error_{Path.GetFileNameWithoutExtension(assemblyModel.FilePath)}.txt"), $"Error procesando ensamblado: {ex.Message}\n{ex.StackTrace}"); } } private object[] GetMembers(Type type, XDocument xmlDoc) { var result = new List(); // Obtener métodos foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { try { if (method.IsSpecialName) // Excluir getters/setters de propiedades continue; var parameters = method.GetParameters().Select(p => new { Name = p.Name, Type = GetFriendlyTypeName(p.ParameterType), IsOptional = p.IsOptional, DefaultValue = p.IsOptional ? ConvertDefaultValueToString(p.DefaultValue) : null }).ToArray(); var methodInfo = new { Name = method.Name, MemberType = "Method", ReturnType = GetFriendlyTypeName(method.ReturnType), IsStatic = method.IsStatic, IsPublic = method.IsPublic, Parameters = parameters, XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(method)) }; result.Add(methodInfo); } catch { // Ignorar métodos con problemas continue; } } // Obtener propiedades foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { try { var propertyInfo = new { Name = property.Name, MemberType = "Property", Type = GetFriendlyTypeName(property.PropertyType), CanRead = property.CanRead, CanWrite = property.CanWrite, IsStatic = property.GetAccessors(true).FirstOrDefault()?.IsStatic ?? false, XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(property)) }; result.Add(propertyInfo); } catch { // Ignorar propiedades con problemas continue; } } // Obtener eventos foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { try { var eventData = new { Name = eventInfo.Name, MemberType = "Event", EventHandlerType = GetFriendlyTypeName(eventInfo.EventHandlerType), IsStatic = eventInfo.GetAddMethod()?.IsStatic ?? false, XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(eventInfo)) }; result.Add(eventData); } catch { // Ignorar eventos con problemas continue; } } // Obtener campos foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { try { var fieldInfo = new { Name = field.Name, MemberType = "Field", Type = GetFriendlyTypeName(field.FieldType), IsStatic = field.IsStatic, IsConstant = field.IsLiteral && !field.IsInitOnly, Value = field.IsLiteral ? ConvertDefaultValueToString(field.GetValue(null)) : null, XmlDocumentation = GetXmlDocumentation(xmlDoc, GetMemberXmlId(field)) }; result.Add(fieldInfo); } catch { // Ignorar campos con problemas continue; } } return result.ToArray(); } private string GetFriendlyTypeName(Type type) { if (type == null) return "void"; if (type.IsGenericType) { var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetFriendlyTypeName)); return $"{type.Name.Split('`')[0]}<{genericArgs}>"; } // Usar nombres simplificados para tipos comunes 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(decimal)) return "decimal"; if (type == typeof(long)) return "long"; if (type == typeof(short)) return "short"; if (type == typeof(byte)) return "byte"; if (type == typeof(char)) return "char"; if (type == typeof(object)) return "object"; return type.FullName ?? type.Name; } private string ConvertDefaultValueToString(object defaultValue) { if (defaultValue == null) return "null"; // Para strings, agregar comillas if (defaultValue is string stringValue) return $"\"{stringValue}\""; return defaultValue.ToString(); } private string GetMemberXmlId(MemberInfo member) { // Generar ID XML según el formato estándar de documentación XML string prefix = ""; 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 if (member is Type) prefix = "T:"; // Manejar tipos anidados string declaringFullName; if (member.DeclaringType != null) { declaringFullName = member.DeclaringType.FullName; } else if (member is Type typeInfo) { declaringFullName = typeInfo.FullName; } else { declaringFullName = "Unknown"; } return prefix + declaringFullName + "." + member.Name; } private object GetXmlDocumentation(XDocument xmlDoc, string memberId) { if (xmlDoc == null) return null; try { // Buscar nodo de miembro por ID var memberNode = xmlDoc.Descendants("member") .FirstOrDefault(m => m.Attribute("name")?.Value == memberId); if (memberNode == null) return null; // Extraer elementos de documentación var summary = memberNode.Element("summary")?.Value.Trim(); var remarks = memberNode.Element("remarks")?.Value.Trim(); var returns = memberNode.Element("returns")?.Value.Trim(); var parameters = memberNode.Elements("param") .Select(p => new { Name = p.Attribute("name")?.Value, Description = p.Value.Trim() }) .Where(p => !string.IsNullOrEmpty(p.Name)) .ToArray(); var examples = memberNode.Elements("example") .Select(e => e.Value.Trim()) .ToArray(); return new { Summary = summary, Remarks = remarks, Returns = returns, Parameters = parameters, Examples = examples }; } catch { return null; } } } }