Diagnostic analyzers are extensions to the Roslyn C# compiler and Visual Studio IDE to analyze user code and report diagnostics. Users will see these diagnostics in the error list after building the project from Visual Studio, and even when building the project on the command line. They will also see the diagnostics live while editing the source code in the Visual Studio IDE. Analyzers can report diagnostics to enforce specific code styles, improve code quality and maintenance, recommend design guidelines, or even report very domain-specific issues which cannot be covered by the core compiler. This chapter enables C# developers to write, debug, test, and publish analyzers that perform different kinds of analyses.
Diagnostic analyzers are built on top of the Roslyn's CodeAnalysis/Compiler layer API. Analyzers can analyze specific code units, such as a symbol, syntax node, code block, compilation, and so on, by registering one or more analyzer actions. The compiler layer makes a callback into the analyzer whenever it compiles a code unit of interest. The analyzer can report diagnostics on code units, which are added to the list of the compiler diagnostics and reported back to the end user.
Analyzers can be broadly categorized into the following two buckets based on the kind of analysis performed:
- Stateless analyzers: Analyzers that report diagnostics about a specific code unit by registering one or more analyzer actions that:
- Do not require maintaining any state across analyzer actions.
- Independent of the order of execution of individual analyzer actions.
For example, an analyzer that looks at every single class declaration independently and reports issues about the declaration is a stateless analyzer. We will show you how to write a stateless symbol, syntax node, and syntax tree analyzer, later in this chapter.
- Stateful analyzers: Analyzers that report diagnostics about a specific code unit, but in the context of an enclosing code unit, such as a code block or a compilation. These are more complicated analyzers that require powerful and wider analysis, hence, need careful design to achieve efficient analyzer execution without memory leaks. These analyzers require at least one of the following kinds of state manipulation for analysis:
- Access to immutable state objects for the enclosing code unit, such as a compilation or the code block. For example, access to certain well-known types defined in a compilation.
- Perform analysis over the enclosing code unit, with mutable state defined and initialized in a start action for the enclosing code unit, intermediate nested actions that access and/or update this state, and an end action to report diagnostic on the individual code units.
For example, an analyzer that looks at all class declarations in a compilation, gathering and updating a common state when analyzing each class declaration, and then finally, after it has analyzed all declarations, reports issues about those declarations is a stateful analyzer. We will show you how to write a stateful method body and compilation analyzer in this chapter.