ノートの端の書き残し

UnityやらC#やら。設計が得意かもしれない。

RoslynAnalyzer自作勉強 -特定の戻り値を許さないアナライザ-

やること

RoslynAnalyzerの使い方はだいたいわかったので、簡単なものから自作できるようにします。

BannedApiAnalyzersという、特定のAPIを禁止にするめっちゃ便利なアナライザがあります。

github.com

例えばUniTaskを採用しているUnityプロジェクトではasyncメソッドの戻り値をあえてTaskにする必要はほぼ無いかと思います。なのでasyncメソッドの戻り値をTaskにするのを禁止にしたいのですが、BannedApiAnalyzersの設定にはメソッドの戻り値を禁止するための項目はありません。

というわけで、作ります。

参考

neue.cc

基本的なアナライザの作り方は完全にここに追従します。今回はアナライザの一番詳細の部分、解析対象と解析条件をアレンジします。

全文

参考にした記事からコピペして問題の部分を書き換えました。

#pragma warning disable RS2008

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SuperSimpleAnalyzer : DiagnosticAnalyzer
{
    // どうせローカライズなんてしないのでString直書きしてやりましょう
    private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
        id: "TaskHate001",
        title: "asyncメソッドにおけるTask戻り値を許さない会",
        messageFormat: "asyncメソッドの戻り値にはUniTaskを使用してください",
        category: "Performance",
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true,
        description: "Nanika suru.");

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

    public override void Initialize(AnalysisContext context)
    {
        // お約束。
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
        
        // メソッド宣言時に解析を行う
        context.RegisterSymbolAction(AnalyzeTaskReturn, SymbolKind.Method);
    }

    private static void AnalyzeTaskReturn(SymbolAnalysisContext context)
    {
        var methodSymbol = (IMethodSymbol)context.Symbol;

        var isAsync = methodSymbol.IsAsync;
        var returnType = methodSymbol.ReturnType;

        // async かどうか、を知るプロパティも、型を知るプロパティもあってすぐ書けた!
        if (isAsync && returnType.Name == nameof(System.Threading.Tasks.Task))
        {
            var diagnostic = Diagnostic.Create(Rule, methodSymbol.Locations[0], methodSymbol.Name);
            context.ReportDiagnostic(diagnostic);
        }
    }
}

結果

(無秩序に色々試すためのプロジェクトに書いたので変なクラス名ですが気にしないで……)

asyncメソッドの戻り値がTaskの場合、ちゃんと警告を出してくれるようになりました!
ジェネリック型の場合は別に書かないといけないなぁと思っていたんですが、型の名前の判定だけだったのが功を奏したのか非ジェネリック型もジェネリック型も両方警告してくれました。
また、Task以外の型だとちゃんと合格して警告は出さないでくれています。

その他参考にしたページやプラグイン

構文木について

そもそも構文木の中身ってどうなってるのか、がわからないのがネックでした。が、我らがSharpLab様に構文木確認機能がちゃんとあるんですね。ここでイメージをつかむことができました。というかこういうの見ないと無理だと思います。普段目にするものじゃないし。

sharplab.io

また、私はコードエディタにRiderを使用しているのですが、以下のような構文木の詳細を確認できる便利なプラグインが公開されており、使用できる環境ならこちらもオススメします。

plugins.jetbrains.com

結論

簡単なRoslynAnalyzerならすぐ書けるし使えるので、みんな書いて公表していきましょう!
資料少なすぎて辛い!