I'm attempting to find all locations in a solution that calls the method IBus.Publish<T> (from NServiceBus). So far this is working:
IMethodSymbol method = ... [IBus.Publish methodsymbol resolved];
var callers = method.FindCallers(solution, new CancellationToken());
This results in a IEnumerable<SymbolCallerInfo> and I get all the correct references to this method.
How would I now go about to get the generic argument IBus.Publish was called with? Do I have to parse the sourcetree manually, or does it exist some Roslyn magic I can leverage?
Example:
In my code I have:
IBus _bus;
_bus.Publish<IMyMessage>(msg => { msg.Text = "Hello world"});
I'm interested in getting the IMyMessage type.
Greatly appreciate the help!
You can use a SemanticModel to go from the SyntaxNode for the call to the actual MethodSymbol, and then you can just read the TypeArguments property to get the TypeSymbols for the arguments. That will even work if the arguments aren't specified explicitly, since the SemanticModel will perform type inference.
For example:
var callers = method.FindCallers(solution, CancellationToken.None);
foreach (var caller in callers)
{
foreach (var location in caller.Locations)
{
if (location.IsInSource)
{
var callerSemanticModel = solution
.GetDocument(location.SourceTree)
.GetSemanticModel();
var node = location.SourceTree.GetRoot()
.FindToken(location.SourceSpan.Start)
.Parent;
var symbolInfo = callerSemanticModel.GetSymbolInfo(node);
var calledMethod = symbolInfo.Symbol as IMethodSymbol;
if (calledMethod != null)
{
var arguments = calledMethod.TypeArguments;
}
}
}
}
I'm not really familiar with the IMethodSymbol and ISymbol interfaces, but here is an another way to get the generic arguments from a InvocationExpressionSyntax
var methodRef = (InvocationExpressionSyntax)find_method();
var genericArguments = methodRef.DescendantNodes().OfType<GenericNameSyntax>().FirstOrDefault();
if (genericArguments != null)
foreach (var g_arg in genericArguments.TypeArgumentList.Arguments)
Console.WriteLine(g_arg);
static InvocationExpressionSyntax find_method()
{
var code = new StreamReader("..\\..\\Tests.cs").ReadToEnd();
SyntaxTree tree = SyntaxTree.ParseText(code);
var root = tree.GetRoot();
//find your methods here
return (InvocationExpressionSyntax)root.DescendantNodes().ToArray()[88];
}
Related
I am writing a function that takes any concrete or constructed Type, say for example typeof(ValueTuple<Nullable<System.Int32>, double, List<string>>) and returns a string that is the reduced C# syntax representation of that type (i.e. (int?, double, List<string>) in this example).
Here's what I have so far:
public static string ToCSharpString(this Type type, string[] usingNamespaces = null, Assembly[] usingAssemblies = null)
{
var compilationUnit = SyntaxFactory.CompilationUnit();
if (usingNamespaces != null)
{
compilationUnit = compilationUnit.AddUsings(
Array.ConvertAll(usingNamespaces, n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n))));
}
else
{
compilationUnit = compilationUnit.AddUsings(
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")));
}
MetadataReference[] metadataReferences;
if (usingAssemblies != null)
{
metadataReferences = Array.ConvertAll(usingAssemblies, u => MetadataReference.CreateFromFile(u.Location));
}
else
{
metadataReferences = new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(type.Assembly.Location)
};
}
TypeSyntax typeName;
using (var provider = new CSharpCodeProvider())
{
typeName = SyntaxFactory.ParseTypeName(provider.GetTypeOutput(new CodeTypeReference(type)));
}
var field = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(typeName).WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("field")))));
compilationUnit = compilationUnit.AddMembers(
SyntaxFactory.ClassDeclaration("MyClass").AddMembers(
field))
.NormalizeWhitespace();
var tree = compilationUnit.SyntaxTree;
var compilation = CSharpCompilation.Create("MyAssembly", new[] { tree }, metadataReferences);
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var typeSymbol = semanticModel.GetDeclaredSymbol(compilationUnit
.DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
.Members.OfType<FieldDeclarationSyntax>().Single()
.Declaration.Type);
return typeSymbol.ToDisplayString(new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
}
I'm attempting to string together a couple of known methods for converting types.
Type -> Fully Qualified Name
via CodeDOM's CSharpCodeProvider.GetTypeOutput
Fully Qualified Name -> TypeSyntax
via Rosly's SyntaxFactory.ParseTypeName
Now I would like to use .ToDisplayString() with a few different options, but I can't find a type node for my type that doesn't return null from the semantic model.
How can I format a TypeSyntax using a SymbolDisplayFormat?
In addition, I expect that this will change System.Int32 -> int, however, it won't automatically fix instances of Nullable<T> or ValueTuple<T1...>
How can I execute the appropriate code analysis rules to replace these type names?
The docs state GetDeclaredSymbol only works for
any type derived from MemberDeclarationSyntax, TypeDeclarationSyntax, EnumDeclarationSyntax, NamespaceDeclarationSyntax, ParameterSyntax, TypeParameterSyntax, or the alias part of a UsingDirectiveSyntax
Yours appears to be QualifiedNameSyntax which seems pretty much in line with the expected input but clearly means something else to Roslyn (I will admit I didn't bother checking whether it actually inherits from one of the expected types).
However, getting TypeInfo instead, seems to get your particular example working:
var typeSymbol = semanticModel.GetTypeInfo(compilationUnit // just changed this method
.DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
.Members.OfType<FieldDeclarationSyntax>().Single()
.Declaration.Type);
return typeSymbol.Type.ToDisplayString(new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); // I'm getting "(int?, double, List)" here
I'm new to Roslyn. I'm trying to write an analyzer that detects when Select is being iterated through in a foreach loop, e.g.
foreach (TResult item in source.Select(x => x.Foo()))
{
...
}
and I'm writing a code fix provider that transforms such statements to
foreach (TSource __ in source)
{
TResult item = __.Foo();
...
}
Here is the code I have so far for the code fix provider. (It's somewhat long; the InlineSimpleLambdaExpressionAsync is where the meat of the changes are, but I included everything from RegisterCodeFixesAsync for context.)
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var selectInvocation = (InvocationExpressionSyntax)syntaxRoot.FindNode(diagnosticSpan);
var forEach = (ForEachStatementSyntax)selectInvocation.Parent;
context.RegisterCodeFix(
CodeAction.Create(
title: Title,
createChangedDocument: ct => InlineSelectorAsync(context.Document, forEach, selectInvocation, ct),
equivalenceKey: Title),
diagnostic);
}
private static Task<Document> InlineSelectorAsync(
Document document,
ForEachStatementSyntax forEach,
InvocationExpressionSyntax selectInvocation,
CancellationToken ct)
{
var selectorExpr = selectInvocation.ArgumentList.Arguments.Single().Expression;
switch (selectorExpr.Kind())
{
case SyntaxKind.SimpleLambdaExpression:
// This will be the most common case.
return InlineSimpleLambdaExpressionAsync(
document,
forEach,
selectInvocation,
(SimpleLambdaExpressionSyntax)selectorExpr,
ct);
}
return Task.FromResult(document);
}
private static async Task<Document> InlineSimpleLambdaExpressionAsync(
Document document,
ForEachStatementSyntax forEach,
InvocationExpressionSyntax selectInvocation,
SimpleLambdaExpressionSyntax selectorExpr,
CancellationToken ct)
{
var smodel = await document.GetSemanticModelAsync(ct).ConfigureAwait(false);
// First, change the foreach to iterate directly through the source enumerable,
// and remove the Select() method call.
// NOTE: GetSimpleMemberAccessExpression() is an extension method I wrote.
var sourceExpr = selectInvocation.GetSimpleMemberAccessExpression()?.Expression;
if (sourceExpr == null)
{
return document;
}
// Figure out the element type of the source enumerable.
var sourceTypeSymbol = smodel.GetTypeInfo(sourceExpr, ct).Type;
Debug.Assert(sourceTypeSymbol != null);
// NOTE: GetElementType is an extension method I wrote.
var elementTypeSymbol = sourceTypeSymbol.GetElementType(smodel);
// Now, update the foreach. Replace the element type of the selected enumerable
// with the element type of the source. Make '__' the identifier (TODO: Improve on this).
var ident = SyntaxFactory.Identifier("__");
int position = forEach.Type.SpanStart;
var elementTypeSyntax = SyntaxFactory.IdentifierName(elementTypeSymbol.ToMinimalDisplayString(smodel, position));
var newForEach = forEach
.WithType(elementTypeSyntax)
.WithIdentifier(ident)
.WithExpression(sourceExpr);
// Now, we have to take the selector and inline it.
var selectorBody = selectorExpr.Body as ExpressionSyntax;
Debug.Assert(selectorBody != null);
var selectorParam = selectorExpr.Parameter;
selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__")); // This doesn't work.
var selectorStatement = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
type: forEach.Type,
variables: SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.VariableDeclarator(
identifier: forEach.Identifier,
argumentList: null,
initializer: SyntaxFactory.EqualsValueClause(selectorBody)))));
var forEachStatment = forEach.Statement as BlockSyntax;
// TODO: Consider supporting non-block statements? Would that happen with no braces?
if (forEachStatment == null)
{
return document;
}
newForEach = newForEach.WithStatement(
// NOTE: InsertStatements is an extension method I wrote.
forEachStatment.InsertStatements(0, selectorStatement));
// Update the syntax root and the document.
var syntaxRoot = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false);
syntaxRoot = syntaxRoot.ReplaceNode(forEach, newForEach);
return document.WithSyntaxRoot(syntaxRoot);
}
When I run the code fix on the following code:
foreach (var item in array.Select(x => x.ToString()))
{
}
I get:
foreach (int __ in array)
{
var item = x.ToString();
}
Which (aside from the whitespace) is almost exactly what I want, except I can't figure out how to substitute the parameter x with __. In particular, this line doesn't seem to be working:
selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__"));
I'm trying to replace the Body of the SimpleLambdaExpression with its Parameter, which does nothing. I suspected that this wouldn't work, but how else can I substitute usages of x with __ in the lambda?
You are trying to replace the selectorParam node that is missing in the selectorBody, because in your case selectorBody is a call of the method (x.ToString() - InvocationExpressionSyntax). You can get the replacement by rewriting the code as follows:
var selectorBody = selectorExpr.Body as InvocationExpressionSyntax;
var nodeToReplace = (selectorBody.Expression as MemberAccessExpressionSyntax).Expression;
selectorBody = selectorBody.ReplaceNode(nodeToReplace, SyntaxFactory.IdentifierName("__"));
Or, if you want to rely on the parameters, then you should replace not SyntaxNode, but SyntaxTokens in the following way:
var selectorBody = selectorExpr.Body as ExpressionSyntax;
var selectorParam = selectorExpr.Parameter.Identifier;
IEnumerable<SyntaxToken> tokensToReplace = selectorBody.DescendantTokens()
.Where(token => String.Equals(token.Text, selectorParam.Text));
selectorBody = selectorBody.ReplaceTokens(tokensToReplace, (t1, t2) => SyntaxFactory.IdentifierName("__").Identifier);
I a bunch of C# Files that I want to migrate to a new version of an internal framework we use for development. So far I have been able to accomplish certain things But there is a particular method invocation that i have to modify to use await modifier and the method calls come in different flavors like so
var a = EntityService.GetBy(Entities.INSTANT_ISSUANCE_REQUEST, requestFilter);
var b = EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
I have to modify the above method calls so that they use the following syntax
var a = await EntityService.GetBy(Entities.INSTANT_ISSUANCE_REQUEST, requestFilter);
var b = await EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
I am using Roslyn CSharpSyntaxWriter to traverse the syntax tree.
public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
var variableDeclarations =
from i in node.DescendantNodes().OfType<VariableDeclaratorSyntax>()
where i.DescendantNodes().OfType<EqualsValueClauseSyntax>().Any<EqualsValueClauseSyntax>(
p => p.DescendantNodes().OfType<InvocationExpressionSyntax>().Any<InvocationExpressionSyntax>())
select i;
foreach (var syntax in variableDeclarations)
{
var equalsToken = syntax.DescendantNodes();
//now we have the equals token
foreach (var syntaxNode in equalsToken)
{
if (syntaxNode is EqualsValueClauseSyntax)
{
var equalsValueSyntax = syntaxNode.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocationExpressionSyntax in equalsValueSyntax)
{
var simpleMemberAcessExpressions = invocationExpressionSyntax
.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Where(i => i.Identifier.Text == "EntityService");
foreach (var simpleMemberAcessExpression in simpleMemberAcessExpressions)
{
var newExpression = $"{invocationExpressionSyntax.ToFullString()}";
Console.WriteLine(newExpression);
//TODO: Modify the Node
}
}
}
}
}
return base.VisitLocalDeclarationStatement(node);
}
My problem here is the output in Console is somewhat duplicative
EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject()).FirstOrDefault();
EntityService.GetBy(Entities.ISSUANCE_SETTINGS, new DejaVuObject())
Both refer to same InvocationExpression but because of the FirstOrDefault() it shows up twice in the console output
I would like for a way to filter the results so that they contain only unique Method Invocations and perform the actual modification of the method call by adding the await modifier and updating the node.
first of all, why override VisitLocalDeclarationStatement? Looks like you should be overriding VisitInvocationExpression.
You shouldn't be doing this:
var simpleMemberAcessExpressions =
invocationExpressionSyntax.DescendantNodes().OfType<IdentifierNameSyntax>()
Instead, it should be something like:
var identifier =
(node.Expression as MemberAccessExpressionSyntax)?.Expression as IdentifierNameSyntax;
if (identifier != null && identifier.Identifier.Text == "EntityService")
{
// process the invocation expression
}
Assuming the invocation expression is called node, your new expression is probably
var newExpression =
SyntaxFactory.ParenthesizedExpression(SyntaxFactory.AwaitExpression(node))
.WithAdditionalAnnotations(Simplifier.Annotation)
.WithAdditionalAnnotations(Formatter.Annotation);
I try to make code completion with Roslyn and AvalonEdit.
For example, user have code:
public void Completion(int i,int j) { }
And he types:
Completion(
So, i want to get arguments of method (int i, int j) and make code completion.
I write simple code, wich works with '.' and may be this code will work for '(' letter?
public List<ICompletionData> GetCompletionData(String code,int offset,CompletionType completionType)
{
var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var compilation = CSharpCompilation.Create("foo")
.AddReferences(Mscorlib)
.AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var textSpan = GetTextSpan(offset,1);// '.' or '(' coordinates
ITypeSymbol lhsType = null;
if (completionType == CompletionType.DotCompletion)
{
var memberAccessNode = (MemberAccessExpressionSyntax)syntaxTree.GetRoot()
.DescendantNodes(textSpan).Last();
lhsType = semanticModel.GetTypeInfo(memberAccessNode.Expression).Type;
}
else if(completionType==CompletionType.ArgumentListCompletion)
{
var arr = syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var argumentListMode = (ArgumentListSyntax)syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var directive = argumentListMode.GetFirstDirective();
var arrgs=argumentListMode.Arguments;
//lhsType = semanticModel.GetTypeInfo(directive).Type;
//how to get lhsType?
}
if (lhsType == null)
return new List<ICompletionData>();
List<ICompletionData> completionDataColl = new List<ICompletionData>();
// and here i make completion data
foreach (var symbol in lhsType.GetMembers())
{
if (!symbol.CanBeReferencedByName
|| symbol.DeclaredAccessibility != Accessibility.Public
|| symbol.IsStatic)
continue;
}
}
The problem is, that i can not get ITypeSymbol lhsType. It is null.
How to get lhsType?
Or, maybe i should use another way?
I don't know the code completion (I couldn't find this class called CompletionType) itself, but here is a way based on Roslyn only: semantic model and the method invocation, which I believe you have available (make the method call string a InvocationExpressionSyntax)
To obtain the arguments of a method you can get its SymbolInfo from the semantic model. Then you get its symbol. A symbol contains the list of parameters(arguments).
You can call SemanticModel.GetSymbolInfo()
The result will provide you a symbol or candidate symbols (if it is an overloaded method).
A method symbol will provide the list of parameters, which is the arguments of that method.
I need to find methods that match certain rules such as
has to be have return type void
has to be named "Set"
has to accept only one parameter
parameter type needs to match the type provided
I started going down the following route, but is just seems too much code. I wonder if there is a better way?
//find type name of the property
foreach (var propertySymbol in propertiesSymbols)
{
var propertyTypeSyntax =
((PropertyDeclarationSyntax) propertySymbol.DeclaringSyntaxNodes.First()).Type;
var propertyTypeInfo = semanticModel.GetTypeInfo(propertyTypeSyntax);
//find method with a name Set that accepts this type of the property
var allSetMethodsSymbols = classSymbol.GetMembers()
.Where(m => m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.ToList();
foreach (var setMethodSymbol in allSetMethodsSymbols)
{
var methodDeclarationSyntax =
((MethodDeclarationSyntax) setMethodSymbol.DeclaringSyntaxNodes.First());
var expressionSyntax =
methodDeclarationSyntax.DescendantNodes().OfType<ExpressionSyntax>().First();
var typeInfo = semanticModel.GetTypeInfo(expressionSyntax);
var typeName = typeInfo.Type.Name;
if (typeName == "Void")
{
//now we know it is a method named "Set" and has return type "Void"
//let's see if parameter matches
var parameterSymbols =
methodDeclarationSyntax.DescendantNodes().OfType<ParameterSyntax>()
.ToList();
if (parameterSymbols.Count() == 1)
{
//this one has one parameter
//now let's see if it is of the type needed
var exprSyntax = ((ParameterSyntax) parameterSymbols.First())
.DescendantNodes().OfType<ExpressionSyntax>().First();
var parameterTypeInfo = semanticModel.GetTypeInfo(exprSyntax);
if (parameterTypeInfo.Type.Equals(propertyTypeInfo.Type))
{
//execute method rewriter
}
}
}
}
}
Solution as suggested by Jason:
var propertyTypeInfo = propertySymbol.Type;
//find method with a name Set that accepts this type of the property
IEnumerable<MethodSymbol> allSetMethodsSymbols = classSymbol
.GetMembers()
.Where(m =>m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.Cast<MethodSymbol>();
var setMethod = allSetMethodsSymbols
.Single(x => x.ReturnsVoid
&& x.Parameters.Count == 1
&& x.Parameters.First().Type == propertyTypeInfo);
Yes, you're switching back and forth between our symbol model and syntax, which is making this more difficult than it needs to be. Cast those symbol objects you are getting from GetMembers to MethodSymbol (once checking that they're a method). Once you've got the MethodSymbol, you can just check the .ReturnType property to get the return type -- don't go to syntax and re-get it that way. Or, just use the handy .ReturnsVoid property for your scenario. Similarly, MethodSymbol has a .Parameters property you can use to get the parameters -- don't go back to syntax for that either.