ReSharper甚の最もシンプルなプラグむンを䜜成したす

目的Rの最も簡単なプラグむンを䜜成、テスト、デプロむし、カスタムのクむック修正ずコンテキストアクションを含みたす。



蚘事の抂芁

  1. 開発環境のセットアップ
  2. 䟋1最も単玔なスタブ拡匵
  3. プラグむンのむンストヌル
  4. デバッグのヒント
  5. 䟋2RAPIを䜿甚したコヌド倉曎
  6. RAPIを䜿甚したプラグむンの機胜テスト


キャスト

Visual Studio 2015

ReSharper Ultimate 10



キャットの䞋で私が招埅する利害関係者



1.開発環境のセットアップ



コヌドを蚘述するVisual Studioのメむンむンスタンスの䜜業を劚げないように、将来のプラグむン甚に別の「゚コシステム」をすぐに準備するのが最善です。 いわゆるダりンロヌドするこずから始めたす 「チェックビルド」-拡匵蚺断情報を備えたRUltimateのアセンブリ。その他は自然ず同じです。

たた、Visual Studio Experimentalむンスタンスも必芁です。これは、りィンドりの堎所からむンストヌルされた拡匵機胜たでのすべおのナヌザヌ蚭定を含む䞀皮の「プロファむル」ですそうです。 プロファむルは蚭定レベルで正確に盞互に分離されおおり、Studioの実行可胜ファむルはどこにもコピヌされたせん。 VS2015では、CreateExpInstance.exeナヌティリティを䜿甚しおプロファむルを管理できたすが、さらに簡単な方法がありたす。 以前にむンストヌルしたチェックビルドのむンストヌラヌを起動し、オプション-実隓ハむブに移動しお、開発されたプラグむンのむンストヌルに䜿甚される新しいプロファむルの名前を入力したす。巧劙なRがこのプロファむルを䜜成し、そこにむンストヌルしたす。 そうです、Rの独自のバヌゞョンを含む各プロファむルに䞀連の拡匵機胜をむンストヌルするこずができたす。これにより、耇数のバヌゞョンのプラグむンを䞀床にテストできたす。 既に述べたように、プロファむルは互いに独立しおいるため、Visual Studioの䜜業プロファむルは圱響を受けたせん。



新しいプロファむルでStudioを起動するには、次の圢匏のショヌトカットが必芁です。

「X\ Microsoft Visual Studio 14.0 \ Common7 \ IDE \ devenv.exe」/ rootSuffix YourHive /ReSharper.Internal



ここで、YourHiveは以前に䜜成されたプロファむルの名前であり、/ ReSharper.Internalパラメヌタヌは開発モヌドでRを起動し、プラグむン内で生成された䟋倖の通知などの䟿利な機胜を有効にしたす。



2.䟋1単玔なプラグむン拡匵



Rは、クむックフィックス、コンテキストアクション、リファクタリングなど、コヌドを倉曎および生成するためのさたざたな方法を提䟛したす。 Quick-Fixを実装するのが最も簡単であるように思えたので、それから始めたす。 クむックフィックスは、「䜿甚されおいない倉数を削陀」、「コンストラクタヌからプロパティを初期化する」など、Alt + Enterでポップアップするメニュヌに黄色/赀色のアむコンで衚瀺されるRナヌザヌに知られおいるコマンドです。







そのため、スタゞオのメむンむンスタンス実隓的ではないを開き、新しいクラスラむブラリプロゞェクトを䜜成したす。 RSDKをむンストヌルしたす。

むンストヌルパッケヌゞJetBrains.ReSharper.SDK



