2016-12-07 28 views
1

背景:C#のでロスリンを使用してRoslyn - 複数のノードをそれぞれ複数のノードに置き換えるにはどうすればよいですか?

、私はアクセサ体がその後の処理によって挿入されたコードを持つことができるように、自動実装プロパティを拡張しようとしています。 StackExchange.Precompilationをコンパイラフックとして使用しているため、これらの構文変換はアナライザまたはリファクタリングの一部ではなく、ビルドパイプラインで行われます。

[SpecialAttribute] 
int AutoImplemented { 
    get { return _autoImplemented; } 
    set { _autoImplemented = value; } 
} 

private int _autoImplemented; 

問題:このに

[SpecialAttribute] 
int AutoImplemented { get; set; } 

私はこれをオンにしたい、私が働いて単純な変換を得ることができた

が、私は、自動プロパティに立ち往生しています。 ays。私が抱えている問題は、ツリー内の複数のノードを置き換えるときに、SyntaxNodeExtensions.ReplaceNodeSyntaxNodeExtensions.ReplaceNodes拡張メソッドを正しく使用することです。

私は変換のためにCSharpSyntaxRewriterを拡張するクラスを使用しています。そのクラスの関連するメンバーをここで共有します。このクラスは、それぞれclassstructの宣言を参照し、SpecialAttributeとマークされているプロパティ宣言を置き換えます。

private readonly SemanticModel model; 

public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { 
    if (node == null) throw new ArgumentNullException(nameof(node)); 
    node = VisitMembers(node); 
    return base.VisitClassDeclaration(node); 
} 

public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { 
    if (node == null) throw new ArgumentNullException(nameof(node)); 
    node = VisitMembers(node); 
    return base.VisitStructDeclaration(node); 
} 

private TNode VisitMembers<TNode>(TNode node) 
    where TNode : SyntaxNode { 

    IEnumerable<PropertyDeclarationSyntax> markedProperties = 
     node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Where(prop => prop.HasAttribute<SpecialAttribute>(model)); 

    foreach (var prop in markedProperties) { 
     SyntaxList<SyntaxNode> expanded = ExpandProperty(prop); 
     //If I set a breakpoint here, I can see that 'expanded' will hold the correct value. 
     //ReplaceNode appears to not be replacing anything 
     node = node.ReplaceNode(prop, expanded); 
    } 

    return node; 
} 

private SyntaxList<SyntaxNode> ExpandProperty(PropertyDeclarationSyntax node) { 
    //Generates list of new syntax elements from original. 
    //This method will produce correct output. 
} 

HasAttribute<TAttribute>そのプロパティは、特定のタイプの属性を持っているかどうかをチェック私はPropertyDeclarationSyntaxに対して定義された拡張メソッドです。この方法は正しく機能します。


私はReplaceNodeを正しく使用していないと思います。関連する3つの方法があります。

TRoot ReplaceNode<TRoot>(
    TRoot root, 
    SyntaxNode oldNode, 
    SyntaxNode newNode); 

TRoot ReplaceNode<TRoot>(
    TRoot root, 
    SyntaxNode oldNode, 
    IEnumerable<SyntaxNode> newNodes); 

TRoot ReplaceNodes<TRoot, TNode>(
    TRoot root, 
    IEnumerable<TNode> nodes, 
    Func<TNode, TNode, SyntaxNode> computeReplacementNode); 

それぞれのプロパティノードをフィールドノードとプロパティノードの両方に置き換える必要があるため、2番目の方法を使用しています。私は多くのノードでこれを行う必要がありますが、1対多のノードの置換を可能にするReplaceNodesの過負荷はありません。その過負荷の周りに私が見いだした唯一の方法は、非常に「不可欠」で、Roslyn APIの機能的な感じに見える、foreachループを使用していました。

このようなバッチ変換を実行するより良い方法はありますか?


更新: 私はロスリンに大きなブログのシリーズを発見し、その不変性に対処します。私はまだ正確な答えを見つけていないが、それは始めるのに適しているようだ。 https://joshvarty.wordpress.com/learn-roslyn-now/


アップデート:私は本当に困惑しているところ だからここにはあります。私は、Roslyn APIがすべて不変のデータ構造に基づいていることを知っています。ここでの問題は、構造のコピーを使用して変更可能性を模倣する方法の微妙な点です。問題は、私がツリー内のノードを置き換えるたびに、新しいツリーを作成するので、ReplaceNodeと呼ぶと、元のツリーには元のノードが含まれていないと思われます。

Roslynでツリーをコピーする方法は、ツリー内のノードを置き換えると、元のツリーの同じノードをすべて参照する新しいツリーが実際に作成されます。そのノードの直上にあるすべてのノード。置換ノードの下にあるノードは、置換ノードがそれらをもはや参照しないか、または新しい参照を追加することができるが、すべての古い参照が以前と同じノードインスタンスを依然として指している場合には、削除される可能性がある。私はこれがまさにAnders HejlsbergがRoslynのthis interview(20〜23分)について説明していると確信しています。

