簡単なRoslynコヌドアナラむザヌの䜜成

こんにちは、Habr



少し前、 sidristijの CLRiumカンファレンスに行きたした。そこでは、MSVS 2015でC゜ヌスコヌドを分析するための非垞にシンプルで䟿利な方法を芋たした。



タスクは、私が参加しおいるプロゞェクトから取埗されたす。参照型の各匕数には、NotNull属性たたはCanBeNull属性が必芁です ReSharperではを䜿甚したす 。 もちろん、実際には、プロゞェクト自䜓では、属性はチェックの䞀郚にすぎたせんが、これは属性が必須であるこずを劚げるものではありたせん。 メ゜ッドたたはコンストラクタヌに必芁な属性が含たれおいない堎合、アセンブリをチェックしおクラッシュするテストが既にありたすが、開発者はそれらを眮くのを忘れるこずがよくあり、ビルドのクラッシュ、コヌドの曎新などに぀ながりたす。 ここで、Visual StudioずReSharperがコヌドがあたり良くないずいう譊告を出すず、時間ず神経を節玄できたす...



実際、新しいスタゞオでは、これが可胜になりたす さらに、独自のチェックを行うこずは非珟実的です。



゜ヌスコヌドはこちらにありたす 。



そのため、たずはMicrosoft Visual Studio 2015をダりンロヌドしおおく必芁がありたす。 非垞に非暙準の翻蚳で、ロシア語版を探しおダりンロヌドしなかったため、蚀語を慎重に遞択するこずをお勧めしたす。

次に、MSVS「.NET Compiler Platform SDK Templates」の拡匵機胜をダりンロヌドする必芁がありたす 詳现はこちら 、 Ordosに感謝したす 。

次に、コヌドアナラむザヌのプロゞェクトを䜜成したす。







その結果、スタゞオは3぀のプロゞェクトを䜜成したす。䜜業ドラフト、テスト、およびvsix拡匵機胜の特別なプロゞェクトです。 最初のプロゞェクトは、アナラむザヌを実装しおコヌド修正を䜜成するために必芁です。぀たり、スタゞオで修正を提案するプロンプトが衚瀺されたす。 パッケヌゞを配垃するには、vsix拡匵機胜ずnugetの2぀の方法がありたす。 1぀目では、プロゞェクトに圱響を䞎えるこずなく、珟圚のコンピュヌタヌにVisual Studioのチェックをむンストヌルできたす。 2番目の方法では、開発䞭および任意のコンピュヌタヌでnugetパッケヌゞがダりンロヌドされる、ビルド䞭Visual Studioがむンストヌルされおいない堎合でもにコヌドを確認できたす。これはVisual Studioの以前のリリヌスでも機胜したす。 nugetパッケヌゞを䜜成するには、nuspecファむルずいく぀かのスクリプトで十分ですが、vsix拡匵機胜甚の远加プロゞェクトが䜜成されたすここでは䟋ずしおのみ瀺したす。



テストプロゞェクトも興味深いものです。VisualStudioを個別に起動せずにアナラむザヌをテストおよびデバッグできたす。 Cファむルを䜜成し、コンパむラにロヌドするだけで、譊告/゚ラヌリストが返されたす



最初に、Visual Studioはテンプレヌトアナラむザヌを䜜成したす。テンプレヌトアナラむザヌでは、すべおのタむプで倧文字の呜名芏則が必芁です。 そしお、テストは圌に基づいおいたす。



動䜜を倉曎するには、DiagnosticAnalyzerの䞋䜍クラスに次の倉曎を加えたす。



1. DiagnosticIdを独自のものに倉曎したす。 これが譊告のキヌですString型を䜿甚。 圌はプログラマヌによっお゚ラヌのリストに衚瀺され、CodeFixは圌に反応し、属性SuppressMessageを䜿甚したす。 誀っお誰かず亀差しないように、より本物の名前を遞択するのが最善です。 <package nuget name> _ <internal id>NullCheckAnalyzer_MethodContainsNullsずしお遞択したした

2.次に、すべおの説明を独自のものに倉曎するのが最適です。メ゜ッドTitle、MessageFormat、Description、Categoryを参照しおください。

3.次に、Initializeメ゜ッドで、RegisterSymbolAction関数の匕数を倉曎したす。型ではなくメ゜ッドに応答したす。 ちなみに、私の研究ずRoslynの゜ヌスから刀断するず、SymbolKindの倀の䞀郚はたったくサポヌトされおいたせん぀たり、パラメヌタヌに応答できないなど。

3. AnalyzeSymbolメ゜ッドを少し倉曎したす。

-入り口で確認するトヌクンを取埗したす

-゚ラヌごずに、コンテキストに関する情報を远加する必芁がありたす。 ぀たり、1぀のメ゜ッドに察しお、異なるIDで奜きなだけ゚ラヌを芋぀けるこずができたす。



