2014-04-23 6 views
5

Sto lavorando alla creazione di un progetto open source per la creazione di diagrammi di sequenza UML .NET che sfruttino una libreria javascript chiamata js-sequence -diagrams. Non sono sicuro che Roslyn sia lo strumento giusto per il lavoro, ma ho pensato di dargli una possibilità, quindi ho messo insieme qualche prova del codice concettuale che tenta di ottenere tutti i metodi e le loro invocazioni e poi emette queste invocazioni in una forma che può essere interpretato da js-sequence-diagrams.Come accedere alle chiamate tramite i metodi di estensione, metodi in classi statiche e metodi con parametri di rif/out con Roslyn

Il codice genera un po 'di output, ma non cattura tutto. Non riesco a catturare invocazioni tramite metodi di estensione, invocazioni di metodi statici in classi statiche.

Vedo invocazioni di metodi con out parametri, ma non in qualsiasi forma che estende la BaseMethodDeclarationSyntax

Ecco il codice (tenere a mente questo è codice di prova e quindi non ho del tutto seguire best- pratiche, ma io non sto chiedendo qui una revisione del codice ... inoltre, sono abituati ad utilizzare le attività, quindi sono nei guai con aspettano, ma non sono del tutto sicuro che sto ancora usando in modo corretto)

https://gist.github.com/SoundLogic/11193841

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection.Emit; 
using System.Threading.Tasks; 
using Microsoft.CodeAnalysis; 
using Microsoft.CodeAnalysis.CSharp; 
using Microsoft.CodeAnalysis.CSharp.Syntax; 
using Microsoft.CodeAnalysis.Formatting; 
using Microsoft.CodeAnalysis.MSBuild; 
using Microsoft.CodeAnalysis.FindSymbols; 
using System.Collections.Immutable; 

