504 lines
17 KiB
C#
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;
|
|
}
|
|
}
|
|
} |