https://docs.unity3d.com/Manual/roslyn-analyzers.html - Если внимательно прочитать документацию по ссылке, то там даются ссылки на документацию Microsoft, где рассказывается, как написать свой и кодогенератор и анализатор кода (это отдельные си-шарп проекты, которые закидываются в Unity-проект как DLL) https://www.nuget.org/packages/Microsoft.CodeAnalysis/#dependencies-body-tab - Это Roslyn на Nuget. все его зависимости в одном месте. Что нашёл на форумах: - [How to add Roslyn code-analyzers to Unity project? (for Unity versions below 2020)](https://stackoverflow.com/questions/66901265/how-to-add-roslyn-code-analyzers-to-unity-project-for-unity-versions-below-202) - [Is there a way to use Roslyn in Unity?](https://stackoverflow.com/questions/77480239/is-there-a-way-to-use-roslyn-in-unity) - [[Solved] Can I use Microsoft.CodeAnalysis.CSharp for Editor script?](https://discussions.unity.com/t/solved-can-i-use-microsoft-codeanalysis-csharp-for-editor-script/743996) ### Способ 1. Написать свой анализатор кода (отдельный проект) Первоначально я не обратил внимание, что кодогенератор и анализатор кода - это два разных подхода в работе с Roslyn. Поэтому, читая раздел документации "[Create and use a source generator](https://docs.unity3d.com/Manual/create-source-generator.html)", обращаем внимание, что речь именно про кодогенератор, а не про анализатор кода. Проблема, которую я вижу, это то, что Unity поддерживает Roslyn только определённой версии, соотвественно, нужно писать свой анализатор кода под определённую версию Roslyn. Та версия, что поддерживается в Unity, она 2022 года на дворе уже 22.10.24, API могло измениться. Что усложняет самостоятельное погружение в эту тему. В IDE при создании проекта, можно создать Roslyn-проект, будет создан шаблон. Не стал дальше двигаться в эту сторону ### Способ 2. Написать просто код с Roslyn и добавить код в редактор Unity Я пробовал написать код на Roslyn, который будет запускаться в Unity Editor. Поставил через NugetForUnity пакет [Microsoft.CodeAnalysis](https://www.nuget.org/packages/Microsoft.CodeAnalysis/#dependencies-body-tab). Там очень много пакетов-зависимостей. Нужно для каждого в dll отключить все платформы, кроме Editor. Руками такое делать - себя не уважать. [[В один клик отметить во всех DLL в Unity только одну платформу|Попросил]] GPT сгенерировать код, который автоматизирует это. Также ChatGPT-4o сгенерировал для меня код, который через Roslyn анализирует код, чтобы запускать его в редакторе Unity. Добавил сгененрированный код в проект, IDE поругался только в одном участке кода. Там был вот такой кусок кода, в котором `MSBuildWorkspace` был неизвестен IDE. Оказывается, для него нужно ставить отдельный пакет [Microsoft.CodeAnalysis.MSBuild](https://www.nuget.org/packages/Microsoft.CodeAnalysis.Workspaces.MSBuild/). А этот пакет не поддерживает `.NETStandard`. То есть его нельзя ставить в Unity-проект. тот самый участок кода, на который ругается IDE. ```csharp // Указываем путь к вашему .sln файлу или к .csproj string solutionPath = @"path\to\your\solution.sln"; using (var workspace = MSBuildWorkspace.Create()) { // Загружаем решение var solution = await workspace.OpenSolutionAsync(solutionPath); ``` по идее текущий способ должен работать, но без использования `MSBuildWorkspace`: - [[Как использовать Roslyn в Unity]]. - [[Как выполнять свой код при каждом изменении проекта в Unity]] В общем и целом, решил в эту сторону не идти, а просто через свой кодогенератор (что работает через [[T4 (Text Template Transformation Toolkit)|T4]]) генерировать то, что нужно, вместо того, чтобы искать косяки разработчика и указывать то, что он не сделал, хотя это можно было бы просто автоматизировать. Код целиком, что был сгенерирован для вставки в Unity Editor для анализа кода. (код не запускался) ```csharp using System; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using UnityEditor; using UnityEngine; namespace AluEngineUnity { public class CustomAssetPostprocessor : AssetPostprocessor { // Этот метод вызывается каждый раз при импорте, добавлении или изменении ассетов static async void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { // Указываем путь к вашему .sln файлу или к .csproj string solutionPath = @"path\to\your\solution.sln"; using (var workspace = MSBuildWorkspace.Create()) { // Загружаем решение var solution = await workspace.OpenSolutionAsync(solutionPath); // Перебираем все проекты решения foreach (var project in solution.Projects) { // Перебираем все документы проекта (файлы .cs) foreach (var document in project.Documents) { // Получаем синтаксическое дерево и семантическую модель для документа var syntaxTree = await document.GetSyntaxTreeAsync(); var semanticModel = await document.GetSemanticModelAsync(); if (syntaxTree == null || semanticModel == null) continue; // Анализируем содержимое документа AnalyzeDocument(syntaxTree, semanticModel); } } } } static void AnalyzeDocument(SyntaxTree syntaxTree, SemanticModel semanticModel) { // var root = syntaxTree.GetCompilationUnitRoot(); var root = syntaxTree.GetRoot(); // Находим базовый класс AluContextElement var baseClass = root.DescendantNodes() .OfType<ClassDeclarationSyntax>() .FirstOrDefault(c => c.Identifier.Text == "AluContextElement"); if (baseClass == null) return; // Класс AluContextElement не найден // Находим всех наследников класса AluContextElement var derivedClasses = root.DescendantNodes() .OfType<ClassDeclarationSyntax>() .Where(c => c.BaseList != null && c.BaseList.Types.Any(b => semanticModel.GetSymbolInfo(b.Type).Symbol?.Name == "AluContextElement")) .ToList(); // Проверяем каждый класс-наследник foreach (var derivedClass in derivedClasses) { var className = derivedClass.Identifier.Text; Console.WriteLine(quot;Проверка класса: {className}"); // Получаем все поля класса var fields = derivedClass.Members.OfType<FieldDeclarationSyntax>().ToList(); // Ищем метод AluContextElementClear var clearMethod = derivedClass.Members .OfType<MethodDeclarationSyntax>() .FirstOrDefault(m => m.Identifier.Text == "AluContextElementClear"); if (clearMethod == null) { Console.WriteLine(quot;Метод AluContextElementClear не найден в классе {className}."); continue; } // Проверка использования полей в методе AluContextElementClear foreach (var field in fields) { var fieldName = field.Declaration.Variables.First().Identifier.Text; // Проверяем, используется ли поле в методе AluContextElementClear var isFieldUsed = clearMethod.DescendantNodes() .OfType<IdentifierNameSyntax>() .Any(i => i.Identifier.Text == fieldName); if (!isFieldUsed) { throw new Exception(quot;Поле '{fieldName}' не используется в методе AluContextElementClear класса {className}."); } } Console.WriteLine(quot;Класс {className} прошел проверку."); } } } } ```