ReSharperの拡張-コンテキストアクション

以前の投稿の1つに対するコメントで、Resharperに拡張機能を作成する方法について話すことを約束しました。 特定の分野での作業を簡素化する拡張機能を定期的に作成しているためです。 すぐに、タイプコンテキストアクションの拡張機能を作成するためのアプローチを簡単に示します。


そのため、コンテキストアクションは、左側に矢印付きで表示されるメニューであり、コード内の「迅速な修正」の可能性を提供します。 ところで、このメニューの開始をバインドしたい場合、コマンドはReSharper.QuickFixと呼ばれます。 このメニューの追加オプションを書いています。 なんで? 時間が節約できることがあるからです。 Resharperのコンテキストアクションの作成方法を見てみましょう。

プラグインとrsquoを作成し、デバッグを構成します
Resharper拡張機能は、リゾルバーのbinフォルダーのPluginsフォルダーに配置される通常のクラスライブラリ(DLL)です。 デバッグのために、それらをコピーする必要はありません。スタジオ自体( devenv.exe )を呼び出すための引数としてプラグインの名前とパスを指定するだけです。 構文は次のようなものです。

devenv.exe /ReSharper.Plugin c:\ path \ to \ your.dll

何も書かずにF5に従ってデバッグを開始すると、プラグインはリゾルバーのプラグインのリストに既に表示されます。 もちろん、今からやろうとするよりも、何らかの種類のコンテンツを追加する方が良いでしょう。

コンテキストアクションを開始します
最初に行うことは、必要なリゾルバーアセンブリへのリンクを追加することです。 名前ReSharperが表示されるすべてのアセンブリへのリンクを愚かに追加します。 何が必要かわからない。