R、RSDK、および䞋䜍互換性のバヌゞョンではなく、その䞍圚
執筆時点で、JetBrains.ReSharper.SDKの最新リリヌスバヌゞョンはバヌゞョン10.0.20151101.194233で、R10に察応しおいたす 。JetBrainsは補品のメゞャヌバヌゞョンずマむナヌバヌゞョン間の互換性を提䟛しないため 、SDK 9.1でビルドされたプラグむンの動䜜はR9.2では保蚌されたせんなど 以降、SDK 10が䜿甚されたす。これは、R10のみをサポヌトし、R9.2で正しくむンストヌルおよび動䜜できないこずを意味したす。 同時に、SDK 9.2の蚘事で説明されおいるすべおのコヌドを再構築するための障害がなく、それによっおR9.2で動䜜するプラグむンを取埗できたす-怜蚌枈みです。



プロゞェクトに次のクラスを配眮したしょう。

[QuickFix] public class SimpleFix : QuickFixBase { public SimpleFix(NotInitializedLocalVariableError error) { } protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) { return null; } public override string Text => "SimpleFix"; public override bool IsAvailable(IUserDataHolder cache) { return true; } }
      
      





Quick-Fixには、SomeError / SomeWarning型の入力パラメヌタヌを持぀少なくずも1぀の類䌌したコンストラクタヌが必芁であるこずに泚意しおください。 パラメヌタヌのタむプにより、ドロップダりンメニュヌで䜿甚可胜なQuick-Fixコヌドの゚ラヌが決たりたす。 初期化されおいないロヌカル倉数を䜿甚するず、Quick-Fixが利甚可胜になりたす。





RSDKは、JetBrains.ReSharper.Daemon.CSharp.Errors名前空間で䜿甚可胜な数癟の゚ラヌクラスを定矩しおいたす。 コンパむル゚ラヌに察応するクラスには、名前にpostfix Errorがあり、重芁ではないさたざたな改善点-Warning postfixがありたす。 次のセクション党䜓では、Quick-Fixの展開に苊しみたす。



3.プラグむンのむンストヌル



プロゞェクトにもう1぀のクラスを远加したす。

 [ZoneMarker] public class ZoneMarker { }
      
      





ゟヌンは、バヌゞョン9.0で導入された新しいRSDK機胜であり、珟圚も開発䞭です。 含む、ゟヌンの助けを借りお、開発䞭の拡匵機胜がRプラットフォヌムのどの補品に向けられおいるかが瀺されたす。 幞いなこずに、今のずころは、スタブクラスに限定する必芁がありたす。

重芁ZoneMarkerは、以前に䜜成されたSimpleFixクラスず同じ名前空間に存圚する必芁がありたす。


次のニュアンスは、R9+でのプラグむンの配垃ずむンストヌルがNuGetパッケヌゞを介しおのみ行われるこずです。 正しいパッケヌゞを䜜成するには、拡匵子が.nuspecのファむルず次の内容をプロゞェクトに远加したす。

 <?xml version="1.0"?> <package> <metadata> <id>PaperSource.ReSharper.HelloWorld</id> <version>1.0.5</version> <authors>You</authors> <owners>You</owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description> ,  id    !</description> <tags>habrahabr.ru</tags> <dependencies> <dependency id="Wave" version="4.0" /> </dependencies> </metadata> <files> <file src="bin\Debug\PluginV10.dll" target="dotFiles\" /> <file src="bin\Debug\PluginV10.pdb" target="dotFiles\" /> </files> </package>
      
      





重芁なポむント

1. Waveパッケヌゞぞの䟝存が必芁です。 WaveはRプラットフォヌムの新しい配垃モデルであり、Rに加えお、dotPeek、dotTraceなどが含たれたす。

ReSharper 9.0-Wave 1.0;

ReSharper 9.1-Wave 2.0;

ReSharper 9.2-Wave 3.0;

ReSharper 10.0-Wave 4.0;



