Roslyn basis

Syntax Tree

All the code could be represented as a very detailed tree.

image

Using Roslyn, we can get this syntax tree from a document or we can use Parse methods of SyntaxFactory class.

For example, you can use the following code to get a C# file syntax tree:

var solution = MSBuildWorkspace.Create().OpenSolutionAsync(slnPath).Result;
var project = solution.Projects.First(f => f.FilePath == csProjPath);
var document = project.Documents.First(d => d.FilePath == filePath);

var syntaxRoot = document.GetSyntaxRootAsync().Result;


 



Then, this tree can be analyzed using the Visitor pattern.



For C#, Roslyn provides two visitors:



  • CScharpSyntaxVisitor (generic or not) to analyze the code
  • CScharpSyntaxRewriter to change the syntax tree. Note that as the syntax tree is immutable, it means that we will get another one.


public class MySyntaxVisitor : CSharpSyntaxVisitor
{
   
public override void Visit(SyntaxNode node)
    {
       
base.Visit(node);
       
foreach (var childNode in node.ChildNodes())
           
Visit(childNode);
    }
   
public override void VisitCompilationUnit(CompilationUnitSyntax node)
    {
       
base.VisitCompilationUnit(node);
    }
   
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
    {
       
base.VisitNamespaceDeclaration(node);
    }
   
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
       
base.VisitClassDeclaration(node);
    }
   
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
       
base.VisitMethodDeclaration(node);
    }
   
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
    {
       
base.VisitPropertyDeclaration(node);
    } 



    public override void VisitBlock(BlockSyntax node)
    {
       
base.VisitBlock(node);
    }
   
public override void VisitExpressionStatement(ExpressionStatementSyntax node)
    {
       
base.VisitExpressionStatement(node);
    }
   
public override void VisitInvocationExpression(InvocationExpressionSyntax node)
    {
       
base.VisitInvocationExpression(node);
    }
   
public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
    {
       
base.VisitMemberAccessExpression(node);
    }
   
public override void VisitIdentifierName(IdentifierNameSyntax node)
    {
       
base.VisitIdentifierName(node);
    } 
   
//…
}



 



public class MySyntaxRewriter : CSharpSyntaxRewriter
{
   
public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
    {
       
return base.VisitCompilationUnit(node);
    }
   
public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
    {
       
return base.VisitNamespaceDeclaration(node);
    }
   
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
       
return base.VisitClassDeclaration(node);
    }
   
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
       
return base.VisitMethodDeclaration(node);
    }
   
public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
    {
       
return base.VisitPropertyDeclaration(node);
    }  
    public override SyntaxNode VisitBlock(BlockSyntax node)
    {
       
return base.VisitBlock(node);
    }
   
public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node)
    {
       
return base.VisitExpressionStatement(node);
    }
   
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
    {
       
return base.VisitInvocationExpression(node);
    }
   
public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
    {
       
return base.VisitMemberAccessExpression(node);
    }
   
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
    {
       
return base.VisitIdentifierName(node);
    } 
    //…



}



 



Note that with SyntaxVisitor you have to Visit children yourself. By default, SyntaxVisitor does not.



With SyntaxRewriter you don’t have to. It is does by default. However, with SyntaxRewriter, you can return a SyntaxNode (without calling Visit or base.VisitXXX) if you want to stop Syntax tree modification.



 



SemanticModel and symbols



Syntax tree is not enough to understand the code.



With SemanticModel you can get the symbol associated to the syntax node.



From this symbol, you can



  • Know what is the method called in an InvocationExpressionSyntax
  • Know type information of a TypeSyntax
  • Know the type of an expression
  • Know if two variables are the same
  • Simplify the code to get some information


To get the SemanticModel, you can use the following code:



var semanticModel = document.GetSemanticModelAsync().Result;



 



Then, to get a symbol from a SemanticModel and a SyntaxNode, there are two methods:



  • semanticModel.GetDeclaredSymbol(node) for declaration SyntaxNode (ClassDeclarationSyntax, MethodDeclarationSyntax, PropertyDeclarationSyntax, etc)
  • semanticModel.GetSymbolInfo(node).Symbol for other SyntaxNode


To get the type of an expression, you can use the GetTypeInfo method:



semanticModel.GetTypeInfo(node).Type



 



Sample





Imagine that you want to get all extension methods with only one parameter that does not return void and that starts with Get.



You can get them without SemanticModel like this:



public class SpecificationVisitor : CSharpSyntaxVisitor
{
    private List<MethodDeclarationSyntax> _getMethods;

    public SpecificationVisitor(List<MethodDeclarationSyntax> getMethods)
    {
        _getMethods = getMethods;
    }

    public override void Visit(SyntaxNode node)
    {
        base.Visit(node);
        foreach (var childNode in node.ChildNodes())
            Visit(childNode);
    }

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var predefinedReturnType = node.ReturnType as PredefinedTypeSyntax;
        if (node.Identifier.ValueText.StartsWith("Get")
            && node.ParameterList.Parameters.Count == 1
            && node.Modifiers.OfType<SyntaxToken>().Any(m => m.CSharpKind() == SyntaxKind.StaticKeyword)
            && node.ParameterList.Parameters[0].Modifiers.OfType<SyntaxToken>().Any(m => m.CSharpKind() == SyntaxKind.ThisKeyword)
            && predefinedReturnType != null && predefinedReturnType.Keyword.CSharpKind() != SyntaxKind.VoidKeyword)
            _getMethods.Add(node);
    }
}




Note it’s useless to look for method body or properties, etc. So for performance aspect, it would be better to use the following code:



public class SpecificationVisitor : CSharpSyntaxVisitor
{
    private List<MethodDeclarationSyntax> _getMethods;

    public SpecificationVisitor(List<MethodDeclarationSyntax> getMethods)
    {
        _getMethods = getMethods;
    }

    public override void VisitCompilationUnit(CompilationUnitSyntax node)
    {
        foreach (var n in node.ChildNodes().OfType<NamespaceDeclarationSyntax>())
            Visit(n);
    }

    public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
    {
        foreach (var c in node.ChildNodes().OfType<ClassDeclarationSyntax>())
            Visit(c);
    }

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        if (node.Modifiers.Any(m => m.CSharpKind() == SyntaxKind.StaticKeyword))
            foreach (var m in node.ChildNodes().OfType<MethodDeclarationSyntax>())
                Visit(m);
    }

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var predefinedReturnType = node.ReturnType as PredefinedTypeSyntax;
        if (node.Identifier.ValueText.StartsWith("Get")
            && node.ParameterList.Parameters.Count == 1
            && node.Modifiers.OfType<SyntaxToken>().Any(m => m.CSharpKind() == SyntaxKind.StaticKeyword)
            && node.ParameterList.Parameters[0].Modifiers.OfType<SyntaxToken>().Any(m => m.CSharpKind() == SyntaxKind.ThisKeyword)
            && predefinedReturnType != null && predefinedReturnType.Keyword.CSharpKind() != SyntaxKind.VoidKeyword)
            _getMethods.Add(node);
    }
}


 



With SemanticModel, VisitMethodDeclaration method is easier:



public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
    var methodSymbol = (IMethodSymbol)_semanticModel.GetDeclaredSymbol(node);
    if (methodSymbol.Name.StartsWith("Get")
        && methodSymbol.Parameters.Length == 1
      && methodSymbol.IsExtensionMethod
      && !methodSymbol.ReturnsVoid)
        _getMethods.Add(node); }


So we saw how to basically use Roslyn.



In next posts, I will explain the usage I made with WAQS.



 



See also: How does WAQS code generation work?

This entry was posted in 16402, 17895. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>