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 I will 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

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 classed 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 develop a method 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.

  • 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.

  • If I hover the cursor on this line I will get my custom message.

  • On the left side I can see the bulb icon

  • By clicking on the popup menu, code fix will be applied