Share via


Writing Diagnostic with Code Fix using Roslyn (.NET Compiler Platform)

Since the preview version of Roslyn (.NET Compiler Platform) released this year, developers can use compiler as a services APIs that provides an object model to develop tools to extend the development of “Visual studio” using code analysis, refactoring and writing custom diagnostics. In this post let's walk you through the steps of developing an extension of Visual Studio using Roslyn Diagnostic with Code Fix project template.

Background

Before going into action, Roslyn is a set of open source compilers for C# and VB languages. It’s an open source project comes under .NET Foundation (Open Source Foundation for .NET). Due to the open source model developers from all around the world can contribute. Apart from contributing to the Roslyn project they can also use Roslyn “Compiler as a service” APIs to extend Visual Studio and applications in specific needs.

Writing Simple Diagnostic with Code Fix

In Visual Studio 13 or Visual Studio CTP 14 you can get the template of writing your own Diagnostic with Code Fix for Visual Studio. You can download the Roslyn preview from https://connect.microsoft.com/VisualStudio/Downloads

http://ovaismehboob.files.wordpress.com/2014/09/092814_1908_writingdiag1.png?w=948

Through “Diagnostic with Code Fix” you can actually develop your own diagnostic tool that is loaded into the Visual Studio and checks your code Syntax**,** Symbols etc. When you select and create a project you will see the DiagnosticAnalyzer and CodeFIxProvider classes created by default.

DiagnosticAnalyzer class implements ISymbolAnalyzer but there are other interfaces as well like ISyntaxAnalyzer to analyze language syntax, ISemanticModelAnalyzer to analyze semantics etc.

CodeFIxProvider is used to fix the code when the diagnostic has been made. GetFixesAsync get the list of fixes and then we can write some code to fix those changes accordingly.

On running the project, a new Visual Studio instance will be loaded and then you can create new project and test your newly diagnostic tool by writing some code.

In this post, we will do a simple example of using “Diagnostic with code fix” that will analyze the naming case of Property and check if the first character is upper case or not.

  • Create a new project “Diagnostic with Code Fix” in Visual Studio 2013

  • Modify the DiagnosticAnalyzer and implement interface ISyntaxNodeAnalyzer

  • There are many interfaces provided for specific reason like suppose if you want to diagnose symbols you can use ISymbolAnalyzer and for Syntax Trees use ISyntaxTreeAnalyzer and so on.

  • Here, we have implemented ISyntaxNodeAnalyzer to analyze the syntax node.

    ** What is Syntax Node? Syntax node represents the syntactic constructs such as declarations, statements, clauses, and expressions. Each category of syntax nodes is represented by a separate class derived from SyntaxNode.

    In Syntax Tree API, each node can be a compilation unit, type declaration, method declaration, property declaration, parameter list etc.

http://ovaismehboob.files.wordpress.com/2014/09/092814_1908_writingdiag2.png?w=948

When you implement ISyntaxNodeAnalyzer, you have implement the property SyntaxsKindOfInterest and AnalyzeNode which is invoked when you write a code in Visual Studio, selecting any project template. In the SyntaxKindOfInterest you can specify the SyntaxKind. In my code, I have specified the SyntaxKind.PropertyDeclaration to analyze the Properties but you can add as many syntax kinds in the ImmutableArray. In the AnalyzeNode method we can check the property first character if it is Upper case or lower case. Here is a complete code.

    [DiagnosticAnalyzer]
 
    [ExportDiagnosticAnalyzer(DiagnosticId, LanguageNames.CSharp)]
 
    public class DiagnosticAnalyzer :  ISyntaxNodeAnalyzer<SyntaxKind>
 
    {
 
        internal const string DiagnosticId = "Diagnostic1";
 
        internal const string Description = "Property Name contains lower case letters";
 
        internal const string MessageFormat = "Property Name '{0}' contains lowercase letters";
 
        internal const string Category = "Naming";
 
  
 
        internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Description, MessageFormat, Category, DiagnosticSeverity.Warning);
 
  
 
        public ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
 
  
 
        public ImmutableArray<SyntaxKind> SyntaxKindsOfInterest
 
        {
 
            get
 
            {
 
                return ImmutableArray.Create(SyntaxKind.PropertyDeclaration);
 
  
 
            }
 
        }
 
         public void AnalyzeNode(SyntaxNode node, SemanticModel semanticModel, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken)
 
        {
 
                var localDescription = (PropertyDeclarationSyntax)node;
 
                string propName = localDescription.Identifier.Text;
 
                propName = propName.Substring(0, 1);
 
                if (propName == propName.ToLower()) {
 
                    addDiagnostic(Diagnostic.Create(Rule, node.GetLocation(), "Property first character should be in Upper case", DiagnosticSeverity.Warning, 1, true));
 
                }
 
        }
 
}
 
When you run the application and if you declare any property having lower case Name you will get the green line and shows as warning.
Now, when the user take an action on the code snippet to fix the code, GetFixesAsync method is called by the Roslyn API which then change the code by calling CodeAction.Create method. Following is a full code snippet of CodeFixProvider that resolves the naming issue.
    [ExportCodeFixProvider(DiagnosticAnalyzer.DiagnosticId, LanguageNames.CSharp)]
 
    internal class CodeFixProvider : ICodeFixProvider
 
    {
 
        public IEnumerable<string> GetFixableDiagnosticIds()
 
        {
 
            return new[] { DiagnosticAnalyzer.DiagnosticId };
 
        }
 
  
 
        public async Task<IEnumerable<CodeAction>> GetFixesAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
 
        {
 
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
  
 
            // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
 
  
 
            var diagnosticSpan = diagnostics.First().Location.SourceSpan;
 
  
 
            // Find the type declaration identified by the diagnostic.
 
            var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().First();
 
  
 
            // Return a code action that will invoke the fix.
 
            return new[] { CodeAction.Create("Fix Property naming convention", c => FixPropertyNaming(document, declaration, c)) };
 
        }
 
  
 
        private async Task<Solution> FixPropertyNaming(Document document, PropertyDeclarationSyntax declaration, CancellationToken cancellationToken)
 
        {
 
            // Compute new uppercase name.
 
            var identifierToken = declaration.Identifier;
 
            var newName = identifierToken.Text;
 
            newName = newName.Substring(0, 1).ToUpper() + newName.Substring(1);
 
  
 
            // Get the symbol representing the type to be renamed.
 
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
 
            var typeSymbol = semanticModel.GetDeclaredSymbol(declaration, cancellationToken);
 
  
 
            // Produce a new solution that has all references to that type renamed, including the declaration.
 
            var originalSolution = document.Project.Solution;
 
            var optionSet = originalSolution.Workspace.GetOptions();
 
  
 
            var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
 
  
 
            // Return the new solution with the now-uppercase type name.
 
            return newSolution;
 
        }
 
    }
  • That’s all, build the project and run
  • When you hit F5 a new instance of Visual Studio open up and you can test your diagnostic utility by writing some code.
  • Following is the screen shot showing the green line for Property that have lower case name

       

  • By hovering the mouse cursor you can see the custom message

         

  • On the left side, you can see a bulb icon showing context menu to apply code fix.

       

  • On clicking the context menu, code fix is applied.

      

Hope this helps!