NetDocsForLLM/Services/ReflectionAnalyzerService.cs

504 lines
17 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.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<string> GenerateMetadataAsync(IEnumerable<AssemblyModel> 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<object>();
foreach (var nsGroup in exportedTypes.GroupBy(t => t.Namespace ?? "Global"))
{
string namespaceName = nsGroup.Key;
var types = new List<object>();
_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<object>();
// 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<object> paramDocs = new List<object>();
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;
}
}
}