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} прошел проверку.");
}
}
}
}
```