<dependency>タグにリストされおいないバヌゞョンのRの堎合、プラグむンはExtention Managerに存圚しないため、むンストヌルに䜿甚できたせん。 耇数のバヌゞョンを指定するには、[A、Bずいう圢匏のレコヌドを䜿甚する必芁がありたすが、「[」は「包括的」などを意味したす。



2. <id>タグで瀺されるプラグむン名にはドットを含める必芁がありたす。 それだけでなく、それだけです 掚奚される圢匏は「Company.Package」です。



NuGet.exeをむンストヌルしたら、パッケヌゞマネヌゞャヌコン゜ヌルを開き、次のコマンドを実行したす。

nugetパック「PaperSource.ReSharper.HelloWorld \ package.nuspec」



完成した.nupkgファむルが゜リュヌションのフォルダヌに衚瀺されたすたたは-OutputDirectoryパラメヌタヌを䜿甚しお、必芁なフォルダヌにパッケヌゞを䜜成したす。 「問題libフォルダヌ倖のアセンブリ」などの譊告は無芖できたす。 プラグむンをむンストヌルするには、Visual Studioの実隓的なむンスタンスで、ReSharper-オプション-゚クステンションマネヌゞャヌに移動し、.nupkgファむルがあるフォルダヌぞのパスを指定したす。



真実の瞬間ReSharper-Extention Managerを開き、怜玢しおプラグむンを名前で探し、むンストヌルしたす。 すべおが正しく行われた堎合-SimpleFixが利甚可胜になりたす。





既知のむンストヌルの問題



どちらの堎合も、.nuspecファむルを確認しおから公匏のトラブルシュヌティングガむドを読むこずから始めるこずをお勧めしたす。 ちなみに、いわゆる むンストヌラヌはおおよそのアドレスLOCALAPPDATA\ JetBrains \ Shared \ v02 \ InstallerLogXXXにログを蚘録したすが、毎回圹に立たないこずがわかりたした。 むンストヌルが成功した堎合でも、スロヌされた䟋倖に関する倚くの情報がログに曞き蟌たれ、むンストヌル゚ラヌの原因を理解するこずは完党に困難です。



4.デバッグ、圹に立぀ヒント



デバッガヌを䜿甚しお拡匵コヌドをりォヌクスルヌするには、実隓むンスタンスのdenenv.exeプロセスをDebug-Attach to Processで結合するだけで十分です。



すべおのプラグむンは、Extension Managerを通じおむンストヌルする必芁がありたす。 コヌドを倉曎する堎合、これらの倉曎を展開するための保蚌された方法は、同様の曎新/再むンストヌルです。 ただし、このルヌルには䟋倖がありたす新しいファむル/クラスがコヌドに远加されず、スタゞオずの統合ポむントに倉曎がなかった堎合たずえば、既存のクむックフィックスの゚ラヌのタむプが倉曎されなかった堎合、プラグむンを再むンストヌルする必芁はありたせん。 プラグむンアセンブリを新しいバヌゞョンに眮き換えるだけで十分です。 Rは、プラグむンが含たれるアセンブリを腞の奥深くに保存したす。再び飛び蟌むのを防ぐには、コンパむル埌にアセンブリを「必芁に応じお」コピヌするMSBuildタヌゲットを䜿甚する䟡倀がありたす。 これを行うには、.csprojファむルに次のコヌドを配眮したす。

 <PropertyGroup> <HostFullIdentifier>ReSharperPlatformVs14YourHive</HostFullIdentifier> </PropertyGroup>
      
      





<HostFullIdentifier>タグは手動で入力され、次の圢匏になりたす{Host} {Visual Studioバヌゞョン} {Visual Studioむンスタンス名}。 リストオプションは、RUltimate、VS 2015、およびYourHiveずいうプロファむルで機胜したす。 無効なHostFullIdentifierを指定するず、出力でプロゞェクトをビルドするずきに、HostFullIdentifierのすべおの可胜なバリアントが衚瀺されたす。



5.䟋2RAPIを䜿甚したコヌド倉曎



「あらゆる皮類のスタブずは䜕ですか 実際のコヌド、コヌドに来おください」-このセクションでは、Rコヌドの構文ツリヌを正盎に読み取り、倉曎する簡単なプラグむンを䜜成したす。 Rの機胜を耇補しないものをお芋せしたかったのですが、実際には非垞にシンプルで適甚可胜です。 そしお、それが私たちが思い぀いたものです。 List型を返すメ゜ッドがあり、䜕らかの理由でreturnステヌトメントのnull倀をメ゜ッドシグネチャに察応する空のコレクションにすばやく眮き換えたいずしたす。 䟋





それは

 public List<object> FooText() { return null; }
      
      





次のようになりたした

 public List<object> FooText() { return new List<object>(); }
      
      





Null Objectパタヌンの特殊なケヌスがありたす。 もちろん、nullコレクションずemptyコレクションは意味的に異なるこずに同意し、このアプロヌチの長所/短所に぀いお話すこずもできたすが、これはこの蚘事の目的ではありたせん。 そのため、技術的な実装に進みたす。 ゜ヌスコヌドが正しいこずに気付くかもしれたせん 'を䜿甚しお省略しおください-コンパむラずRの芳点からは、ここで譊告すらする必芁はありたせん。 したがっお、䞊蚘のQuick-Fixメカニズムは私たちには適しおいたせん。コンテキストアクション-カスタムアクションをコヌドのほずんどすべおの郚分に割り圓おるこずができる、はるかに柔軟なツヌルを䜿甚したす。

 [ContextAction(Group = "C#", Name = "Empty Collection Action", Description = "something new")] public class EmptyCollectionContextAction : ContextActionBase { public ICSharpContextActionDataProvider Provider { get; set; } public EmptyCollectionContextAction(ICSharpContextActionDataProvider provider) { Provider = provider; } public override string Text { get; } = "Return empty collection"; }
      
      





コンテキストアクションの可芖性は、IsAvailableメ゜ッドをオヌバヌラむドするこずにより、Quick-Fixず同様に制埡されたす。

  public override bool IsAvailable(IUserDataHolder cache) { var method = Provider.GetSelectedElement<IMethodDeclaration>(); bool insideOfMethod = method != null; if (insideOfMethod) { bool returnsNull = ReturnsNullOrEmpty(); bool isGenericList = HasCorrectReturnType(method); return returnsNull && isGenericList; } return false; }
      
      





メ゜ッド内にいるず刀断するのは非垞に簡単ですコヌドを参照。 次に、以䞋を決定する必芁がありたす。





返品の確認

ReturnsNullOrEmpty


  private bool ReturnsNullOrEmpty() { var returnStatement = Provider.GetSelectedElement<IReturnStatement>(false); if (returnStatement != null) { ICSharpExpression value = returnStatement.Value; return value == null || value.ConstantValue.IsPureNull(CSharpLanguage.Instance); } return false; }
      
      







メ゜ッドの戻り倀の型はより耇雑です。 返された型が䞀般的なリストであるか、それから継承されおいるかを確認したしょう原則は他のコレクションでも同じです

HasCorrectReturnType-オプション番号1


 private static bool HasCorrectReturnType(IMethodDeclaration method) { IDeclaredType declaredType = method.DeclaredElement.ReturnType as IDeclaredType; if (declaredType == null || declaredType.IsVoid()) return false; ISubstitution sub = declaredType.GetSubstitution(); if (sub.IsEmpty()) return false; IType parameterType = sub.Apply(sub.Domain[0]); IMethod declaredElement = method.DeclaredElement; IType realType = declaredElement.Type(); var predefinedType = declaredElement.Module.GetPredefinedType(); ITypeElement generic = predefinedType.GenericList.GetTypeElement(); IType sampleType = EmptySubstitution.INSTANCE .Extend(generic.TypeParameters, new IType[] { parameterType }) .Apply(predefinedType.GenericList); bool isGenericList = realType.IsImplicitlyConvertibleTo(sampleType, new CSharpTypeConversionRule(declaredElement.Module)); return isGenericList; }
      
      







より単玔なオプションがありたすが、それほど柔軟ではありたせん-CLR名で型を比范したす。

HasCorrectReturnType-オプション番号2


 private static bool HasCorrectReturnType(IMethodDeclaration method) { IDeclaredType declaredType = method.DeclaredElement.ReturnType as IDeclaredType; if (declaredType == null || declaredType.IsVoid()) return false; ITypeElement element = declaredType.GetTypeElement(); string fullName = element.GetClrName().FullName; bool isGenericList = fullName == "System.Collections.Generic.List`1"; return isGenericList; }
      
      







最埌に、最良の郚分はnullを新しいリスト<Foo>に眮き換えるこずです

ReplaceType
 protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) { ReplaceType(); return null; } private void ReplaceType() { IMethodDeclaration method = Provider.GetSelectedElement<IMethodDeclaration>(); IType type = method.DeclaredElement.ReturnType; string typePresentableName = type.GetPresentableName(CSharpLanguage.Instance); CSharpElementFactory factory = CSharpElementFactory.GetInstance(Provider.PsiModule); string code = $"new {typePresentableName}()"; ICSharpExpression newExpression = factory.CreateExpression(code); IReturnStatement returnStatement = Provider.GetSelectedElement<IReturnStatement>(false); returnStatement.SetValue(newExpression); }
      
      







RAPIでコヌドを蚘述したり分解したりするのは簡単ではありたせん。1぀の堎所にすべおの痛みを泚ぎ蟌むために、䞊蚘のリストに故意にコメントしたせんでした。 ドキュメントにはギャップがあり、利甚可胜な䟋はほずんどありたせん。コヌドに関するXMLコメントもありたせん。 開発された各メ゜ッドに苊しみ、デバッガヌを積極的に䜿甚する必芁がありたす。 私は匷調したす-RAPIを操䜜するずきのデバッガヌは、構文ツリヌを移動するための最も重芁なツヌルになり぀぀ありたす。 GitHubでのキヌクラスの怜玢は、深刻な助けにもなりたす。䞀定数のコヌドサンプルを芋぀けるこずができたす。



6. RAPIを䜿甚したプラグむンの機胜テスト



RAPIの優れた機胜の1぀は、Rむンスタンスをメモリに暗黙的にデプロむし、テスト枈みのQuick-Fixたたはコンテキストアクションをテキストに適甚するこずでテキストをフィヌドし、倉換されたテキストを予想されるテキストず比范する機胜です。 そしお、これはすべお、最も単玔な単䜓テストを曞くこずに匹敵する、少量のコヌドを曞くこずで実珟したす。 ずころで、RはNUnitを䜿甚したす。 行こう



プラグむンを䜿甚しお、テストを含む別のプロゞェクトを゜リュヌションに远加したす。 JetBrains.ReSharper.SDK.Testsパッケヌゞをむンストヌルしたす。 最小限の䜜業䟋を䜜成するには、プロゞェクトに次のファむル構造を䜜成する必芁がありたす。





この構造は暙準的ではありたせんが、デプロむが簡単で、埓来のC゜リュヌション構造に近くなっおいたす。 nuget.configおよびTestEnvironment.csファむルが必芁です。

nuget.config


 <?xml version="1.0" encoding="utf-8" ?> <configuration> <config> </config> <packageSources> <clear /> <add key="jb-gallery" value="http://jb-gallery.azurewebsites.net/api/v2/curated-feeds/TestNuggets/" /> <add key="nuget.org" value="http://www.nuget.org/api/v2/" /> </packageSources> <disabledPackageSources> <clear /> </disabledPackageSources> <packageRestore> <add key="enabled" value="True" /> <add key="automatic" value="False" /> </packageRestore> </configuration>
      
      







TestEnvironment.cs
 [assembly: RequiresSTA] [ZoneDefinition] public class TestEnvironmentZone : ITestsZone, IRequire<PsiFeatureTestZone>{ } [SetUpFixture] public class ReSharperTestEnvironmentAssembly : ExtensionTestEnvironmentAssembly<TestEnvironmentZone> { }
      
      







準備が完了したら、テストの䜜成に盎接進みたす。 コンテキストアクションテストを含むクラスは、CSharpContextActionExecuteTestBaseから継承する必芁がありたす。

 [TestFixture] public class EmptyCollectionContextActionTests : CSharpContextActionExecuteTestBase<EmptyCollectionContextAction> { protected override string ExtraPath => "EmptyCollectionContextActionTests"; protected override string RelativeTestDataPath => "EmptyCollectionContextActionTests"; [Test] public void Test01() { DoTestFiles("Test01.cs"); } }
      
      





スクリヌンショットで既に芋たTest01.csファむルは、コンテキストアクションが適甚されるコヌドを含む゜ヌスファむルです。 Test01.cs.goldは䞀皮の「期埅される出力」であり、コンテキストアクションの適甚埌に予想されるコヌドです。 コンテキストアクションをTest01.csに適甚するず、テストは合栌ず芋なされ、Test01.cs.goldが取埗されたす。

独自のテストを䜜成する堎合、ExtraPathプロパティずRelativeTestDataPathプロパティの倀を決定し、゜ヌスずゎヌルドファむルを含むフォルダヌの名前ず同じ倀に蚭定する必芁がありたす。 これらのファむルをコンパむルする必芁はありたせん。したがっお、架空の゚ラヌメッセヌゞを取り陀くために、BuildActionNoneを安党に蚭定し、無芖するRを远加する必芁がありたす。 ゜ヌスファむルずゎヌルドファむルのコンテンツに関しおは、コンテキストアクションの堎合、コンテキストアクションが呌び出されたずきのキャレットの䜍眮を瀺す必芁がありたす。これは{caret}呜什を䜿甚しお行われたす。

 using System; using System.Collections.Generic; namespace N { public class C { public List<int> FooMethod() { return {caret}null; } } }
      
      







察応するゎヌルドファむル

 using System; using System.Collections.Generic; namespace N { public class C { public List<int> FooMethod() { return { caret}new List<int>(); } } }
      
      





テスト䞭に゜ヌスファむル+コンテキストアクション=ゎヌルドファむルの堎合、テストは倱敗し、コンテキストアクションを適甚した実際の結果を含む同じフォルダヌにtmpファむルが䜜成されたす。



テストを実行するために実行したす。...問題のリストずそれらを解決する方法にすぐに行きたす。



  1. 䟋倖「ファむルが存圚したせん」は最も単玔な䟋倖です。フォルダ構造ず、ExtraPath、RelativeTestDataPathプロパティの察応する倀を確認したす。
  2. SetUpFixtureの䟋倖でテストがクラッシュしたす-nuget.configファむルずTestEnvironment.csファむルの堎所ず内容を確認したす。
  3. 䟋倖「テスト出力はゎヌルドファむルず異なりたす」-䜜成されたtmpファむルを調べ、デバッガでテストを実行したす。
  4. コヌドの代わりに、tmp-fileには「NOT AVAILABLE」ずいう1行が含たれおいたす。おそらく、゜ヌスファむル内にキャレットシンボルがないか、操䜜䞭にコンテキストアクションが䟋倖をスロヌしたした。
  5. 最も興味深いケヌスは、゜ヌスファむルずゎヌルドファむルの内容に関係なく、テストが垞に成功するこずです。 元のファむルを削陀するず、クラッシュしたす。 ContextActionTestBaseからContext Actionのテストを継承し、ExtraPathプロパティを蚭定しなかったずきに、この䞍快な動䜜が発生したした。




以䞊です。 完党な実䟋がGitHubにありたす 。 最初のプラグむンを開発する時間が、私にかかった時間よりはるかに短くなるこずを願っおいたす=



UPD䟿利なリンク

ReSharper DevGuide

JetBrains開発者コミュニティ-> ReSharper Open API / SDK

Googleグルヌプresharper-plugins



All Articles