Showing posts with label Code Fix. Show all posts
Showing posts with label Code Fix. Show all posts

Monday, December 8, 2014

Updates to the "IfBracesAnalyzer" Code Fix Method

Last week, I published a video on creating a code analyzer with the .NET Compiler Platform "Roslyn" (Building a Diagnostic Analyzer with Code Fix in Visual Studio 2015). As I mentioned in the video and the accompanying article, I have not been working with Roslyn for very long, and I keep learning how to do things a little better.

So, I have a few updates to the code fix that we built. (And I'm still waiting to hear from my friends who have been working with Roslyn for a while; I'm sure they have some suggestions as well.)

The Original Method
Here's the method that we build in the video:


Here's a reminder of what this does:
  1. We take the "Statement" property of our "ifStatement" (which comes in as a parameter) and cast this to "ExpressionStatementSyntax".
    We can safely make this cast (and we don't need to check for nulls here) because we did that filtering in our diagnostic analyzer. So we already know that the "Statement" is an "ExpressionStatement" that needs to be wrapped in braces.
  2. Next. we create a new "Block" statement with the "SyntaxFactory". The parameter is the statement we want to wrap.
    In addition, we add "WithAdditionalAnnotations" so that the block will be formatted nicely in our code.
  3. Then we use the "ReplaceNode" method to swap out the old (non-braces) statement for the new block that we just created.
    As a reminder, nodes are immutable, so this returns us a new object.
  4. We get the "root" node from our document. This contains the entire syntax tree.
  5. Then we use the "ReplaceNode" method to swap out the old if statement for the updated if statement.
    Again, this creates a new root rather than modifying the existing one.
  6. After we have our new root, we call "document.WithSyntaxRoot" to create a new document.
    This new document will have all of the same properties as our old document, but we are replacing the contents with our updated root.
  7. Finally, we return the new document.
With this code fix in place, we can add curly braces to an if statement that needs them.



A Few Updates
There are a few minor updates that we can do to this code. The first has to do with our call to "ReplaceNode" on the "ifStatement". Here is the original:


We need to cast our parameters to "SyntaxNode" because the types are different: the first is "ExpressionStatementSyntax" and the second is "BlockSyntax". Now it's a bit subtle, but notice that "SyntaxNode" is greyed-out. Technically, we only need one cast -- it doesn't matter which one -- and the compiler will take care of the other one. But that may make things unclear when we're reading the code.

To clean things up a little, we can remove the cast and provide explicit generic types for the method. Here's what that looks like:


The first generic type is the type that we're working with ("IfStatementSyntax"), and the second generic type is the type for our 2 parameters ("SyntaxNode"). By specifying the generic type, we no longer need to cast the parameters; the compiler will handle everything for us. I think this is a bit easier to read.

The next fix has to do with how we retrieve the syntax root of our document. Here's the original:


This is an asynchronous method call. It turns out our entire method is also async, and it includes a cancellation token as a parameter. So we really should pass the cancellation token along to any async methods that we call.

Here's our updated call:


Eliminating Steps
The biggest change we'll make is to eliminate some steps. It turns out we're doing some extra work that we don't need. Let's look at our "ReplaceNode" method calls.
First, we use "ReplaceNode" to update the if statement -- this is where we swap out our old expression for the one wrapped in a block.
Later on, we use "ReplaceNode" to update the root -- this is where we swap out the old if statement for the updated if statement.
We can skip the intermediate step and simply replace the old expression for the new block in the root itself. Here's the updated method:


And here are the steps:
  1. We take the "Statement" property of our "ifStatement" (which comes in as a parameter) and cast this to "ExpressionStatementSyntax".
  2. Next. we create a new "Block" statement with the "SyntaxFactory". The parameter is the statement we want to wrap.
    Then we use the "ReplaceNode" method to swap out the old (non-braces) statement for the new block that we just created.
  3. We get the "root" node from our document. This contains the entire syntax tree.
    Note that we pass the cancellation token to this asynchronous method.
  4. Then we use the "ReplaceNode" method to swap out the old (non-block) statement for the new block statement.
    Since we specify "SyntaxNode" for the generic type, we do not need to cast the parameters.
  5. After we have our new root, we call "document.WithSyntaxRoot" to create a new document.
  6. Finally, we return the new document.
This is not a huge difference, but we do eliminate one of our steps. Optimization of the code fix is not as critical as optimizing the diagnostic analyzer, but it is always good to eliminate code that we do not need.

As a reminder, we want our diagnostic analyzer code to run as quickly as possible. This code is constantly running against our code in Visual Studio. If we aren't careful, this can really bog things down. The code fix only runs when we choose to show the fixes, so this requires some user interaction. But the code fix does need to run to show the preview, and it also may run multiple times if we choose "fix all occurrences", so we don't want it to be slow, either.

Wrap Up
A big part of being a developer is going back and taking another look at the code we've written. Often we'll find that we can optimize the code because we've learned more about the API. Sometimes, we question readability because it doesn't look quite as clear as when we wrote it.

This doesn't bother me at all. I see this as a sign that I am constantly improving. And I look forward to hearing what some other folks say about this code so that I can improve further.

Happy Coding!

Wednesday, December 3, 2014

New Video: Building a Diagnostic Analyzer with Code Fix in Visual Studio 2015

Code analyzers are pretty cool. Many of us have been using ReSharper or another third-party tool with Visual Studio for many years. With the latest preview of Visual Studio 2015 and the .NET Compiler Platform ("Roslyn"), it's really easy to build these code analyzers for yourself.

You can spend about 45 minutes following along with me in my latest video:

Building a Diagnostic Analyzer with Code Fix in Visual Studio 2015
The .NET Compiler Platform ("Roslyn") allows us to easily create diagnostic analyzers that can alert us to issues in code as well as provide automated code fixes. We'll create a very simple analyzer and fix to show how easy this is to do. This uses the Visual Studio 2015 Preview, and instructions on getting what you need are in the video.
Here's a sample that I built:


This analyzer looks for "if" statements that don't have curly braces, warns us about it, and then offers to add the braces for us automatically.

You can get the code from my website: http://www.jeremybytes.com/Downloads.aspx#Analyzers

New to Roslyn
Now, I am very new to Roslyn (now called the ".NET Compiler Platform"). Many people I know have been working with it for years, but I've been struggling with whether it is relevant to me. I still don't have the answer to that question.

But at the MVP Summit last month, there was a hackathon where we built diagnostic analyzers and code fixes. This was a whirlwind introduction to Roslyn, the syntax visualizer, and the diagnostic analyzer project template. After going through the demo, I slowed down to try to understand things a bit better.

Today, I released a video that shows my initial exploration. I write a simple code analyzer and walk through the process of building it. You can watch the video on YouTube: Building a Diagnostic Analyzer with Code Fix in Visual Studio 2015.

I'm definitely not an expert (just a month in to actually using it). But I thought it would be useful to share my experiences.

If nothing else, this has given me a good appreciation for what benefits we get with the .NET Compiler Platform so I can think about possible uses in the future.

Happy Coding!