343 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |