🛝

Unity向けRoslynAnalyzerをRiderで開発する

2024/05/04に公開

Unity向けRoslynAnalyzerをRiderで開発する

WindowsとMac環境で作動するRoslynAnalyzerをRiderを使用して構築します。
かつてはVisualStudio/Windowsの特権だった気がしますが、いつのまにかRiderにテンプレートが追加されていたので、Riderを介してWindowsとMac両方で開発できるAnalyzer環境を構築します。

リポジトリ

今回作成するサンプルはこちらにあります。

環境

Windows 11 22H2
Unity 2022.2.20
Rider 2024.1.1

Mac 14.4.1
Rider 2024.1
Unity 2022.3.22f1

RoslynTemplateを使用してソリューションを作成

ターゲットフレームワークはインストールされている.NetSDK依存です。

作成場所はUnityのプロジェクト下に置きました。
アナライザのリポジトリ分けて置くのも良さそうです。

UnityProjectRoot
├── Assets
└── Analyzer
    └── RoslynAnalyzerSample
        └── RoslynAnalyzerSample.sln

アナライザのプロジェクト設定変更

プロジェクトのPackageReferenceを変更します。

生成時の参照が3.3.44.3.0でした。
このバージョンだとWindows環境でUnityEditor上のコンパイルに反応しなかったので、
VisualStudioのAnalyzer with code fix (.NET Standard)テンプレートを参考にバージョンを下げます。


<!-- このバージョンだとビルド成果物がunityEditor上のコンパイルで反応しない、Riderのコンパイルには反映される -->
<!-- <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
</ItemGroup> -->

<ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.8">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1"/>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.3.1"/>
</ItemGroup>

Templateの内容を最小の内容に変更

最小限のサンプルにするため今回使用しないコードを削除します。
大本のTemplate

RoslynAnalyzerSample
└── RoslynAnalyzerSample.sln
    └── RoslynAnalyzerSample
        └── RoslynAnalyzerSample
            ├── RoslynAnalyzerSample.csproj 
            ├── SampleCodeFixProvider.cs
            ├── SampleSemanticAnalyzer.cs
            ├── SampleSyntaxAnalyzer.cs    
            └── etc...     
        └── RoslynAnalyzerSample.Sample
            ├── RoslynAnalyzerSample.Sample.csproj
            └── etc...    
        └── RoslynAnalyzerSample.Test
            ├── RoslynAnalyzerSample.Test.csproj
            └── etc...  

最小化
Analyzerとそのテスト以外を削除します。

RoslynAnalyzerSample
└── RoslynAnalyzerSample.sln
    └── RoslynAnalyzerSample
        └── RoslynAnalyzerSample
            ├── RoslynAnalyzerSample.csproj
            └── SampleSyntaxAnalyzer.cs
        └── RoslynAnalyzerSample.Test
            ├── RoslynAnalyzerSample.Test.csproj
            └── SampleSyntaxAnalyzerTests.cs    

今回は環境構築が目的なのでSampleというクラス名があればエラーとして扱うことにします。

SampleSyntaxAnalyzer.cs
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace RoslynAnalyzerSample;

//class名がSampleならエラーを吐く
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SampleSyntaxAnalyzer : DiagnosticAnalyzer
{
	private static readonly DiagnosticDescriptor Rule = new("SampleError01", "NamingError", "命名にSampleを使用した", "Naming",
		DiagnosticSeverity.Error, isEnabledByDefault: true, description: "サンプルエラー、名前にSampleを使わないでください");

	public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
		ImmutableArray.Create(Rule);

	public override void Initialize(AnalysisContext context)
	{
		context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
		context.EnableConcurrentExecution();

		context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.ClassDeclaration);
	}

	private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
	{
		if (context.Node is not ClassDeclarationSyntax classDeclarationNode)
			return;

		var classDeclarationIdentifier = classDeclarationNode.Identifier;
		if (classDeclarationIdentifier.Text == "Sample")
		{
			var diagnostic = Diagnostic.Create(Rule,
				classDeclarationIdentifier.GetLocation(),
				classDeclarationIdentifier.Text);

			context.ReportDiagnostic(diagnostic);
		}
	}
}
SampleSyntaxAnalyzerTests.cs
using System.Threading.Tasks;
using Xunit;
using Verifier =
	Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<
		RoslynAnalyzerSample.SampleSyntaxAnalyzer>;

namespace RoslynAnalyzerSample.Tests;

public class SampleSyntaxAnalyzerTests
{
	[Fact]
	public async Task ClassWithMyCompanyTitle_AlertDiagnostic()
	{
		const string text = @"
public class Sample
{
}
";

		var expected = Verifier.Diagnostic()
			.WithLocation(2, 14)
			.WithArguments("Sample");
		await Verifier.VerifyAnalyzerAsync(text, expected).ConfigureAwait(false);
	}
}

リリースビルドを行いUnityにインポートするDLLを用意します。
デフォルトだとこの辺りに出力されます。

RoslynAnalyzerSample\RoslynAnalyzerSample\RoslynAnalyzerSample\bin\Release\netstandard2.0

Unityに導入

公式マニュアルに従い前項で出力したDLLをインポートします。

Plugin Inspector ウィンドウの中で、以下を行います。
Select platforms for plugin の見出しで、Any Platform を無効にします。
Include Platforms の見出しで、Editor と Standalone を無効にします。
Plugin Inspector ウィンドウの Asset Labels の見出しの下にある、青いラベルアイコンをクリックして、Asset Labels サブメニューを表示します。
RoslynAnalyzer という新しいラベルを作成し、割り当てます。

挙動確認

Windows/Macそれぞれの環境で導入を行い、Sampleというclassを定義することでUnityEditor/Rider上でコンパイルエラーが起こせることを確認します。

Windows


Mac


気を付けるポイント

アナライザの挙動に必要な.netの稼働要件をすり合わせておく必要があります。

You must install or update .NET to run this application.

などで実行弾かれます。

次のステップ

公式のサンプルを読んでみる
https://github.com/dotnet/roslyn-sdk/tree/main/samples/CSharp/Analyzers/Analyzers.Implementation

既存のAnalyzerを読んでみる
https://github.com/microsoft/microsoft.unity.analyzers
https://github.com/dotnet/roslyn-analyzers/tree/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers

コード修正を実装する
https://qiita.com/ikuzak/items/2d4286079d1657269071

参考文献

https://neue.cc/2021/05/08_600.html
https://swet.dena.com/entry/2021/05/25/100000#f-06d4f15d
https://speakerdeck.com/dena_tech/unitykai-fa-defalsemisuwowei-ran-nifang-guroslynanaraizafalsesu-me-dena-techcon-2022
https://light11.hatenadiary.com/entry/2022/11/01/193339
https://zenn.dev/naminodarie/articles/32973a36fcbe99

Discussion