如何使用Roslyn通过扩展方法,静态类中的方法以及具有ref/out参数的方法访问调用 [英] How to access invocations through extension methods, methods in static classes and methods with ref/out parameters with Roslyn

查看:46
本文介绍了如何使用Roslyn通过扩展方法,静态类中的方法以及具有ref/out参数的方法访问调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建一个用于创建.NET UML序列图的开源项目,该项目利用了一个称为js-sequence-diagrams的JavaScript库.我不确定罗斯林是否是适合该工作的工具,但我想我会尝试一下,因此我整理了一些概念验证代码,试图获取所有方法及其调用,然后以某种形式输出这些调用可以用js-sequence-diagrams解释.

I'm working on creating an open source project for creating .NET UML Sequence Diagrams that leverages a javascript library called js-sequence-diagrams. I am not sure Roslyn is the right tool for the job, but I thought I would give it a shot so I have put together some proof of concept code which attempts to get all methods and their invocations and then outputs these invocations in a form that can be interpreted by js-sequence-diagrams.

该代码生成一些输出,但不能捕获所有内容.我似乎无法通过扩展方法捕获调用,即在静态类中调用静态方法.

The code generates some output, but it does not capture everything. I cannot seem to capture invocations via extension methods, invocations of static methods in static classes.

我确实看到了带有 out 参数的方法的调用,但是没有以任何形式扩展 BaseMethodDeclarationSyntax

I do see invocations of methods with out parameters, but not in any form that extends the BaseMethodDeclarationSyntax

这里是代码(请记住,这是概念验证代码,因此我没有完全遵循最佳实践,但是我在这里不要求进行代码审查……而且,我习惯于使用Tasks,所以我忙着等待,但是还不能完全确定我是否正确使用了它)

Here is the code (keep in mind this is proof of concept code and so I did not entirely follow best-practices, but I am not requesting a code review here ... also, I am used to using Tasks so I am messing around with await, but am not entirely sure I am using it properly yet)

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

这里的任何指导将不胜感激!

Any guidance here would be much appreciated!

推荐答案

ProcessClass 方法中使用 methodSymbol ,我采纳了Andy的建议,并提出了以下建议(尽管我想可能会有一个更简单的方法来解决这个问题):

Using the methodSymbol in the ProcessClass method I took Andy's suggestion and came up with the below (although I imagine there may be an easier way to go about this):

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 a>(我已经用所有的完整信息更新了 github gist 的完整源代码,如果有人觉得有用的话-我排除了方法参数,因此该图易于理解,但是可以有选择地将其重新打开):

and the resulting image generated by plugging the output text into js-sequence-diagrams (I have updated the github gist with the full source for this should anyone find it useful - I excluded method parameters so the diagram was easy digest, but these can optionally be turned back on):

我已经更新了代码(请参见 github gist ),因此现在显示了调用按照创建顺序(根据来自FindCallersAsync结果的调用方法中被调用方法的span起始位置):

I've updated the code (see the github gist) so now calls are shown in the order they were made (based on the span start location of a called method from within the calling method via results from FindCallersAsync):

这篇关于如何使用Roslyn通过扩展方法,静态类中的方法以及具有ref/out参数的方法访问调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