新しいnodeインスタンスには、元のシーケンスに同じpropインスタンスが含まれていないはずですか?特別な場合を


ハックソリューション:

私は最終的にどのツリー変換で変更されませんプロパティ識別子、に依存することによって動作するようにプロパティ宣言を変換のこの特定の問題を取得することができました。しかし、私はまだ、複数のノードをそれぞれ複数のノードに置き換えるための一般的な解決策が好きです。このソリューションは実際にはAPIを介さずに作業しています。ここで

は、特殊なケースのソリューションです:

private TNode VisitMembers<TNode>(TNode node) 
    where TNode : SyntaxNode { 

    IEnumerable<PropertyDeclarationSyntax> markedPropertyNames = 
     node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Where(prop => prop.HasAttribute<SpecialAttribute>(model)) 
      .Select(prop => prop.Identifier.ValueText); 

    foreach (var prop in markedPropertyNames) { 
     var oldProp = node.DescendantNodes() 
      .OfType<PropertyDeclarationSyntax>() 
      .Single(p => p.Identifier.ValueText == prop.Name); 

     SyntaxList<SyntaxNode> newProp = ExpandProperty(oldProp); 

     node = node.ReplaceNode(oldProp, newProp); 
    } 

    return node; 
} 

私が働いている別の同様の問題は、事後条件のチェックを挿入する方法では、すべてのreturn文を修正しています。この場合は、明らかにプロパティ宣言のような一意の識別子に依存することはできません。

+0

あなたが 'ReplaceNode'に送る' m'は誰ですか?あなたはそれが 'node'に存在すると確信していますか?なぜあなたは 'PropertyDeclarationSyntax'を訪れていませんか? –

+0

申し訳ありませんが、元のソースコードでは 'prop'と呼ばれていましたが、' m'と呼ばれましたが、ここで読みやすくするために 'prop'に変更しました。私がプロパティ宣言を訪れていないのは、プロパティ宣言を新しいプロパティ宣言とフィールド宣言の両方に置き換える必要があるからです。 1つのノードにアクセスしたときに1つのノードを複数のノードに置き換えることはできないと思います。親ノードにアクセスするときに行う必要があります(型宣言)。 – JamesFaix

+0

私はこのエラーが 'Replace'メソッドの使用にあると信じています。 'Replace'から得た結果には、変更は適用されません。私は、不変のデータ構造をコピーするため、 'markedProperties'列挙を作成したときとは違うツリーへの参照があると思います。私は木の参照が変化し続けるとき、このような繰り返しの置換を行う方法を理解できません。 – JamesFaix

答えて

2

あなたがそれを行うとき:最初に交換した後、node(例えば、あなたのclass)はもう元のプロパティが含まれていません

foreach (var prop in markedProperties) { 
    SyntaxList<SyntaxNode> expanded = ExpandProperty(prop); 
    //If I set a breakpoint here, I can see that 'expanded' will hold the correct value. 
    //ReplaceNode appears to not be replacing anything 
    node = node.ReplaceNode(prop, expanded); 
} 

Roslynでは、すべてが不変なので、最初の置換はあなたのために働くはずです。新しいツリー\ノードがあります。

それはあなたが以下のいずれかを考えることができます動作させるために:

  • は、元のツリーを変更せずに、あなたのリライタークラスで結果を構築し、あなたが終了すると、一度にすべてを置き換えます。あなたの場合、その平均はclassの注記をすぐに置き換えます。私は文を置き換えるときに良いオプションだと思う(私はlinqクエリ(理解)を流暢な構文に変換するコードを書いたときに使用した)が、すべてのクラスに対して、おそらく最適ではない。
  • ツリーが変更された後に、SyntaxAnnotaion \ TrackNodesを使用してノードを検索します。これらのオプションを使用すると、必要に応じてツリーを変更することができ、新しいツリーの古いノードを追跡することができます。
  • DocumentEditorを使用すると、ドキュメントを複数回変更してから新しいドキュメントを返すことができます。

これらのいずれかの例が必要な場合は、お知らせください。

+0

私はあなたの第一の選択肢の中にビルダーのようなものを考えていました。空のツリーから始めて、コピーが完全なツリーになるまで元のノード(または処理されたノード)を追加しますか?私は前に 'SyntaxAnnotation'について聞いていなかった。それは有望そうだ。 'Document'に関して、' StackExchange.Precompilation'がコンパイラにフックする方法は、 'CSharpSyntaxRewriter'を中心にしています。 'Document'を使うにはより広い範囲が必要でしょうか? – JamesFaix

+0

私はあなたのブログを見つけました。非常に有用なもの。私は最近RoslynとCecilにダイビングを始めました。 – JamesFaix

+0

@JamesFaixありがとう:)私はLINQの変換に使用した最初の方法を使用する方法の例を見ることができます。 SyntaxAnnotionsとTrackNodesは非常に使いやすいです。あなたは[Josh Varty blog](https://joshvarty.wordpress.com/learn-roslyn-now/)に役立つ情報を見つけることができると信じています。ご質問がある場合は、ここで質問するか、新しい質問をしてください。 –

関連する問題