189 lines
9.5 KiB
C#
189 lines
9.5 KiB
C#
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<CodeChunk> ChunkCSharpFile(string filePath, string fileContent)
|
||
{
|
||
var chunks = new List<CodeChunk>();
|
||
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<MethodDeclarationSyntax>();
|
||
foreach (var method in methods)
|
||
{
|
||
var chunk = new CodeChunk
|
||
{
|
||
FilePath = filePath,
|
||
MethodName = method.Identifier.Text,
|
||
Content = method.ToFullString().Trim()
|
||
};
|
||
|
||
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
||
|
||
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<EnumDeclarationSyntax>();
|
||
foreach (var enumSyntax in enums)
|
||
{
|
||
chunks.Add(new CodeChunk
|
||
{
|
||
FilePath = filePath,
|
||
MethodName = enumSyntax.Identifier.Text, // Сохраняем имя енама
|
||
Content = enumSyntax.ToFullString().Trim()
|
||
});
|
||
}
|
||
|
||
// 3. ДОБАВЛЯЕМ СБОР КЛАССОВ-МОДЕЛЕЙ (без методов)
|
||
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
|
||
foreach (var classSyntax in classes)
|
||
{
|
||
// Берем только свойства, чтобы понимать структуру модели
|
||
var properties = classSyntax.Members.OfType<PropertyDeclarationSyntax>();
|
||
if (properties.Any())
|
||
{
|
||
chunks.Add(new CodeChunk
|
||
{
|
||
FilePath = filePath,
|
||
MethodName = classSyntax.Identifier.Text,
|
||
// Сохраняем объявление класса и его свойства
|
||
Content = $"class {classSyntax.Identifier.Text} {{\n" +
|
||
string.Join("\n", properties.Select(p => p.ToFullString().Trim())) +
|
||
"\n}"
|
||
});
|
||
}
|
||
}
|
||
|
||
return chunks;
|
||
}
|
||
|
||
public async Task<List<CodeChunk>> ParseProjectAsync(string csprojPath)
|
||
{
|
||
// 1. Создаем воркспейс и загружаем проект (это может занять пару секунд)
|
||
using var workspace = MSBuildWorkspace.Create();
|
||
Console.WriteLine("Загружаем проект и строим семантическую модель...");
|
||
var project = await workspace.OpenProjectAsync(csprojPath);
|
||
|
||
var chunksList = new List<CodeChunk>();
|
||
|
||
// 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<MethodDeclarationSyntax>();
|
||
|
||
foreach (var method in methods)
|
||
{
|
||
var chunk = new CodeChunk
|
||
{
|
||
MethodName = method.Identifier.Text,
|
||
Content = method.ToFullString(),
|
||
FilePath = document.FilePath
|
||
};
|
||
|
||
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
||
|
||
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.MethodName} -> {methodSymbol.Name} ({namespaceName})");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 1. Ищем вообще ВСЕ слова (идентификаторы) внутри метода
|
||
var identifiers = method.DescendantNodes().OfType<IdentifierNameSyntax>();
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|