2012-03-19 19 views
5

Ho riscontrato una situazione difficile utilizzando SyntaxRewriter in Roslyn. Vorrei riscrivere certi tipi di dichiarazioni, incluse le dichiarazioni delle variabili locali. La soluzione mi impone di trasformare le dichiarazioni in questione in più istruzioni, come nel seguente esempio banale:SyntaxRewriter e più istruzioni

void method() 
{ 
    int i; 
} 

diventa

void method() 
{ 
    int i; 
    Console.WriteLine("I declared a variable."); 
} 

Ho visto altri esempi in cui vengono utilizzati i blocchi per raggiungere qualcosa di simile, ma ovviamente nel caso di una dichiarazione variabile, l'ambito della dichiarazione sarà interessato. Ho trovato la seguente soluzione, ma mi sto prendendo in giro. Sembra troppo complicato e richiede un'interruzione nel modello di visitatore:

class Rewriter: SyntaxRewriter 
{ 
    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     if (typeof(TNode) == typeof(StatementSyntax)) 
      return Syntax.List<TNode>(list.SelectMany(st => RewriteStatementInList(st as StatementSyntax).Cast<TNode>())); 
     else 
      return base.VisitList<TNode>(list); 
    } 

    private IEnumerable<SyntaxNode> RewriteStatementInList(StatementSyntax node) 
    { 
     if (node is LocalDeclarationStatementSyntax) 
      return PerformRewrite((LocalDeclarationStatementSyntax)node); 
     //else if other cases (non-visitor) 

     return Visit(node).AsSingleEnumerableOf(); 
    } 

    private IEnumerable<SyntaxNode> PerformRewrite(LocalDeclarationStatementSyntax orig) 
    { 
     yield return orig; 
     yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");"); 
    } 
} 

Cosa mi manca? Modificare le istruzioni e rimuoverle (tramite istruzioni vuote) sembra più semplice della riscrittura in multipli.

mio assumere la risposta:

class Rewriter : SyntaxRewriter 
{ 
    readonly ListVisitor _visitor = new ListVisitor(); 

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     var result = Syntax.List(list.SelectMany(_visitor.Visit).Cast<TNode>()); 
     return base.VisitList(result); 
    } 

    private class ListVisitor : SyntaxVisitor<IEnumerable<SyntaxNode>> 
    { 
     protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node) 
     { 
      yield return node; 
     } 

     protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
      LocalDeclarationStatementSyntax node) 
     { 
      yield return node; 
      yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");"); 
     } 
    } 
} 

risposta

3

Penso che ci sia un modo semplice per rendere il vostro Rewriter più visitatore-tipo: utilizzare un altro visitatore per elaborare i nodi della lista:

class Rewriter: SyntaxRewriter 
{ 
    readonly Visitor m_visitor = new Visitor(); 

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list) 
    { 
     var result = Syntax.List(list.SelectMany(m_visitor.Visit).Cast<TNode>()); 
     return base.VisitList(result); 
    } 
} 

class Visitor : SyntaxVisitor<IEnumerable<SyntaxNode>> 
{ 
    protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node) 
    { 
     return new[] { node }; 
    } 

    protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
     LocalDeclarationStatementSyntax node) 
    { 
     return new SyntaxNode[] 
       { 
        node, 
        Syntax.ParseStatement(
         "Console.WriteLine(\"I declared a variable.\");") 
       }; 
    } 
} 

Nota che questo non è sicuro e genera un InvalidCastException se si restituisce una raccolta che contiene oggetto che non è TNode dal Visitor.

+0

Stavo pensando di farlo in questo modo. L'unico lato negativo sarebbe che IEnumerable avrà senso solo all'interno di una chiamata a VisitList. Penso che un costruttore/scrittore, sarebbe una risposta convincente, ma penso di essere ossessionato a riguardo :) Ti inviterò in futuro ragazzi, apparentemente non ho ancora abbastanza peso. –

+0

Perché è un problema che 'IEnumerable' ha senso solo in una chiamata a' VisitList() '? Ogni volta che ha senso sostituire un nodo con una sequenza di nodi, credo che quel nodo dovrebbe essere una parte di qualche lista. – svick

+0

In realtà, non ho notato che stavi chiamando direttamente "Visitor" e solo da "VisitList". Mi piace molto questa soluzione.Lo inserirò nella mia domanda, con 'yield return' per generare gli iteratori e annidare la classe visitor dell'elenco. –

2

Non so di un modo radicalmente migliore per gestire questa situazione. Un altro approccio leggermente più "simile ai visitatori" è quello di utilizzare VisitLocalDeclaration per annotare nodi che si desidera sostituire con qualcosa come: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);. Quindi, in VisitList, puoi trovare i nodi figlio che la tua annotazione e fare la riscrittura in quel punto.

+0

Grazie, Kevin. È qualcosa che non ho guardato per questo. Quindi, se dovessi adottare tale approccio, avrei comunque bisogno di fare un caso per ogni tipo di gestione speciale per tipo di nodo, quando riscrivi in ​​VisitList, corretto? –

0

Stavo sfogliando il codice sorgente di Roslyn per vedere come lo fa il team di Roslyn. Ecco un esempio: http://source.roslyn.codeplex.com/Microsoft.CodeAnalysis.CSharp.Features/R/bcd389b836bf7b4c.html

In breve, penso che assomigli più o meno a questo. (questo rewriter capita di eliminare solo StatementExpressions, ma puoi vedere che è costruito con un metodo iteratore, quindi è anche facile aggiungere metodi).

class TreeRewriter : CSharpSyntaxRewriter 
{ 
    public override SyntaxNode VisitBlock(BlockSyntax node) 
     => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements)))); 

    public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node) 
     => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements)))); 

    IEnumerable<StatementSyntax> ReplaceStatements(IEnumerable<StatementSyntax> statements) 
    { 
     foreach (var statement in statements) 
     { 
      if (statement is ExpressionStatementSyntax) continue; 
      yield return statement; 
     } 
    } 
} 

Ecco come guido quel codice:

var rewriter = new TreeRewriter(); 
var syntaxTree = await document.GetSyntaxTreeAsync(); 
var root = await syntaxTree.GetRootAsync(); 
var newRoot = rewriter.Visit(root); 
var newDocument = document.WithSyntaxRoot(newRoot); 
Problemi correlati