namespace Diagrams 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      string solutionName = "Diagrams"; 
      string solutionExtension = ".sln"; 
      string solutionFileName = solutionName + solutionExtension; 
      string rootPath = @"C:\Workspace\"; 
      string solutionPath = rootPath + solutionName + @"\" + solutionFileName; 

      MSBuildWorkspace workspace = MSBuildWorkspace.Create(); 
      DiagramGenerator diagramGenerator = new DiagramGenerator(solutionPath, workspace); 
      diagramGenerator.ProcessSolution(); 

      #region reference 

      //TODO: would ReferencedSymbol.Locations be a better way of accessing MethodDeclarationSyntaxes? 
      //INamedTypeSymbol programClass = compilation.GetTypeByMetadataName("DotNetDiagrams.Program"); 

      //IMethodSymbol barMethod = programClass.GetMembers("Bar").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol; 
      //IMethodSymbol fooMethod = programClass.GetMembers("Foo").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol; 

      //ITypeSymbol fooSymbol = fooMethod.ContainingType; 
      //ITypeSymbol barSymbol = barMethod.ContainingType; 

      //Debug.Assert(barMethod != null); 
      //Debug.Assert(fooMethod != null); 

      //List<ReferencedSymbol> barReferencedSymbols = SymbolFinder.FindReferencesAsync(barMethod, solution).Result.ToList(); 
      //List<ReferencedSymbol> fooReferencedSymbols = SymbolFinder.FindReferencesAsync(fooMethod, solution).Result.ToList(); 

      //Debug.Assert(barReferencedSymbols.First().Locations.Count() == 1); 
      //Debug.Assert(fooReferencedSymbols.First().Locations.Count() == 0); 

      #endregion 

      Console.ReadKey(); 
     } 
    } 

    class DiagramGenerator 
    { 
     private Solution _solution; 

     public DiagramGenerator(string solutionPath, MSBuildWorkspace workspace) 
     { 
      _solution = workspace.OpenSolutionAsync(solutionPath).Result; 
     } 

     public async void ProcessSolution() 
     { 
      foreach (Project project in _solution.Projects) 
      { 
       Compilation compilation = await project.GetCompilationAsync(); 
       ProcessCompilation(compilation); 
      } 
     } 

     private async void ProcessCompilation(Compilation compilation) 
     { 
      var trees = compilation.SyntaxTrees; 

      foreach (var tree in trees) 
      { 
       var root = await tree.GetRootAsync(); 
       var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>(); 

       foreach (var @class in classes) 
       { 
        ProcessClass(@class, compilation, tree, root); 
       } 
      } 
     } 

     private void ProcessClass(
       ClassDeclarationSyntax @class 
      , Compilation compilation 
      , SyntaxTree tree 
      , SyntaxNode root) 
     { 
      var methods = @class.DescendantNodes().OfType<MethodDeclarationSyntax>(); 

      foreach (var method in methods) 
      { 
       var model = compilation.GetSemanticModel(tree); 
       // Get MethodSymbol corresponding to method 
       var methodSymbol = model.GetDeclaredSymbol(method); 
       // Get all InvocationExpressionSyntax in the above code. 
       var allInvocations = root.DescendantNodes().OfType<InvocationExpressionSyntax>(); 
       // Use GetSymbolInfo() to find invocations of target method 
       var matchingInvocations = 
        allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol)); 

       ProcessMethod(matchingInvocations, method, @class); 
      } 

      var delegates = @class.DescendantNodes().OfType<DelegateDeclarationSyntax>(); 

      foreach (var @delegate in delegates) 
      { 
       var model = compilation.GetSemanticModel(tree); 
       // Get MethodSymbol corresponding to method 
       var methodSymbol = model.GetDeclaredSymbol(@delegate); 
       // Get all InvocationExpressionSyntax in the above code. 
       var allInvocations = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>(); 
       // Use GetSymbolInfo() to find invocations of target method 
       var matchingInvocations = 
        allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol)); 

       ProcessDelegates(matchingInvocations, @delegate, @class); 
      } 

     } 

     private void ProcessMethod(
       IEnumerable<InvocationExpressionSyntax> matchingInvocations 
      , MethodDeclarationSyntax methodDeclarationSyntax 
      , ClassDeclarationSyntax classDeclarationSyntax) 
     { 
      foreach (var invocation in matchingInvocations) 
      { 
       MethodDeclarationSyntax actingMethodDeclarationSyntax = null; 
       if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax)) 
       { 
        var r = methodDeclarationSyntax; 
        var m = actingMethodDeclarationSyntax; 

        PrintCallerInfo(
         invocation 
         , classDeclarationSyntax 
         , m.Identifier.ToFullString() 
         , r.ReturnType.ToFullString() 
         , r.Identifier.ToFullString() 
         , r.ParameterList.ToFullString() 
         , r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty 
         ); 
       } 
      } 
     } 

     private void ProcessDelegates( 
       IEnumerable<InvocationExpressionSyntax> matchingInvocations 
      , DelegateDeclarationSyntax delegateDeclarationSyntax 
      , ClassDeclarationSyntax classDeclarationSyntax) 
     { 
      foreach (var invocation in matchingInvocations) 
      { 
       DelegateDeclarationSyntax actingMethodDeclarationSyntax = null; 

       if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax)) 
       { 
        var r = delegateDeclarationSyntax; 
        var m = actingMethodDeclarationSyntax; 

        PrintCallerInfo(
         invocation 
         , classDeclarationSyntax 
         , m.Identifier.ToFullString() 
         , r.ReturnType.ToFullString() 
         , r.Identifier.ToFullString() 
         , r.ParameterList.ToFullString() 
         , r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty 
        ); 
       } 
      } 
     } 

     private void PrintCallerInfo(
       InvocationExpressionSyntax invocation 
      , ClassDeclarationSyntax classBeingCalled 
      , string callingMethodName 
      , string returnType 
      , string calledMethodName 
      , string calledMethodArguments 
      , string calledMethodTypeParameters = null) 
     { 
      ClassDeclarationSyntax parentClassDeclarationSyntax = null; 
      if (!SyntaxNodeHelper.TryGetParentSyntax(invocation, out parentClassDeclarationSyntax)) 
      { 
       throw new Exception(); 
      } 

      calledMethodTypeParameters = calledMethodTypeParameters ?? String.Empty; 

      var actedUpon = classBeingCalled.Identifier.ValueText; 
      var actor = parentClassDeclarationSyntax.Identifier.ValueText; 
      var callInfo = callingMethodName + "=>" + calledMethodName + calledMethodTypeParameters + calledMethodArguments; 
      var returnCallInfo = returnType; 

      string info = BuildCallInfo(
        actor 
       , actedUpon 
       , callInfo 
       , returnCallInfo); 

      Console.Write(info); 
     } 

     private string BuildCallInfo(string actor, string actedUpon, string callInfo, string returnInfo) 
     { 
      const string calls = "->"; 
      const string returns = "-->"; 
      const string descriptionSeparator = ": "; 

      string callingInfo = actor + calls + actedUpon + descriptionSeparator + callInfo; 
      string returningInfo = actedUpon + returns + actor + descriptionSeparator + "returns " + returnInfo; 

      callingInfo = callingInfo.RemoveNewLines(true); 
      returningInfo = returningInfo.RemoveNewLines(true); 

      string result = callingInfo + Environment.NewLine; 
      result += returningInfo + Environment.NewLine; 

      return result; 
     } 
    } 

    static class SyntaxNodeHelper 
    { 
     public static bool TryGetParentSyntax<T>(SyntaxNode syntaxNode, out T result) 
      where T : SyntaxNode 
     { 
      // set defaults 
      result = null; 

      if (syntaxNode == null) 
      { 
       return false; 
      } 

      try 
      { 
       syntaxNode = syntaxNode.Parent; 

       if (syntaxNode == null) 
       { 
        return false; 
       } 

       if (syntaxNode.GetType() == typeof (T)) 
       { 
        result = syntaxNode as T; 
        return true; 
       } 

       return TryGetParentSyntax<T>(syntaxNode, out result); 
      } 
      catch 
      { 
       return false; 
      } 
     } 
    } 

    public static class StringEx 
    { 
     public static string RemoveNewLines(this string stringWithNewLines, bool cleanWhitespace = false) 
     { 
      string stringWithoutNewLines = null; 
      List<char> splitElementList = Environment.NewLine.ToCharArray().ToList(); 

      if (cleanWhitespace) 
      { 
       splitElementList.AddRange(" ".ToCharArray().ToList()); 
      } 

      char[] splitElements = splitElementList.ToArray(); 

      var stringElements = stringWithNewLines.Split(splitElements, StringSplitOptions.RemoveEmptyEntries); 
      if (stringElements.Any()) 
      { 
       stringWithoutNewLines = stringElements.Aggregate(stringWithoutNewLines, (current, element) => current + (current == null ? element : " " + element)); 
      } 

      return stringWithoutNewLines ?? stringWithNewLines; 
     } 
    } 
} 