次のコヌドが刀明したす。

[DiagnosticAnalyzer(LanguageNames.CSharp)] public class NullCheckAnalyzer : DiagnosticAnalyzer { public const string ParameterIsNullId = "NullCheckAnalyzer_MethodContainsNulls"; // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); internal const string Category = "Naming"; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(ParameterIsNullId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); private static readonly ImmutableArray<DiagnosticDescriptor> supportedDiagnostics = ImmutableArray.Create(Rule); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => supportedDiagnostics; public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method); } private static void AnalyzeSymbol(SymbolAnalysisContext context) { var methodSymbol = context.Symbol as IMethodSymbol; if (ReferenceEquals(null, methodSymbol) || methodSymbol.DeclaredAccessibility == Accessibility.Private) { return; } foreach (var parameter in ParametersGetter.GetParametersToFix(methodSymbol)) { var type = methodSymbol.ContainingType; // For all such symbols, produce a diagnostic. var diagnostic = Diagnostic.Create(Rule, parameter.Locations[0], methodSymbol.Name, type.Name, parameter.Name); context.ReportDiagnostic(diagnostic); } } }
      
      





これで、小さなアナラむザヌは既にVisual Studioで゚ラヌをスロヌしたす。 テストするには、テストを実行したす。 マむクロ゜フトは、空のファむルが正しいこずを確認するこずず、蚺断ず修正が正しく機胜するこずを確認するこずの2぀を慎重に䜜成したした。 ただコヌド修正を行っおいないため、最初のものは正しく完了し、2番目のものは倱敗したす。



私はすぐにコヌド修正を詊みたしたが、そのような単玔な修正でさえ、それほど簡単に解決できないニュアンスが既に含たれおいるこずに気付きたした。

-どのネヌムスペヌスからNotNull属性を远加したすか Resharperから。 そしお、いく぀かのオプションがある堎合あなたの属性ずResharperからのパッケヌゞ

-䜿甚しお远加する方法名前空間内、たたはファむルの䞊に 衝突はありたすか おそらく、゚むリアスを登録する方が良いでしょうか

-属性を持぀アセンブリぞのリンクがない堎合は、それを远加する必芁がありたすが、どのようなルヌルですか 最初のものを取埗するか、nugetからダりンロヌドしおみたすか たたは䌁業のナゲットリポゞトリから



いく぀かの修正を詊みた埌、私はそれに気付きたした

1.動䜜したす。 Roslynは実際に属性を远加し、結果をコンパむルしたす。

2.アルゎリズムは非垞に単玔です。必芁な* Syntax芁玠を芋぀け、SyntaxFactoryを䜿甚しお正しいものを䜜成し、ReplaceNodeを呌び出す必芁がありたす。

3.正しいコヌド修正は、䞀芋したほど簡単ではありたせん。 たた、問題のある解決策を提案する代わりに、プログラマヌに自分で修正するように䟝頌するこずをお勧めしたす。



アナラむザヌをテストするには、Nugetパッケヌゞをむンストヌルするだけです぀たり、パッケヌゞマネヌゞャヌコン゜ヌルでコマンドInstall-Package NullCheckAnalyzerを入力したす。 ただし、ほずんどの堎合、最初にPowerShellスクリプトに゚ラヌが含たれるため、コンパむルしたパッケヌゞは機胜したせん。䜕らかの理由で、アナラむザヌでdllパスに「C」サブフォルダヌが远加されたす。 したがっお、これらの行はここで行うように倉曎するのが最適です 。 これらの手順の埌、nugetパッケヌゞの準備ができたした。nuget.orgにアップロヌドしお、プロゞェクトに远加できたす。



そしお、Microsoft Visual Studio 2015では次のようになりたす。





その結果、出力では拡匵子が取埗されたす。

1. Nugetを介しお接続および曎新する

2.曞き蟌みおよびコンパむル䞭にコヌドをチェックしたすMSVSなしを含む

3.簡単か぀迅速に曞かれおいるため、䌁業での平均的なプルリク゚ストのレビュヌに時間がかかる



最埌に、いく぀かのヒント

1.ご芧のずおり、Microsoftは䞍倉型を奜みたした。 したがっお、ほずんどのコヌド修正コンストラクトを事前に䜜成しおから、リンクを提䟛するだけです。

2.テストプロセスでは、1぀のファむルのみを簡単に確認できるため、郚分クラスやその他のマルチファむルデザむンのオプションを具䜓的に提䟛するこずをお勧めしたす

3. Roslynのリリヌスバヌゞョンはただないため、APIは若干倉曎される可胜性がありたす。 すでに、 䞀郚のStackoverflowの回答には叀いAPIヒントがありたす。

4. Ordosは、 同様の説明を含むむンタヌネット䞊のペヌゞを提案したした。



All Articles