Using Mono.Cecil I want to rewrite the following property:
public string FirstName
{
get { return _FirstName; }
set
{
_FirstName = value;
}
}
to this:
public string FirstName
{
get { return _FirstName; }
set
{
if (System.Object.Equals(_FirstName, value))
{
return;
}
_FirstName = value;
}
}
This is just a snippet of what the rewrite will be but it is where I'm having a problem.
Using Reflector I can see the following code rewrites the property as required except the call to System.Object.Equals(). If expect the IL code to be:
call bool [mscorlib]System.Object::Equals(object, object)
but it is being written as:
call instance void RewriteSharp.Person::.ctor()
The code to write the call to System.Object.Equals is:
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
The method used to init objectEqualsMethodReference is:
private static MethodReference GetSystemObjectEqualsMethodReference(
AssemblyDefinition assembly
)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
It seems to me setMethodWriter.Create() or GetSystemObjectEqualsMethodReference() is incorrect and no amount of debugging has solved the problem.
The property being written and code to rewrite the property have the same framework target. 3.5 and 4.0 both fail.
I'm using the master branch https://github.com/jbevain/cecil to build Mono.Cecil.
Complete Code Listing
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Linq;
namespace RewriteNotifyPropertyChanged
{
class Program
{
static void Main(string[] args)
{
var rewrite = "..\\RewriteSharp.dll";
var rewritten = "..\\RewritenSharp.dll";
var typeName = "Person";
var propertyName = "FirstName";
var assembly = AssemblyDefinition.ReadAssembly(rewrite);
var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName);
var propertyDefintion = typeDefinition.Properties
.Single(p => p.Name == propertyName);
var setMethodWriter = propertyDefintion.SetMethod.Body.GetILProcessor();
var backingFieldReference = GetBackingFieldReference(typeDefinition, propertyName);
var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
var firstExistingInstruction = setMethodWriter.Body.Instructions[0];
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldarg_0));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldfld, backingFieldReference));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldarg_1));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Brfalse_S, firstExistingInstruction));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ret));
assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });
Console.WriteLine("Done.");
Console.ReadKey();
}
private static MethodReference GetSystemObjectEqualsMethodReference(
AssemblyDefinition assembly
)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
private static FieldReference GetBackingFieldReference(
TypeDefinition typeDefinition,
string propertyName
)
{
var fieldName = "_" + propertyName;
var fieldReference = typeDefinition.Fields.Single(f => f.Name == fieldName);
return fieldReference;
}
}
}
Cecil, unlike System.Reflection, makes the distinction between a reference and a definition, and those are scoped per module. It means that you can't simply use a MethodDefinition from another module inside your own. You have to create a proper reference to it. This is a process called importing in the Cecil terminology.
Concretely, GetSystemObjectEqualsMethodReference returns a method defined in the corlib, you need to create a reference to it in your module :
Replacing:
var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
by:
var objectEqualsMethodReference = assembly.MainModule.Import (GetSystemObjectEqualsMethodReference(assembly));
And fixing the IL should make it work.
Also, while I'm at it, the method:
private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
Would be better written as:
private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
var #object = assembly.MainModule.TypeSystem.Object.Resolve ();
return #object.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.MetadataType == MetadataType.Object
&& m.Parameters[1].ParameterType.MetadataType == MetadataType.Object);
}
And
assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });
Doesn't make much sense if you don't pass new ReaderParameters { ReadSymbols = true } when reading the assembly.
You could take a look at KindOfMagic codeplex project.
It does almost the same, but little bit better - it does not call Object.Equals(), but equality operator defined on the target type.
http://kindofmagic.codeplex.com
Related
I'm trying to call the equivalent of this code via reflection:
prop.WhenAnyValue(s => s.Value).Skip(1).Subscribe(...);
This is the reflection code I use to "solve" the WhenAnyValue part:
MethodInfo[] whenAnyMethods = typeof(WhenAnyMixin).GetMethods();
MethodInfo whenAnyMethod = new Func<MethodInfo>(() =>
{
foreach (var mi in whenAnyMethods)
{
if (mi.Name == "WhenAnyValue" && mi.GetGenericArguments().Length == 2)
{
return mi;
}
}
return null;
})();
MethodInfo whenAnyMethodGeneric = whenAnyMethod.MakeGenericMethod(new Type[] { containingType, propertyType });
var whenAnyResult = whenAnyMethodGeneric.Invoke(null, new object[] { containingObject, expression });
The containing object derives from ReactiveObject (ReactiveUI).
The type of the result is exactly the same as when I'm calling WhenAnyValue directly on a property I'm interested in (without reflection), so I think I got this part right. :)
Now I'm trying to skip:
MethodInfo[] methods = typeof(Observable).GetMethods();
MethodInfo skipMethod = new Func<MethodInfo>(() =>
{
foreach (var mi in methods)
{
if (mi.Name == "Skip" && mi.GetParameters().Length == 2 && mi.GetParameters()[1].ParameterType == typeof(int))
{
return mi;
}
}
return null;
})();
MethodInfo skipMethodGeneric = skipMethod.MakeGenericMethod(new Type[] { whenAnyResult.GetType() });
var skipResult = skipMethodGeneric.Invoke(null, new object[] { whenAnyResult, 1 }); <<< this line throws the exception
On execution of the last line an exception is thrown:
System.ArgumentException: "Object of type 'System.Reactive.Linq.ObservableImpl.Select`2+Selector[ReactiveUI.IObservedChange`2[type1,type2],type3]' cannot be converted to type 'System.IObservable`1[System.Reactive.Linq.ObservableImpl.Select`2+Selector[ReactiveUI.IObservedChange`2[type1,type2],type3]]'."
And here I'm stuck. What am I missing?
I want to be able to pass FieldName as a string into Method that updates a table. I have looked at Linq.Expressions but this doesn't seem to expose all the details I need about the field Name and dataType and I don't really want to go through the existing string builder solution that executes a sql command directly.
public static Main(string[] args)
{
UpdateLicenceField(2284, "laststoragefullalert", DateTime.Now);
UpdateLicenceField(2284, "numberofalerts", int(tooMany);
UpdateLicenceField(2284, "lastalertmessage", "Oops");
}
public static void UpdateLicenceField(int LicenceID, string FieldName, object value)
{
using (myContext db = new MyContext())
{
Licence licence = db.Licence.Where(x => x.ID == LicenceID &&
x.Deleted == false).FirstOrDefault();
// so db.Licence has fields .laststoragefullalert .numberofalerts .lastalertmessage
// (in real life this has hundred of settings and we very often just want to update one)
// I'm tring to get a single Method that will act dynamically like the current ADO function that creates a SQL string and executes that.
// 1. check that the FieldName type is the same type as the object passed in value.
// 2. update that FieldName with value and Saves the Licence table.
}
}
You can use "System.Linq.Dynamic.Core" library from https://github.com/StefH/System.Linq.Dynamic.Core and use:
Licence licence = db.Licence.Where(FieldName + "==#0", fieldValue).FirstOrDefault();
or use a method like this:
public Expression<Func<TEntity, bool>> GetPredicate(string methodName, string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(TEntity), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod(methodName, new[] { typeof(string) });
if (method == null)
{
var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == methodName);
foreach (var m in containsMethods)
{
if (m.GetParameters().Count() == 2)
{
method = m;
break;
}
}
}
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<TEntity, bool>>(containsMethodExp, parameterExp);
}
Licence licence = db.Licence.Where(GetPredicate("Contains", fieldName, fieldValue)).FirstOrDefault();
I hope it helps you.
Thanks very much for your responses. Using a bit from both Renan and Roberto's reply I put something together that does the job. It's obviously not ideal to update in this way but this is instead of having 50 or 60 functions with similar code within an automated process. There's still error checking and validations to be added but this will reduce my codebase considerably.
Thanks again Glenn.
public static int Main(string[] args)
{
UpdateLicenceField(2284, "LastStorageFullAlert", DateTime.Now);
UpdateLicenceField(2284, "IsProcessing", true);
UpdateLicenceField(2284, "RegSource", "this is a string");
UpdateLicenceField(2284, "ProcessFilesDelay", 200);
return 1;
}
public static void UpdateLicenceField(int LicenceID, string Field, object Value)
{
using (BACCloudModel BACdb = new BACCloudModel())
{
Licence licence = BACdb.Licence.Where(x => x.ID == LicenceID && x.Deleted == false).FirstOrDefault();
if (licence != null)
{
var fieldvalue = licence.GetType().GetProperty(Field);
var ftype = fieldvalue.PropertyType;
var vtype = Value.GetType();
if (ftype == vtype)
{
object[] Values = { Value };
licence.GetType().GetProperty(Field).SetMethod.Invoke(licence, Values);
BACdb.SaveChanges();
}
}
}
}
Thanks to Roslyn, having access to .Net compiler, in our project Gcop we need to have list of references where are calling a method.
VS IDE shows the reference places very suitable like this:
Actually I want to understand which class/name space and even assembly is calling my method by Roslyn C# syntax.
Currently I have access to MethodSymbol here :
var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
what should I write to get access to references of this method?
//added recently for double check
var solutionPath = Utilities.DteExtensions.SolutionFullPath;
var msWorkspace = Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
var result = new List<ReferenceLocation>();
var refrences = SymbolFinder.FindReferencesAsync(methodSymbol, solution).Result;
foreach (var refernc in refrences)
{
foreach (var location in refernc.Locations)
{
result.Add(location);
}
}
///???????? why the result is empty ?
You're looking for the FindReferencesAsync() methods on the SymbolFinder class.
I tested this approach but is not quick , and can not find some syntax
protected override void Analyze(SyntaxNodeAnalysisContext context)
{
NodeToAnalyze = context.Node;
Method = context.Node as MethodDeclarationSyntax;
// only public method should be checked
if (!IsModifiersValid) return;
var methodSymbol = context.SemanticModel.GetDeclaredSymbol(Method) as IMethodSymbol;
var solutionPath = Utilities.DteExtensions.SolutionFullPath;
var msWorkspace = Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
// looking in all projects inside of one solution
var allDocumentsInEntireSolution = solution.Projects.SelectMany(it => it.Documents);
//skip rule when in entire solution we have web form project
if (allDocumentsInEntireSolution.Any(x => x.Name == "Default.aspx.cs")) return;
//Looking for all references
var refrencesFound = FindAllMethodReferences(Method.GetName(), solution);
if (refrencesFound.Count() ==0)
ReportDiagnostic(context, Method);
else
{
var xyx = refrencesFound.Count();
}
}
IEnumerable<ReferenceLocation> FindAllMethodReferences(string methodName, Solution solution)
{
IMethodSymbol methodSymbol = null;
bool found = false;
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
var model = document.GetSemanticModelAsync().Result;
var methodInvocation = document.GetSyntaxRootAsync().Result;
try
{
var nodes = methodInvocation.DescendantNodes().OfType<InvocationExpressionSyntax>().Where(x => x.Expression.ToString().Contains(methodName));
foreach (var node in nodes)
{
if (node == null) continue;
var member = node?.Expression as MemberAccessExpressionSyntax;
if (member == null)
continue;
var name = member?.Name?.ToString();
if (name.IsEmpty()) continue;
if (name != methodName) continue;
methodSymbol = model.GetSymbolInfo(node).Symbol as IMethodSymbol;
found = true;
break;
}
}
catch (Exception exp)
{
// Swallow the exception of type cast.
// Could be avoided by a better filtering on above linq.
continue;
}
}
if (found) break;
}
if (found == false) return Enumerable.Empty<ReferenceLocation>();
if (methodSymbol == null) return Enumerable.Empty<ReferenceLocation>();
var result = new List<ReferenceLocation>();
var refrences = SymbolFinder.FindReferencesAsync(methodSymbol, solution).Result;
foreach (var refernc in refrences)
{
foreach (var location in refernc.Locations)
{
result.Add(location);
}
}
return result;
}
I prefer to change following to make it work:
var nodes = methodInvocation.DescendantNodes().OfType<InvocationExpressionSyntax>()
.Where(x => x.Expression.ToString().Contains(methodName));
To:
var nodes = new List<InvocationExpressionSyntax>();
foreach (var methodInvocationExpression in methodInvocation.DescendantNodes().OfType<InvocationExpressionSyntax>())
{
if(methodInvocationExpression.DescendantNodes().OfType<MemberAccessExpressionSyntax>().Any(x => x.Name.Identifier.ValueText == methodName))
{
nodes.Add(methodInvocationExpression);
}
}
I have a fairly simple method:
public static LinkItemCollection ToList<T>(this LinkItemCollection linkItemCollection)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is T)
newCollection.Add(linkItem);
}
return newCollection;
}
This works fine - I can call the method and provide a type as T. However, what I want to be able to do is to be able to specify multiple types. I therefore, wrongly, assumed I could refactor the method to:
public static LinkItemCollection ToList(this LinkItemCollection linkItemCollection, Type[] types)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
foreach (var type in types)
{
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is type)
newCollection.Add(linkItem);
}
}
return newCollection;
}
However, Visual Studio is showing it cannot resolve the symbol for type on the line if(content is type).
I know I'm doing something wrong, and I'm guessing I need to use Reflection here.
What you're looking for is:
type.IsAssignableFrom(content.GetType())
is is only used for checking against a type known at compile time, not at runtime.
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.
I do see invocations of methods with out parameters, but not in any form that extends the BaseMethodDeclarationSyntax
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!
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;
}
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):
Edit:
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):