Qualsiasi consiglio qui sarebbe molto apprezzato!

+2

Aneddoticamente direi Roslyn è lo strumento adatto per il lavoro, ma potrebbe essere necessario estenderlo con parti personalizzate al fine di trattenere le informazioni si pensa che manca (ad un certo punto anche il compilatore Roslyn sarà ho saputo quali erano i metodi di estensione ecc.). Potresti anche essere un tracciato qui perché è così nuovo, ti preghiamo di segnalarlo e rispondere alla tua domanda se dovessi arrivare ovunque con questo SO esterno :-) –

+2

Considera di scrivere un visitatore del nodo della sintassi. – SLaks

+0

@AdamHouldsworth Riesco sempre a riferire con la risposta se dovessi arrivare ovunque :) Inoltre, questo progetto sarà interamente open source e condividerò il collegamento GitHub una volta passato il P.O.C. – Jordan

risposta

1

Utilizzando la methodSymbol nel metodo ProcessClass ho preso il suggerimento di Andy e si avvicinò con la sotto (anche se immagino che ci può essere un modo più semplice per andare su questo):

private async Task<List<MethodDeclarationSyntax>> GetMethodSymbolReferences(IMethodSymbol methodSymbol) 
{ 
    var references = new List<MethodDeclarationSyntax>(); 

    var referencingSymbols = await SymbolFinder.FindCallersAsync(methodSymbol, _solution); 
    var referencingSymbolsList = referencingSymbols as IList<SymbolCallerInfo> ?? referencingSymbols.ToList(); 

    if (!referencingSymbolsList.Any(s => s.Locations.Any())) 
    { 
     return references; 
    } 

    foreach (var referenceSymbol in referencingSymbolsList) 
    { 
     foreach (var location in referenceSymbol.Locations) 
     { 
      var position = location.SourceSpan.Start; 
      var root = await location.SourceTree.GetRootAsync(); 
      var nodes = root.FindToken(position).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>(); 

      references.AddRange(nodes); 
     } 
    } 

    return references; 
} 

e l'immagine risultante generato da collegare il testo di output in js-sequence-diagrams (ho aggiornato il github gist con la sorgente completo per questo qualcuno dovrebbe trovare utile - ho escluso i parametri del metodo in modo che il diagramma è stato facile digerire, ma questi può opzionalmente essere riacceso):

Edit :

Ho aggiornato il codice (vedi la github gist), così ora le chiamate sono mostrati nell'ordine in cui sono state fatte (in base alla posizione di partenza arco di un metodo chiamato dall'interno del metodo di chiamata tramite i risultati da FindCallersAsync):

enter image description here

Problemi correlati