NetDocsForLLM/Services/XmlDocGenerator.cs

431 lines
16 KiB
C#

using NetDocsForLLM.Models;
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;
using System.Xml.Linq;
namespace NetDocsForLLM.Services
{
public class XmlDocGenerator : IDocFxService
{
private readonly string _workingDirectory;
private TextWriter _logWriter;
public XmlDocGenerator()
{
// 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 generación de documentación XML: {DateTime.Now}");
// Para cada ensamblado, generar documentación XML
foreach (var assembly in assemblies)
{
await Task.Run(() => GenerateXmlDocumentation(assembly, metadataPath));
}
}
return metadataPath;
}
private void GenerateXmlDocumentation(AssemblyModel assemblyModel, string outputPath)
{
try
{
Assembly assembly = assemblyModel.LoadedAssembly;
if (assembly == null)
{
_logWriter.WriteLine($"ERROR: El ensamblado {assemblyModel.Name} no está cargado");
return;
}
_logWriter.WriteLine($"\n=== Procesando ensamblado: {assemblyModel.Name} ===");
// Crear el documento XML
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("doc",
new XElement("assembly",
new XElement("name", assembly.GetName().Name)
),
new XElement("members")
)
);
// Obtener el elemento members para agregar miembros
var membersElement = doc.Root.Element("members");
// Procesar todos los tipos exportados
foreach (var type in assembly.GetExportedTypes().Where(t => t.IsPublic))
{
try
{
// Generar documentación para el tipo
GenerateTypeDocumentation(type, membersElement);
// Generar documentación para métodos
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName))
{
GenerateMethodDocumentation(method, membersElement);
}
// Generar documentación para propiedades
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
GeneratePropertyDocumentation(property, membersElement);
}
// Generar documentación para eventos
foreach (var eventInfo in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
GenerateEventDocumentation(eventInfo, membersElement);
}
// Generar documentación para campos
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
{
GenerateFieldDocumentation(field, membersElement);
}
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error procesando tipo {type.FullName}: {ex.Message}");
}
}
// Guardar la documentación XML
string xmlFileName = $"{assembly.GetName().Name}.xml";
string xmlFilePath = Path.Combine(outputPath, xmlFileName);
// Guardar con formato
using (var writer = new XmlTextWriter(xmlFilePath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Indentation = 4;
doc.Save(writer);
}
_logWriter.WriteLine($"Documentación XML guardada en: {xmlFilePath}");
}
catch (Exception ex)
{
_logWriter.WriteLine($"ERROR general procesando ensamblado {assemblyModel.Name}: {ex.Message}");
_logWriter.WriteLine(ex.StackTrace);
}
}
private void GenerateTypeDocumentation(Type type, XElement membersElement)
{
string typeId = $"T:{type.FullName}";
var memberElement = new XElement("member", new XAttribute("name", typeId));
// Agregar summary
memberElement.Add(new XElement("summary",
$"Represents a {GetTypeKindDescription(type)}: {type.Name}"));
// Si es una clase que hereda de otra (excepto Object), agregar esa información
if (type.IsClass && type.BaseType != null && type.BaseType != typeof(object))
{
memberElement.Add(new XElement("remarks",
$"Inherits from {type.BaseType.Name}"));
}
// Si implementa interfaces, mencionarlas
var interfaces = type.GetInterfaces();
if (interfaces.Length > 0)
{
var implementsText = $"Implements: {string.Join(", ", interfaces.Select(i => i.Name))}";
if (memberElement.Element("remarks") != null)
{
memberElement.Element("remarks").Value += $"\n{implementsText}";
}
else
{
memberElement.Add(new XElement("remarks", implementsText));
}
}
membersElement.Add(memberElement);
}
private void GenerateMethodDocumentation(MethodInfo method, XElement membersElement)
{
try
{
string methodId = GetMethodXmlId(method);
var memberElement = new XElement("member", new XAttribute("name", methodId));
// Agregar summary básico
memberElement.Add(new XElement("summary",
$"{method.Name} {GetMethodDescription(method)}"));
// Agregar parámetros
foreach (var param in method.GetParameters())
{
memberElement.Add(new XElement("param",
new XAttribute("name", param.Name),
GetParameterDescription(param)));
}
// Agregar returns si no es void
if (method.ReturnType != typeof(void))
{
memberElement.Add(new XElement("returns",
$"A {GetSimpleTypeName(method.ReturnType)} value."));
}
membersElement.Add(memberElement);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para método {method.Name}: {ex.Message}");
}
}
private void GeneratePropertyDocumentation(PropertyInfo property, XElement membersElement)
{
try
{
string propertyId = $"P:{property.DeclaringType.FullName}.{property.Name}";
var memberElement = new XElement("member", new XAttribute("name", propertyId));
// Accesibilidad (get/set)
string access = "";
if (property.CanRead) access += "get; ";
if (property.CanWrite) access += "set; ";
// Agregar summary
memberElement.Add(new XElement("summary",
$"Gets{(property.CanWrite ? " or sets" : "")} the {property.Name} property."));
// Agregar valor
memberElement.Add(new XElement("value",
$"A {GetSimpleTypeName(property.PropertyType)} representing the {property.Name}."));
membersElement.Add(memberElement);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para propiedad {property.Name}: {ex.Message}");
}
}
private void GenerateEventDocumentation(EventInfo eventInfo, XElement membersElement)
{
try
{
string eventId = $"E:{eventInfo.DeclaringType.FullName}.{eventInfo.Name}";
var memberElement = new XElement("member", new XAttribute("name", eventId));
// Agregar summary
memberElement.Add(new XElement("summary",
$"Occurs when {eventInfo.Name} is raised."));
// Agregar remarks con el tipo del handler
memberElement.Add(new XElement("remarks",
$"Event handler type: {GetSimpleTypeName(eventInfo.EventHandlerType)}"));
membersElement.Add(memberElement);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para evento {eventInfo.Name}: {ex.Message}");
}
}
private void GenerateFieldDocumentation(FieldInfo field, XElement membersElement)
{
try
{
string fieldId = $"F:{field.DeclaringType.FullName}.{field.Name}";
var memberElement = new XElement("member", new XAttribute("name", fieldId));
// Modificadores
string modifiers = "";
if (field.IsStatic) modifiers += "static ";
if (field.IsInitOnly) modifiers += "readonly ";
if (field.IsLiteral) modifiers += "constant ";
// Agregar summary
memberElement.Add(new XElement("summary",
$"Represents the {modifiers}{field.Name} field."));
membersElement.Add(memberElement);
}
catch (Exception ex)
{
_logWriter.WriteLine($"Error generando documentación para campo {field.Name}: {ex.Message}");
}
}
// Métodos auxiliares
private string GetMethodXmlId(MethodInfo method)
{
var parameters = method.GetParameters();
if (parameters.Length == 0)
{
return $"M:{method.DeclaringType.FullName}.{method.Name}";
}
var paramTypes = string.Join(",", parameters.Select(p => GetParameterTypeName(p.ParameterType)));
return $"M:{method.DeclaringType.FullName}.{method.Name}({paramTypes})";
}
private string GetParameterTypeName(Type type)
{
// El formato de los parámetros en los IDs XML de documentación es específico
// Arrays
if (type.IsArray)
{
return $"{GetParameterTypeName(type.GetElementType())}[]";
}
// Genéricos
if (type.IsGenericType)
{
var genericTypeDef = type.GetGenericTypeDefinition();
var genericArgs = string.Join(",", type.GetGenericArguments().Select(GetParameterTypeName));
// Para tipos como List<T>, devolver algo como System.Collections.Generic.List{T}
if (genericTypeDef == typeof(System.Collections.Generic.List<>))
{
return $"System.Collections.Generic.List{{{genericArgs}}}";
}
// Para otros tipos genéricos, usar el nombre completo con {}
return $"{genericTypeDef.FullName.Split('`')[0]}{{{genericArgs}}}";
}
// Para tipos normales, usar el nombre completo
return type.FullName;
}
private string GetTypeKindDescription(Type type)
{
if (type.IsEnum) return "enumeration";
if (type.IsInterface) return "interface";
if (type.IsValueType && !type.IsPrimitive) return "structure";
if (type.IsClass)
{
if (type.IsAbstract && type.IsSealed) return "static class";
if (type.IsAbstract) return "abstract class";
return "class";
}
return "type";
}
private string GetMethodDescription(MethodInfo method)
{
// Crear una descripción básica basada en el nombre y parámetros
var parameters = method.GetParameters();
if (parameters.Length == 0)
{
if (method.ReturnType == typeof(void))
{
return "performs an operation.";
}
return $"gets a {GetSimpleTypeName(method.ReturnType)} value.";
}
return $"with {parameters.Length} parameter{(parameters.Length > 1 ? "s" : "")}.";
}
private string GetParameterDescription(ParameterInfo param)
{
string desc = $"A {GetSimpleTypeName(param.ParameterType)}";
if (param.IsOptional)
{
object defaultValue = param.DefaultValue;
string defaultValueStr = defaultValue?.ToString() ?? "null";
if (param.ParameterType == typeof(string) && defaultValue != null)
{
defaultValueStr = $"\"{defaultValueStr}\"";
}
desc += $" (Optional, defaults to {defaultValueStr})";
}
if (param.IsOut)
{
desc += " (Output)";
}
else if (param.ParameterType.IsByRef)
{
desc += " (Reference)";
}
return desc;
}
private string GetSimpleTypeName(Type type)
{
if (type == null) return "void";
// Si es un tipo por referencia (como out o ref parámetros)
if (type.IsByRef)
{
return GetSimpleTypeName(type.GetElementType());
}
// 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(decimal)) return "decimal";
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;
}
}
}