コンテキストアクションは、 CSharpContextActionBase (C#の場合)から継承することによって、および他のいくつかのインターフェイスを実装することによって実行されます。 幸いなことに、配管の一部は他のプラグインの開発者によって実装されました。 コンテキストContextActionBase場合、 Agent Johnsonプラグインの作成者が作成したContextActionBaseクラスをContextActionBaseに追加します。 実際には、ファイル自体はここにあります

ここで、CAを作成するには、2つのことを行う必要があります。



CAのインターフェース
動作させるには、結果のクラスに4つのメソッドのみを追加する必要があります。


簡単な例を見てみましょう。 Math.Pow()呼び出しを整数値ですばやくMath.Pow()化する関数を実装する必要があると想像してください。 これは必要です


それで、このミニリファクタリングを徐々に実装してみましょう。

フレーム
まず、CAのクラスを作成し、メタデータの小さなセットで装飾します。 textフィールドが追加されるため、CAメニューで直接リファクタリング後のコードに何が起こるかをユーザーに伝えることができます。



[ContextAction(Group = "C#" , Name = "Inline a power function" ,
Description = "Inlines a power statement; eg, changes Math.Pow(x, 3) to x*x*x." ,
Priority = 15)]
internal class InlinePowerAction : ContextActionBase
{
private string text;
public InlinePowerAction(ICSharpContextActionDataProvider provider) : base (provider)
{
//
}

}

フレームの準備ができました。 次に、CAが適用可能かどうかを判断する方法を学習する必要があります。

IsAvailable()
CAは、 Math.Pow()本体に座っており、この本体が整数度(たとえば3.0 Math.Pow()である場合にのみ適用できます。 どうやってやるの? まず、ユーザーがカーソルを持っている場所を見つけます。 次に、カーソルと同じ場所にある構文ツリーのノードを取得し、予想されるタイプにキャストしようとします。 Math.Pow()は関数呼び出しであるため、本体にIInvocationExpressionIInvocationExpressionが表示されることを期待しています。 など、チェーンに沿って、そして式が期待したものでない場合に備えて、 as演算子を常に使用します。

チェーン全体の最後で、指数の値を見つけて確認します。 整数であり、1〜10-CAが適用可能な場合、 true返しtrue 。 それ以外の場合はすべて、 false返しfalse

以下にコード例を示します。 読むのではなく、デバッガーを使って歩いてみる方が良いでしょう。 これは、ReSharper全体での作業に適用されます-構文ツリーの構造について詳しく知る最良の方法は、デバッガーを使用することです。



protected override bool IsAvailable(JetBrains.ReSharper.Psi.Tree.IElement element)
{
using (ReadLockCookie.Create())
{
IInvocationExpression invEx = GetSelectedElement<IInvocationExpression>( false );
if (invEx != null && invEx.InvokedExpression.GetText() == "Math.Pow" )
{
IArgumentListNode node = invEx.ToTreeNode().ArgumentList;
if (node != null && node.Arguments.Count == 2)
{
ILiteralExpression value = node.Arguments[1].Value as ILiteralExpression;
if ( value != null )
{
float n;
if ( float .TryParse( value .GetText().Replace( "f" , string .Empty), out n) &&
(n - Math.Floor(n) == 0 && n >= 1 && n <= 10))
{
text = "Replace with " + (n-1) + " multiplications" ;
return true ;
}
}
}
}
}
return false ;
}

true返す前に変数text値を設定する方法を参照してください。 これにより、ユーザーがCAをより適切に読み取ることができます。 はい。コードがラップされているReadLockCookieについては、Resharperの内部セマンティクスの要素です。 彼が何をしているかわからない-念のために、このような例からコピーするだけ。 結局のところ、Resharperのプラグインの作成に関する詳細で更新されたドキュメントはありません。

GetText()
IsAvailable()からtrueを返したtrueIsAvailable()はメニューに描画するテキストを知りたいです。 この場合、何を返すか、つまりtext変数の内容をすでに知っています。

protected override string GetText()
{
return text;
}

ああ、すべてがとても簡単だったら...

実行()
ユーザーには、意図した目的でCAを使用する機会があります。 彼がメニューでそれをクリックすると、 Execute()メソッドが呼び出されます。 そして、ここで置換アルゴリズムが機能し始めます。 覚えておいてください-たとえば、 Math.Pow(x, 3.0)x*x*xに変更したいのです。 どうやってやるの?

ここでも、 Math.Pow()を含むツリーノードが必要です。 両方のパラメーター(上記の例xおよび3)を引き出し、たとえ3.0でなく3.0fのように書き込まれている場合でも、値を慎重に変換します。 次に、式の長さを左に決定しますxの累乗にするとx*x*x書くことができますが、 x+y場合は角括弧(x+y)*(x+y)*(x+y)書く必要があるため(x+y)*(x+y)*(x+y) 。 これを行うために、型を中断しますILiteralExpressionまたはIReferenceExpression場合、cheersは式「short」です。

式と整数の力を受け取ったら、 StringBuilderを使用して置換する文字列を作成します。 しかし、その後、興味深いことが起こります。

最初に、 ICSharpExpression型のオブジェクトを作成します。これにより、 Math.Powノードを置き換えることができるノードをラインから作成できます。 次の式はそれを実行しますLowLevelModificationUtilを使用して、あるノードを別のノードに置き換えます。

protected override void Execute(JetBrains.ReSharper.Psi.Tree.IElement element)
{
IInvocationExpression expression = GetSelectedElement<IInvocationExpression>( false );
if (expression != null )
{
IInvocationExpressionNode node = expression.ToTreeNode();
if (node != null )
{
IArgumentListNode args = node.ArgumentList;
int count = ( int ) double .Parse(args.Arguments[1].Value.GetText().Replace( "f" , string .Empty));
bool isShort = node.Arguments[0].Value is ILiteralExpression ||
node.Arguments[0].Value is IReferenceExpression;
var sb = new StringBuilder();
sb.Append( "(" );
for ( int i = 0; i < count; ++i)
{
if (!isShort) sb.Append( "(" );
sb.Append(args.Arguments[0].GetText());
if (!isShort) sb.Append( ")" );
if (i + 1 != count)
sb.Append( "*" );
}
sb.Append( ")" );
// now replace everything
ICSharpExpression newExp = Provider.ElementFactory.CreateExpression(
sb.ToString(), new object [] { });
if (newExp != null )
{
LowLevelModificationUtil.ReplaceChildRange(
expression.ToTreeNode(),
expression.ToTreeNode(),
new [] { newExp.ToTreeNode() });
}
}
}
}

以上です。 すべてが機能します。 完全なCAはここからダウンロードできます 。 基本クラスContextActionBaseここにあることを思い出してください。 この例はResarperバージョン4.5でテストされました。バージョン5については何も知りません:)

おわりに
例が複雑であることは知っています。 構文ツリーをたどることは簡単な作業ではありません。 私が書いたほぼすべての SAに苦しんでいます。 もちろん、デバッガーはこの点で非常に役立ちますが、複雑なアクションゲームを作成する場合は、たとえば、同じF#で簡単なDSLをスケッチすることをお勧めします。C#でのツリー検索は、これらのすべての型キャスト、 nullチェックなどで乱雑に見えるためです。 頑張って ■

Source: https://habr.com/ru/post/J74026/


All Articles