Roslyn Analyzerでコンフィグを読み込ませて挙動を変更する

方法。が、欲すぃ。例えば採番する時に0ベースなのか1ベースなのかプロジェクトによって変えたい。例えばCodeFix時の名前変更のルールを先頭アンスコ付けるのか付けないのかを自由に変えさせたい。NotifyPropertyChangedGenerator - RoslynによるVS2015時代の変更通知プロパティの書き方の時は、専用のAttributeを使うので、その中のインターフェイスを書き換えてコンフィグ代わりにしてね、という方法を取ったのですが、専用の属性が使えなきゃ適用できない手法で、全然汎用的っぽくないし、当然ながら全然イケてない。

ではどうするか。実は、Additional Filesという仕組みが用意されているので、それを用いることでコンフィグを読みこませることができます!詳細はroslyn/Using Additional Files.mdに書かれていますが、任意のテキストファイルを色々な手法(コマンドラインの引数やcsprojの定義としてなど)でプロジェクトに設定し、Analyzer側では AnalyzerOptions.AdditionalFiles で読めます。

では、やってみましょう。

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AdditionalFileAnalyzer : DiagnosticAnalyzer
{
    static DiagnosticDescriptor Rule = new DiagnosticDescriptor("AdditionalFileAnalyzer", "AdditionalFileテスト", "AdditionalFiles:{0}", "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true);
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSymbolAction(Analyze, SymbolKind.NamedType);
    }

    private static void Analyze(SymbolAnalysisContext context)
    {
        // AnalyzerOptionsにAdditionalFilesがある。
        // CodeFixContextの場合はProjectに生えてるので、 context.Document.Project.AnalyzerOptions で取得可能。
        var additionalFiles = context.Options.AdditionalFiles;

        // Pathから引っ掛けて取る
        var config = additionalFiles.FirstOrDefault(x => System.IO.Path.GetFileName(x.Path) == "config.json");
        if (config != null)
        {
            // GetText().ToString()で文字列が取れるので、あとはJsonConvertでデシリアライズするなりなんなりどうぞご自由に……。
            var text = config.GetText().ToString();
            context.ReportDiagnostic(Diagnostic.Create(Rule, context.Symbol.Locations[0], text));
        }
        else
        {
            context.ReportDiagnostic(Diagnostic.Create(Rule, context.Symbol.Locations[0], "JSONが見つかってないぞ"));
        }
    }
}

これでJSONが見つかってないぞ、と怒られまくります。ファイルを追加するには、まずプロジェクトの適当なところにconfig.jsonを足して、Build Actionを AdditionalFiles に変更します。が、変更しようとするとやっぱり怒られるので、しょうがないからcsprojを手動で書き換えます。

<ItemGroup>
    <AdditionalFiles Include="config.json" />
</ItemGroup>

これで、以下の様な結果が得られます。

image

うん、ちゃんと読めてる!

コンフィグとして使うには、まぁイマドキのJSONでコンフィグだったらJSON.NETでJsonConvert.DeserializeObjectなんかでサクッと復元してやるのが楽でしょう。外部DLL読み込むのが嫌だという場合は、XMLにしてLINQ to XMLやXmlSerializer使うのも全然お手軽でいいとは思います。

コンフィグなんてほぼほぼ固定になるのに毎回デシリアライズ走らせるのは嫌だなあ、って場合はstaticオブジェクトにキャッシュしちゃうのも悪くない手だと思います。その場合は、書き換えた場合はプロジェクト再読み込み(or VS再起動)になってしまいますが、実用的には全然問題ないはず。といいたいのですが、複数のプロジェクト跨ぎで共有されちゃうと厄介だったりするので注意が必要なので、まぁちょっとDeserializeするぐらい大したことねーよということで、毎度Deserializeするのも全然構わないかな、とは。

このコンフィグを読み込む手法はStyleCopAnalyzer/Configuration.mdでも採用されているし、これがスタンダードのやり方だと思って良さそうです。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive