Как получить доступ к вызовам через методы расширения, методы в статических классах и методы с параметрами ref/out с Roslyn

Я работаю над созданием проекта с открытым исходным кодом для создания диаграмм последовательности .NET UML, в котором используется библиотека javascript, называемая js-sequence-diagrams. Я не уверен, что Roslyn является подходящим инструментом для этой работы, но я решил попробовать его, поэтому я собрал некоторый код для проверки концепции, который пытается получить все методы и их вызовы, а затем выводит эти вызовы в форме, которая можно интерпретировать с помощью js-sequence-диаграмм.

Код генерирует некоторые выходные данные, но не фиксирует все. Кажется, я не могу зафиксировать вызовы с помощью методов расширения, вызовы статических методов в статических классах.

Я вижу вызовы методов с out параметрами, но не в любой форме, которая расширяет BaseMethodDeclarationSyntax

Вот код (имейте в виду, что это доказательство концептуального кода, поэтому я не полностью следовал передовым методам, но я не прошу здесь проверку кода ... кроме того, я привык использовать Задачи, поэтому я бездельничаю с ожиданием, но я не совсем уверен, что использую его правильно)

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

Любое руководство здесь будет высоко оценено!


person Jordan    schedule 23.04.2014    source источник
comment
Как ни странно, я бы сказал, что Roslyn - правильный инструмент для работы, но вам может потребоваться расширить его с помощью пользовательских частей, чтобы сохранить информацию, которую вы считаете отсутствующей (в какой-то момент даже компилятор Roslyn будет знать, что такое методы расширения так далее). Возможно, вы тоже прокладываете путь здесь, так как это так ново, пожалуйста, отчитайтесь и ответьте на свой вопрос, если вы добьетесь чего-либо с помощью этого внешнего ТАК :-)   -  person Adam Houldsworth    schedule 23.04.2014
comment
Рассмотрите возможность написания посетителя синтаксического узла.   -  person SLaks    schedule 23.04.2014
comment
@AdamHouldsworth Я всегда отчитываюсь с ответом, если я что-то с ним добьюсь :) Кроме того, этот проект будет полностью с открытым исходным кодом, и я поделюсь ссылкой на GitHub, как только пройду мимо P.O.C.   -  person Jordan    schedule 23.04.2014
comment
@SLaks, это полезно, спасибо!   -  person Jordan    schedule 23.04.2014
comment
@Мартин Рад это слышать, удачи!   -  person Adam Houldsworth    schedule 23.04.2014
comment
После беглого просмотра вашего кода я думаю, что вы просто смотрите на вызовы метода в том же синтаксическом дереве, что и метод, а не во всем решении. Вместо этого вы можете рассмотреть возможность использования SymbolFinder.FindReferencesAsync(..).   -  person andyp    schedule 23.04.2014
comment
@andyp да, я думаю, вы правы ... это может объяснить, почему я не получаю статические методы. Я попробую!   -  person Jordan    schedule 23.04.2014


Ответы (1)


Используя methodSymbol в методе ProcessClass, я принял предложение Энди и придумал следующее (хотя я думаю, что это может быть более простой способ):

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

и результирующее изображение, сгенерированное путем вставки выходного текста в js-sequence-diagrams ( Я обновил суть github, указав полный исходный код, если он кому-то покажется полезным - я исключил параметры метода, чтобы диаграмма была легко воспринята, но при желании их можно снова включить):

Редактировать:

Я обновил код (см. github gist), поэтому теперь вызовы отображаются в порядке они были созданы (на основе начального местоположения вызываемого метода из вызывающего метода с помощью результатов FindCallersAsync):

введите здесь описание изображения

person Jordan    schedule 23.04.2014