Book Image

Roslyn Cookbook

Book Image

Roslyn Cookbook

Overview of this book

Open-sourcing the C# and Visual Basic compilers is one of the most appreciated things by the .NET community, especially as it exposes rich code analysis APIs to analyze and edit code. If you want to use Roslyn API to write powerful extensions and contribute to the C# developer tool chain, then this book is for you. Additionally, if you are just a .NET developer and want to use this rich Roslyn-based functionality in Visual Studio to improve the code quality and maintenance of your code base, then this book is also for you. This book is divided into the following broad modules: 1. Writing and consuming analyzers/fixers (Chapters 1 - 5): You will learn to write different categories of Roslyn analyzers and harness and configure analyzers in your C# projects to catch quality, security and performance issues. Moving ahead, you will learn how to improve code maintenance and readability by using code fixes and refactorings and also learn how to write them. 2. Using Roslyn-based agile development features (Chapters 6 and 7): You will learn how to improve developer productivity in Visual Studio by using features such as live unit testing, C# interactive and scripting. 3. Contributing to the C# language and compiler tool chain (Chapters 8 - 10): You will see the power of open-sourcing the Roslyn compiler via the simple steps this book provides; thus, you will contribute a completely new C# language feature and implement it in the Roslyn compiler codebase. Finally, you will write simple command line tools based on the Roslyn service API to analyze and edit C# code.
Table of Contents (19 chapters)
Title Page
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
Dedication

Creating a syntax node analyzer to report issues about language syntax


A syntax node analyzer registers action callbacks to analyze one or more kinds of syntax nodes, such as operators, identifiers, expressions, declarations, and so on, and reports semantic issues about syntax. These analyzers generally need to fetch semantic information about different syntax nodes being analyzed and use the compiler semantic model APIs to get this information.

In this section, we will create a syntax analyzer that analyzes VariableDeclarationSyntax nodes for local declarations and reports a diagnostic recommending use of the explicit type instead of an implicitly typed declaration, that is, variables defined with the keyword var, such as var i = new X();. Analyzer will not report diagnostics if there is a compiler syntax error (implicitly typed declarations cannot define more than one variable), or the right side of the assignment has an error type or special System type such as int, char, string, and so on. For example, the analyzer will not flag locals local1, local2, and local3 here, but will flag local4.

int local1 = 0;
Class1 local2 = new Class1();
var local3 = 0;
var local4 = new Class1();

Getting ready

You will need to have created and opened an analyzer project, say CSharpAnalyzers in Visual Studio 2017. Refer to the first recipe in this chapter to create this project.

How to do it...

  1. In Solution Explorer, double-click on Resources.resx file in CSharpAnalyzers project to open the resource file in the resource editor.
  2. Replace the existing resource strings for AnalyzerDescription, AnalyzerMessageFormat and AnalyzerTitle with new strings:
  1. Replace the Initialize method implementation with the following:
public override void Initialize(AnalysisContext context)
{
  context.RegisterSyntaxNodeAction(syntaxNodeContext =>
  {
    // Find implicitly typed variable declarations.
    // Do not flag implicitly typed declarations that declare more than one variables,
    // as the compiler already generates error CS0819 for those cases.
    var declaration = (VariableDeclarationSyntax)syntaxNodeContext.Node;
    if (!declaration.Type.IsVar || declaration.Variables.Count != 1)
    {
      return;
    }

    // Do not flag variable declarations with error type or special System types, such as int, char, string, and so on.
    var typeInfo = syntaxNodeContext.SemanticModel.GetTypeInfo(declaration.Type, syntaxNodeContext.CancellationToken);
    if (typeInfo.Type.TypeKind == TypeKind.Error || typeInfo.Type.SpecialType != SpecialType.None)
    {
      return;
    }

    // Report a diagnostic.
    var variable = declaration.Variables[0];
    var diagnostic = Diagnostic.Create(Rule, variable.GetLocation(), variable.Identifier.ValueText);
    syntaxNodeContext.ReportDiagnostic(diagnostic);
  }, 
  SyntaxKind.VariableDeclaration);
}
  1. Click on Ctrl + F5 to start a new Visual Studio instance with the analyzer enabled.
  2. In the new Visual Studio instance, create a new C# class library with the following code:
namespace ClassLibrary
{
  public class Class1
  {
    public void M(int param1, Class1 param2)
    {
      // Explicitly typed variables - do not flag.
      int local1 = param1;
      Class1 local2 = param2;
    }
  }
}
  1. Verify the analyzer diagnostic is not reported in the error list for explicitly typed variables.
  2. Now, add the following implicitly typed variable declarations to the method:
 // Implicitly typed variable with error type - do not flag.
 var local3 = UndefinedMethod();

 // Implicitly typed variable with special type - do not flag.
 var local4 = param1;
  1. Verify the analyzer diagnostic is not reported in the error list for implicitly typed variables with error type or special type.
  2. Add the violating implicitly typed variable declaration to the method:
 // Implicitly typed variable with user defined type - flag.
 var local5 = param2;
  1. Verify the analyzer diagnostic is reported for this implicitly typed variable:

How it works...

Syntax node analyzers register one or more syntax node action callbacks to analyse syntax kinds of interest. We specified interest in analyzing VariableDeclaration syntax kind in the RegisterSyntaxNodeAction invocation.

 

context.RegisterSyntaxNodeAction(syntaxNodeContext =>
{
...
}, SyntaxKind.VariableDeclaration);

Analysis works by operating on the syntax node and semantic model exposed off the syntax node analysis context in the callback. We first do syntactic checks to verify that we are operating on a valid implicitly typed declaration:

 

// Do not flag implicitly typed declarations that declare more than one variables,
// as the compiler already generates error CS0819 for those cases.
var declaration = (VariableDeclarationSyntax)syntaxNodeContext.Node;
if (!declaration.Type.IsVar || declaration.Variables.Count != 1)
{
 return;
}

We then perform semantic checks using the semantic model APIs to get semantic type information about the type declaration syntax node and verify it is not an error type or primitive system type:

// Do not flag variable declarations with error type or special System types, such as int, char, string, and so on.
var typeInfo = syntaxNodeContext.SemanticModel.GetTypeInfo(declaration.Type, syntaxNodeContext.CancellationToken);
if (typeInfo.Type.TypeKind == TypeKind.Error || typeInfo.Type.SpecialType != SpecialType.None)
{
 return;
}

Note

You can perform many powerful semantic operations on the syntax node exposed from the SyntaxNodeAnalysisContext using the public semantic model APIs, for reference see https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/Compilation/SemanticModel.cs.

If both the syntactic and semantics check succeed, then we report a diagnostic about recommending explicit type instead of var.