このメモに記載していること
- Roslyn(Githubへのリンク) そのものに関する説明は記載しない。
- 詳しい説明については、よくまとまっている記事があるのでそちらにお任せ。
- 「Visual Studio 2015のライブコード分析を追加してみて、その過程でわかったことをメモとして残している」程度の記事
- メモとして残す理由は、Roslyn の日本語記事が少ないと感じたため。
-
Diagnositc(ソースコードの分析) と Code Fix(適切な記述への変換) を実現できるが、このメモで取り扱うのは Diagnositc についてのみ。
- 内容は軽め。よくわからなくて二の足を踏んでる人の背中を押せればいいかなーくらいの感覚。
- 応用編まで記載すると冗長になるのでまた別の記事で。というか現状ネタが少ない。
- Code Fixはまだ試せていない。実装でき次第、記事にする。
ソースコード分析を実際に追加する
準備
VisualStudio2015でSDKが必要なのでインストールしておく。SDKをインストールするといくつかのプロジェクトテンプレートが追加される。
- SDK
- 追加されるプロジェクトテンプレート
- Analyzer with Code Fix (Nuget + VSIX)
- Code Refactoring (VSIX)
- Stand-Alone Code Analysis Tool
今回はライブコード分析を追加するので、追加されたプロジェクトテンプレートのうち、 Analyzer with Code Fix (Nuget + VSIX) を利用してプロジェクトを作成する。
それぞれのプロジェクトテンプレートについては、SDKの詳細ページに記載されているのでそちらを参照していただきたい。
大まかな処理の流れ
実装を進める前に、ライブコード分析機能を実装するための大まかな処理の流れを把握しておく。
大体以下のような感じ。
- 分析の対象とする構文を登録する。
- その構文に対して分析処理をする。
- 必要に応じて分析処理の結果を表示する。
実装の詳細
DiagnosticAnalyzerクラスを継承し、クラスのメソッドを活用して一連の処理を実装する。
以下にシンプルな例を示す。1,2,3の処理にあたる部分をコメントで追記しておいた。
// DiagnosticAnalyzerクラスを継承する
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SampleAnalyzer : DiagnosticAnalyzer
{
/* 下準備にあたる部分 */
// 分析で問題があると判断された箇所に表示するメッセージを定義する。
public const string DiagnosticId = "任意のIDを割振る。";
private static readonly LocalizableString Title = "タイトルを入れる"
private static readonly LocalizableString MessageFormat = "表示するメッセージのフォーマットを定義する。'{0}'で引数も受け取れる。"
private static readonly LocalizableString Description = "説明"
private const string Category = "分析内容のカテゴリ";
// 上で定義した諸々をベースに DiagnosticDescriptor を作成する。
// ソースコードに問題が見つかった場合に表示するメッセージ形式を定義している。
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
/* 「1. 分析の対象とする構文を登録する。」 に該当する部分 */
// SymbolKindの部分がポイント。ここで分析対象を指定している。NamedTypeは、クラス名やメソッド名などの名前。
// 第一引数に分析の処理を指定する。
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
// Tips: 分析対象はSyntaxKindでも指定可能。
// その場合、RegisterSyntaxNodeActionメソッドを呼び出す。
// context.RegisterSyntaxNodeAction(AnalyzeBlock, SyntaxKind.XXX);
}
/* 「2.その構文に対して分析処理をする。」 に該当する部分 */
// context.RegisterSymbolAction の第一引数で指定されているメソッド
private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
// contextからソースコードの情報を取り出して、任意の分析処理を実行する。
var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower))
{
/* 「3. 必要に応じて分析処理の結果を表示する。」 に該当する部分 */
var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
AnalyzeSymbol内の実装を工夫すれば、様々なライブコード分析が追加可能となる。
上記サンプル内で、Registerのprefixを持つメソッドを2つ紹介しているが、Symbol
とSyntaxNode
のどちらを登録したかで、分析処理を担当するメソッドの SymbolAnalysisContext context
で取得できる内容が変化する。分析したい内容に応じて、Symbol
とSyntaxNode
を使い分けることになる。ちなみに、取得できる内容の確認は、デバッグ実行(方法は後述)、もしくは、テストで地道にに調べるという泥臭い方法で行っておりました・・・効率良い方法があればぜひぜひ教えていただきたいです。
実装のポイント
把握しておくと、より理解しやすくなる要素を、メモがてら記載しておく。
以下の内容がふんわりわかっていれば、なんとなく実装を始められるかと思う。
列挙型 | 役割 |
---|---|
SymbolKind SyntaxKind |
分析対象として指定可能な対象が一覧で定義されているものだと思えば良い。 それぞれ定義にジャンプすれば詳細を確認できるため、ここでは細かく触れない。 |
DiagnosticSeverity | メッセージを表示するときのレベルが定義されている。 Hidden, Info, Warning, Errorの4つのレベルが定義されている。内容で警告レベルを使い分けられるようになっている。ErrorかWarningの場合、該当箇所にメッセージが表示される。 |
メソッド名 | 役割 |
---|---|
RegisterSymbolAction RegisterSyntaxNodeAction |
SymbolKindもしくSyntaxKindを登録するためのメソッド。一度に複数のSymbolKindもしくSyntaxKindを指定することもできる。 |
Diagnostic.Create | 表示用メッセージ情報を作成するのに利用する。 第一引数にDiagnosticDescriptor,第二引数にメッセージを表示するソースコードの位置情報、第三引数にDiagnosticDescriptorのパラメータを指定して呼び出す。 |
ReportDiagnostic | Diagnostic.Createで作成したメッセージ情報を表示する。 |
動作確認
デバッグ実行をすると、新たに別のVisualStudioが立ち上がるので、そのVisualStudio上でコードを記述することでライブコード分析が動作していることを確認できる。即座に自分の書いた分析コードが動作しているのを見てニヤニヤできる。