【问题标题】:How to access invocations through extension methods, methods in static classes and methods with ref/out parameters with Roslyn如何使用 Roslyn 通过扩展方法、静态类中的方法和带有 ref/out 参数的方法访问调用
【发布时间】:2014-04-25 00:54:38
【问题描述】:

我正在创建一个用于创建 .NET UML 序列图的开源项目,该项目利用了一个名为 js-sequence-diagrams 的 javascript 库。我不确定 Roslyn 是否适合这项工作,但我想我会试一试,所以我整理了一些概念验证代码,它试图获取所有方法及其调用,然后以如下形式输出这些调用可以通过 js-sequence-diagrams 来解释。

代码会生成一些输出,但它不会捕获所有内容。我似乎无法通过扩展方法捕获调用,静态类中的静态方法调用。

我确实看到了带有 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;
        }
    }
}

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

【问题讨论】:

  • 有趣的是,我会说 Roslyn 是完成这项工作的正确工具,但您可能需要使用自定义部件对其进行扩展,以保留您认为缺失的信息(有时甚至 Roslyn编译器将知道什么是扩展方法等)。您可能也在这里开辟了道路,因为它太新了,如果您在 SO 之外遇到任何问题,请报告并回答您自己的问题 :-)
  • 考虑写一个语法节点访问器。
  • @AdamHouldsworth 如果我得到任何答案,我总是会报告答案:) 此外,这个项目将完全开源,一旦我通过 P.O.C.,我将分享 GitHub 链接。
  • @SLaks 很有帮助,谢谢!
  • 在浏览完您的代码后,我认为您只是在查看方法的调用 在与方法相同的语法树中not 在整个解决方案。您可能需要考虑改用 SymbolFinder.FindReferencesAsync(..)。

标签: c# .net roslyn


【解决方案1】:

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

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 gist - 我排除了方法参数,因此图表很容易消化,但是这些可以选择重新打开):

编辑:

我已经更新了代码(参见github gist),所以现在调用按它们进行的顺序显示(基于调用方法中被调用方法的跨度开始位置,通过 FindCallersAsync 的结果):

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-06
    • 2016-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    相关资源
    最近更新 更多