事前準備
環境
StyleCopAnalyzers
の開発を行うには、以下のものが必要です。
- C# 8.0 以上
- .NET 5 以上
- VisualStudio 2022 以上
リポジトリ
以下のStyleCopAnalyzers
の公式リポジトリをローカルにクローンし、VisualStudio 2022
でプロジェクトを開きます。
StyleCopAnalyzersのルールについて
StyleCopAnalyzers
には、以下のようなルール既にあります。
Special Rules(特別ルール) (SA0000~)
回避策、構成エラーなどの特別な機能を提供するルール。
Spacing Rules(間隔の規則) (SA1000~)
コード内のキーワードと記号の間隔要件を強制するルール。
Readability Rules(可読性ルール) (SA1100~)
コードが適切にフォーマットされ、読みやすいことを保証するルール。
Ordering Rules(発注ルール) (SA1200~)
コードの内容に標準の順序付けスキームを適用するルール。
Naming Rules(命名規則) (SA1300~)
メンバー、型、および変数の名前付け要件を強制する規則。
Maintainability Rules(保守容易性ルール) (SA1400~)
コードの保守性を向上させるルール。
Layout Rules(レイアウトルール) (SA1500~)
コードのレイアウトと行間を強制するルール。
Documentation Rules(ドキュメンテーションルール) (SA1600~)
コードドキュメントの内容とフォーマットを検証するルール。
Alternative Rules(代替ルール) (SX0000~)
既定の StyleCop の動作に非標準の拡張機能を提供するルール。
開発
今回は、Layout Rules
に「三項演算子をネストしてはならない」と言うルールを追加していこうと思います。
適当なファイル(SA1520UseBracesConsistently.cs
)辺りを複製し、名前をSX1580TernaryOperatorsNotBeNested.cs
に変更します。
クラスを定義している部分を、以下のソース
ベースのコード
新規でルールを作る際は、以下のような状態がベースとなります。
ここに肉付けしていきます。
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class SX1580TernaryOperatorsNotBeNested : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="SX1580TernaryOperatorsNotBeNested"/> analyzer.
/// </summary>
public const string DiagnosticId = "SX1580";
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(LayoutResources.SX1580Title), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(LayoutResources.SX1580MessageFormat), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(LayoutResources.SX1580Description), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.LayoutRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description);
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
}
}
それぞれの変数の説明です。
-
DiagnosticId
- 各ルールを識別するためのIDです。重複不可。
-
Title
- ルールのタイトルです。
- 後述するリソースファイルからメッセージを取得します。
-
MessageFormat
- ルールの本文です。実際にアナライザの指摘部分に表示される部分になります。
- 後述するリソースファイルからメッセージを取得します。
-
Description
- ルールの説明です。
- 後述するリソースファイルからメッセージを取得します。
-
Descriptor
- 上記のIDやメッセージ、ルールカテゴリなどをまとめたものです。
- 基本変更不要です。
-
SupportedDiagnostics
-
Descriptor
をImmutableArray
型に変換して、外から参照出来るようにします。 - 基本変更不要です。
-
処理を追加する
ベースのコードに以下の部分を追加します。
以下では、CheckChildExpression
メソッドを追加し、三項演算子がネストしている事を検知する処理を記載しています。
引数のExpressionSyntax
型の引数childExpression
に、構文やトークンの情報※1が入っている為、それが三項演算子かどうか判定しています。
構文が三項演算子と判定された後に、再度三項演算子と判定されれば、ネストしていると断定することが出来ます。
最後に、Initialize
メソッドで先ほど追加したCheckChildExpression
をコンテキストに登録します。
こうすることで、アナライザー実行時に分析を行ってくれます。
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class SX1580TernaryOperatorsNotBeNested : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="SX1580TernaryOperatorsNotBeNested"/> analyzer.
/// </summary>
public const string DiagnosticId = "SX1580";
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(LayoutResources.SX1580Title), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(LayoutResources.SX1580MessageFormat), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(LayoutResources.SX1580Description), LayoutResources.ResourceManager, typeof(LayoutResources));
private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.LayoutRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description);
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
+ context.RegisterSyntaxNodeAction(ctx => CheckChildExpression(ctx, ((ConditionalExpressionSyntax)ctx.Node)), SyntaxKind.ConditionalExpression);
}
+ private static void CheckChildExpression(SyntaxNodeAnalysisContext context, ExpressionSyntax childExpression)
+ {
+ if (childExpression is ConditionalExpressionSyntax child)
+ {
+ if (child.Parent is ConditionalExpressionSyntax)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, childExpression.GetLocation()));
+ }
+ }
return;
}
}
※1 構文やトークンの情報について
VisualStudioのRoslyn Syntax Visualizer
と言うツールを使用すると、以下のようにコードが木構造で分析された結果を表示することが出来ます。
メッセージの追加
リソースファイルにメッセージを追加します。
以下はコードで表現していますが、基本的にGUIで編集することが多いです。
MessageFormat
では「"if分を{0}回以上ネストする事は出来ません"」のように記述することで、後で値を受けこむことが可能です。
.....
<data name="SX1580Description" xml:space="preserve">
<value>三項演算子をネストすることは出来ません</value>
</data>
<data name="SX1580MessageFormat" xml:space="preserve">
<value>三項演算子をネストすることは出来ません</value>
</data>
<data name="SX1580Title" xml:space="preserve">
<value>三項演算子をネストすることは出来ません</value>
</data>
.....
アナライザーの出力
ターゲットフレームワークの設定
アナライザーの作成が終わったら、アナライザーを動かす為のターゲットフレームワークを指定します。
プロジェクト -> 右クリック -> プロパティ -> アプリケーション -> 全般 -> ターゲットフレームワーク
今回は.NET Framework 4.7.2向けに出力したいので、net472
の様に指定します。
dllファイルの生成
設定が終わったら、プロジェクトを'Relese'でビルドします。
そうすると\StyleCop.Analyzers\bin\Release\net472\
の配下にStyleCop.Analyzers.dll
というファイルが生成されます。
アナライザーの適応
出力が終わったら、適応したいプロジェクトをVisualStudioで開き、
プロジェクト -> 参照 -> アナライザー -> 右クリック -> アナライザーの追加
で、先ほど出力したStyleCop.Analyzers.dll
を参照に加えます。
ここまで来たら、もう少しです。
プロジェクト -> 右クリック -> プロパティ -> コード分析 -> アクティブな規則 -> 構成
でStyleCopの使いたいルールにチェックを入れます。(SA~やSX~がStyleCopのルールになります。)
ルールを保存すると、*.ruleset
をファイルが生成される為、エディタ等で編集することも可能です。
※VisualStudio 2022からは上記の方法は廃止され、EditorConfigによるルール設定に切り替わっています。