NetDocsForLLM/Models/ReflectionAnalyzerService.cs

343 lines
13 KiB
C#

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
{
/// <summary>
/// Implementación del servicio de análisis de ensamblados basado en Reflection
/// </summary>
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<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> 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<object>();
// 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;
}
}
}
}