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 symbol analyzer to report issues about symbol declarations


A symbol analyzer registers action callbacks to analyze one or more kinds of symbol declarations, such as types, methods, fields, properties, events, and so on, reports semantic issues about declarations.

In this section, we will create a symbol analyzer that extends the compiler diagnostic CS0542 (member names cannot be the same as their enclosing type) to report a diagnostic if member names are the same as any of the outer parent type. For example, the analyzer will report a diagnostic for the innermost type NestedClass here:

public class NestedClass
{
  public class InnerClass
  {
    public class NestedClass
    {
    }
  }
}

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.RegisterSymbolAction(symbolContext =>
  {
    var symbolName = symbolContext.Symbol.Name;

    // Skip the immediate containing type, CS0542 already covers this case.
    var outerType = symbolContext.Symbol.ContainingType?.ContainingType;
    while (outerType != null)
    {
      // Check if the current outer type has the same name as the given member.
      if (symbolName.Equals(outerType.Name))
      {
        // For all such symbols, report a diagnostic.
        var diagnostic = Diagnostic.Create(Rule, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name);
        symbolContext.ReportDiagnostic(diagnostic);
        return;
      }

      outerType = outerType.ContainingType;
    }
  },
  SymbolKind.NamedType,
  SymbolKind.Method,
  SymbolKind.Field,
  SymbolKind.Event,
  SymbolKind.Property);
}
  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 OuterClass
 {
  public class NestedClass
  {
   public class NestedClass
   {
   }
  }
 }
}
  1. Verify the compiler reported diagnostic CS0542 in the error list: 'NestedClass': member names cannot be the same as their enclosing type.
  2. Change the class library code to following:
namespace ClassLibrary
{
 public class OuterClass
 {
  public class NestedClass
  {
   public class InnerClass
   {
    public class NestedClass
    {
    }
   }
  }
 }
}
  1. Verify that CS0542 isn t reported anymore, but the error list has our analyzer diagnostic:
  1. Replace the innermost type declaration for NestedClass with a field: public int NestedClass, and verify the same analyzer diagnostic is reported. You should get the same diagnostic for other member kinds such as method, property, and events with the same name.

How it works...

Symbol analyzers register one or more symbol action callbacks to analyze symbol kinds of interest. Note that, unlike the default implementation that registered a delegate method named AnalyzeSymbol, we registered a lambda callback.

We specified interest in analyzing all the top-level symbol kinds that can have an enclosing type, namely types, methods, fields, properties, and events in the RegisterSymbolAction invocation:

context.RegisterSymbolAction(symbolContext =>
{
 ...
},
SymbolKind.NamedType,
SymbolKind.Method,
SymbolKind.Field,
SymbolKind.Event,
SymbolKind.Property);

The analyzer driver ensures that the registered lambda is invoked for all symbols of the registered interest kinds in the compilation.

Analysis skips the immediate enclosing type, as C# compiler already reports error CS0542, if a member has the same name as its enclosing type.

// Skip the immediate containing type, CS0542 already covers this case.
var outerType = symbolContext.Symbol.ContainingType?.ContainingType;

Core analysis works by looping over the outer types and comparing the name of the symbol in a symbol analysis context with the relevant outer types, until it finds a match, in which case, it reports a diagnostic; if the outer type has no containing type, it doesn t report a diagnostic.

while (outerType != null)
{
 // Check if the current outer type has the same name as the given member.
 if (symbolName.Equals(outerType.Name))
 {
  // For all such symbols, report a diagnostic.
  ...
 }

 outerType = outerType.ContainingType;
}

Note

It is recommended that symbol actions only analyze and report diagnostics about declarations, not the executable code within it. If you need to analyze executable code within a symbol, you should try to register other action kinds discussed later in this chapter.

There s more...

Trivia: The preceding implementation of the symbol analyzer does not have optimal performance. For example, if you have n levels of type nesting, and m fields in the inner innermost nested type, the analysis we implemented will be O(m*n) algorithmic complexity. Can you implement an alternate implementation where the analysis can be implemented with a much superior O(m + n) complexity?

See also

Our current analyzer implementation is completely stateless because it doesn t require analysis that is dependent upon more than one symbol at a time. We analyze each symbol individually and report diagnostics for it. However, if you need to do more complex analysis that requires collecting state from multiple symbols and then doing a compilation-wide analysis, you should write a stateful compilation analyzer with symbol and compilation actions. This is covered later in this chapter in the recipe Creating a compilation analyzer to analyze a whole compilation and report issues.