using CodeBase.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.MSBuild; namespace CodeBase.Services { public class ChunkService(VectorizationService vectorizationService) { public List ChunkCSharpFile(string filePath, string fileContent) { var chunks = new List(); var syntaxTree = CSharpSyntaxTree.ParseText(fileContent); var root = syntaxTree.GetRoot(); var compilation = CSharpCompilation.Create("MyAnalysis") .AddSyntaxTrees(syntaxTree) // Подкидываем базовые библиотеки .NET, чтобы он узнал System.Linq и прочее .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)); var semanticModel = compilation.GetSemanticModel(syntaxTree); // 1. Собираем методы (как и раньше) var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { var chunk = new CodeChunk { FilePath = filePath, EntityName = method.Identifier.Text, Content = method.ToFullString().Trim() }; var invocations = method.DescendantNodes().OfType(); foreach (var invocation in invocations) { var symbolInfo = semanticModel.GetSymbolInfo(invocation); if (symbolInfo.Symbol is IMethodSymbol methodSymbol) { // Получаем полный путь пространства имен (например, "System.Linq") string namespaceName = methodSymbol.ContainingNamespace.ToString(); // Пропускаем все системные вызовы .NET if (namespaceName.StartsWith("System") || namespaceName.StartsWith("Microsoft")) { continue; } // Если это наш метод (например, "Bas.Core.Services"), добавляем его chunk.OutgoingCalls.Add(methodSymbol.Name); Console.WriteLine($"[ПАРСЕР] Добавлена бизнес-связь -> {methodSymbol.Name}"); } } chunk.OutgoingCalls = chunk.OutgoingCalls.Distinct().ToList(); chunks.Add(chunk); } // 2. ДОБАВЛЯЕМ СБОР ENUM (Перечислений) var enums = root.DescendantNodes().OfType(); foreach (var enumSyntax in enums) { chunks.Add(new CodeChunk { FilePath = filePath, EntityName = enumSyntax.Identifier.Text, // Сохраняем имя енама Content = enumSyntax.ToFullString().Trim() }); } // 3. ДОБАВЛЯЕМ СБОР КЛАССОВ-МОДЕЛЕЙ (без методов) var classes = root.DescendantNodes().OfType(); foreach (var classSyntax in classes) { // Берем только свойства, чтобы понимать структуру модели var properties = classSyntax.Members.OfType(); if (properties.Any()) { chunks.Add(new CodeChunk { FilePath = filePath, EntityName = classSyntax.Identifier.Text, // Сохраняем объявление класса и его свойства Content = $"class {classSyntax.Identifier.Text} {{\n" + string.Join("\n", properties.Select(p => p.ToFullString().Trim())) + "\n}" }); } } return chunks; } public async Task> ParseProjectAsync(string csprojPath) { // 1. Создаем воркспейс и загружаем проект (это может занять пару секунд) using var workspace = MSBuildWorkspace.Create(); Console.WriteLine("Загружаем проект и строим семантическую модель..."); var project = await workspace.OpenProjectAsync(csprojPath); var chunksList = new List(); // 2. Проходимся по всем C#-файлам в проекте foreach (var document in project.Documents) { // Теперь у нас есть ГАРАНТИРОВАННАЯ семантическая модель для каждого файла var semanticModel = await document.GetSemanticModelAsync(); var syntaxTree = await document.GetSyntaxTreeAsync(); var root = await syntaxTree.GetRootAsync(); var methods = root.DescendantNodes().OfType(); foreach (var method in methods) { var chunk = new CodeChunk { EntityName = method.Identifier.Text, Content = method.ToFullString(), FilePath = document.FilePath }; var invocations = method.DescendantNodes().OfType(); foreach (var invocation in invocations) { // 3. Просим у модели 100% точную информацию о вызываемом методе var symbolInfo = semanticModel.GetSymbolInfo(invocation); if (symbolInfo.Symbol is IMethodSymbol methodSymbol) { // Получаем пространство имен метода (например, "System.Linq" или "CodeBase.Services") string namespaceName = methodSymbol.ContainingNamespace?.ToString() ?? ""; // Отсеиваем только системные вызовы, оставляя ВЕСЬ бизнес-код if (!namespaceName.StartsWith("System") && !namespaceName.StartsWith("Microsoft")) { chunk.OutgoingCalls.Add(methodSymbol.Name); Console.WriteLine($"[ПАРСЕР] Успех: {chunk.EntityName} -> {methodSymbol.Name} ({namespaceName})"); } } } // 1. Ищем вообще ВСЕ слова (идентификаторы) внутри метода var identifiers = method.DescendantNodes().OfType(); foreach (var identifier in identifiers) { // 2. Спрашиваем у семантической модели: "Что это за слово?" var symbolInfo = semanticModel.GetSymbolInfo(identifier); // 3. Если это тип данных (класс, структура, интерфейс или ЕНУМ) if (symbolInfo.Symbol is INamedTypeSymbol typeSymbol) { string namespaceName = typeSymbol.ContainingNamespace?.ToString() ?? ""; // Отсеиваем системные типы (string, int, List и т.д.) if (!namespaceName.StartsWith("System") && !namespaceName.StartsWith("Microsoft")) { // Проверяем, что это именно то, что нам нужно if (typeSymbol.TypeKind == TypeKind.Enum) { chunk.OutgoingCalls.Add(typeSymbol.Name); Console.WriteLine($"[ПАРСЕР] Нашли использование енума -> {typeSymbol.Name}"); } else if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Interface) { chunk.OutgoingCalls.Add(typeSymbol.Name); Console.WriteLine($"[ПАРСЕР] Нашли использование класса/интерфейса -> {typeSymbol.Name}"); } } } } // Убираем дубликаты chunk.OutgoingCalls = chunk.OutgoingCalls.Distinct().ToList(); chunksList.Add(chunk); } } return chunksList; } } }