2014-04-23 11 views
5

Pracuję nad utworzeniem projektu open source do tworzenia diagramów sekwencji UML .NET, które wykorzystują bibliotekę javascript o nazwie js-sequence -diagramy. Nie jestem pewien, czy Roslyn jest właściwym narzędziem do tej pracy, ale pomyślałem, że dałbym mu szansę, więc przygotowałem jakiś kod koncepcyjny, który próbuje uzyskać wszystkie metody i ich wywołania, a następnie wyprowadza te inwokacje w formie, która można interpretować za pomocą diagramów sekwencji js.Jak uzyskać dostęp do wywołań za pomocą metod rozszerzeń, metod w statycznych klasach i metodach z parametrami wyszukiwania/wylogowywania z Roslyn

Kod generuje pewne wyniki, ale nie przechwytuje wszystkiego. Nie mogę uchwycić inwokacji za pomocą metod rozszerzeń, wywołań metod statycznych w klasach statycznych.

widzę wywołania metod z out parametrów, ale nie w jakiejkolwiek formie, która rozciąga się BaseMethodDeclarationSyntax

Oto kod (należy pamiętać, jest to dowód kodu koncepcyjnego a więc nie całkowicie podążać najlepsze- praktyki, ale nie mam prośbę o sprawdzenie kodu tutaj ... również, jestem przyzwyczajony do korzystania Zadania więc jestem aprowizacji z poczekać, ale nie jestem do końca pewien, używam go właściwie jeszcze)

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; 
     } 
    } 
} 

Wszelkie wskazówki tutaj będą mile widziane!

+2

Anecdotally powiedziałbym Roslyn jest prawidłowe narzędzie do pracy, ale być może trzeba rozszerzyć z niestandardowych części w celu utrzymania chwyt informacji uważasz brakuje (w pewnym momencie nawet kompilator Roslyn będzie wiedzieli, jakie były metody rozszerzenia itp.). Możesz być tutaj również świetnym szlakiem, ponieważ jest tak nowy, zgłoś to i odpowiedz na swoje pytanie, jeśli znajdziesz się poza tym słowem :-) –

+2

Rozważ pisanie odwiedzającego węzeł składni. – SLaks

+0

@AdamHouldsworth Zawsze przesyłam raport z odpowiedzią, powinienem znaleźć się w dowolnym miejscu :) Również ten projekt będzie całkowicie open source i udostępnię link do GitHub, gdy przejdę obok P.O.C. – Jordan

Odpowiedz

1

Używanie methodSymbol w metodzie ProcessClass Wziąłem sugestię Andy'ego i wpadł poniżej (chociaż wyobrażam sobie nie może być łatwiejszy sposób, aby przejść na ten temat):

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; 
} 

i uzyskany obraz generowany przez podłączenie tekstu wyjściowego do js-sequence-diagrams (Zaktualizowałem github gist z pełnym źródłem tego, jeśli ktoś uzna to za użyteczne - wyłączyłem parametry metody, aby schemat był łatwy do strawienia, ale można je opcjonalnie ponownie włączyć):

Edytuj :

I został uaktualniony kod (patrz github gist), więc teraz rozmowy są wyświetlane w kolejności, w jakiej zostały wykonane (na podstawie początkowej lokalizacji rozpiętość zwaną metodą od wewnątrz metody wywołującego poprzez wynika z FindCallersAsync):

enter image description here