Introduction
Most programmers treat compilers as black boxes. They write the source code as input, do some processing on it, and get the output as executable binaries. Source code processing consists of three main phases: syntax analysis, semantic analysis, and code generation. Although each phase outputs its own intermediate results and passes them as input to the next phase, these are just internal data structures that are not accessible outside the compiler.
In this article, I am going to demonstrate how to write your own compiler, (i.e. type of code analyzer and code fixer using the .NET compile platform called Roslyn), using the latest Visual Studio 2019 and .NET framework, which consists of syntax and semantic analysis. The code fixer will be the code generation.
Background
The concept of Roslyn can be found in another article published by Mr. Mahesh.
Prerequisites
Most Roslyn API syntaxes have changed from VS2014 to VS2019. Hence, let's compile the build using VS 2019 with the latest framework. Before starting the new code analyzer, make sure you have installed the prerequisites of this article, i. e. “.NET Compiler Platform SDK” and the same from the VS extension. These tools will be your faithful companions as you build your own applications and VS extensions on top of the .NET Compiler Platform APIs.
Build Analyzer and Code Fix
Let's begin with creating code analyzers and code fixes step by step and see how to test those in your VS project and create the extension from it.
Step 1
Open VS 2019 and Select Roslyn or search “Analyzer with Code Fix” project template with C#, create a new project with this option.

Step 2
You will find three projects that are created as part of one solution; i.e. MainAnalyzeProject, UnitTestProject, and VsixProject to install this analyzer as an extension.

Step 3
Go to Your Main Analyzer project and you will find two important classes, one for Analyzer and another for code fix. Analyzer class is used for finding the diagnostic and code fix is used for fixing those diagnostics.
Let's understand these two main steps one by one in detail.
Code Analyzer
Step 1
Let's start with writing two new Diagnostic Rules.
- Finding all the fields which start with an underscore.
- Finding all blocks like if, for each, or those that don't have curly braces.
Step 2
Open the analyzer class which is derived from DiagnosticAnalyzer and define DiagnosticDescriptor which contains “Unique Id, title to show during analyze, message, category, severity, enable this rule by default description”.
- private static DiagnosticDescriptor StartUnderScoreRule = new DiagnosticDescriptor
- (StartUnderScoreDiagnosticId, "name starts with underscore",
- "the name of field {0} start with an underscore", "naming", DiagnosticSeverity.Warning, true);
Step 3
The next step is to register the type of Action with the kind of property/syntax (Symbol, Syntax, compilation, etc.) in the Initialize method.
- context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
Step 4
The next step is to implement a delegate that was associated with registration of the type. This delegate will be triggered during the analysis of each class.
- private static void AnalyzeField(SymbolAnalysisContext context)
- {
- var namedTypeSymbol = (IFieldSymbol)context.Symbol;
- // Find just those named type symbols with names starting with _.
- if (namedTypeSymbol.Name.StartsWith("_") && namedTypeSymbol.Name.Length > 1)
- {
- // For all such symbols, produce a diagnostic.
- var diagnostic = Diagnostic.Create(StartUnderScoreRule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
- context.ReportDiagnostic(diagnostic);
- }
- }
Step 5
Define all other rules in this class with Unique RuleID, DiagnosticDescriptor and register it. Here is the full implementation of all rules defined in CodeAnalyzer to find all diagnostics and provide to codeFixProvider.
- [DiagnosticAnalyzer(LanguageNames.CSharp)]
- public class CSharpeStyleCopAnalyzer : DiagnosticAnalyzer
- {
- public const string LowerCaseDiagnosticId = "LowerCase";
- public const string StartUnderScoreDiagnosticId = "StartUnderScore";
- public const string CurlyBraceDiagnosticId = "CurlyBrace";
- private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
- private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
- private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
- private static DiagnosticDescriptor LowerCaseNameRule = new DiagnosticDescriptor(LowerCaseDiagnosticId, Title, MessageFormat, "naming", DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
- private static DiagnosticDescriptor StartUnderScoreRule = new DiagnosticDescriptor(StartUnderScoreDiagnosticId, "name starts with underscore", "the name of field {0} start with an underscore", "naming", DiagnosticSeverity.Warning, true);
- private static DiagnosticDescriptor SyntaxRule = new DiagnosticDescriptor(CurlyBraceDiagnosticId, "Add curly braces", "the block {0} does not contains curly braces", "syntax", DiagnosticSeverity.Warning, true);
- public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(LowerCaseNameRule, StartUnderScoreRule, SyntaxRule); } }
- public override void Initialize(AnalysisContext context)
- {
- context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
- context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.IfStatement, SyntaxKind.ForEachStatement);
- }
- private static void AnalyzeField(SymbolAnalysisContext context)
- {
- var namedTypeSymbol = (IFieldSymbol)context.Symbol;
- // Find just those named type symbols with names starting with _.
- if (namedTypeSymbol.Name.StartsWith("_") && namedTypeSymbol.Name.Length > 1)
- {
- // For all such symbols, produce a diagnostic.
- var diagnostic = Diagnostic.Create(StartUnderScoreRule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
- context.ReportDiagnostic(diagnostic);
- }
- }
- private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
- {
- var body = default(StatementSyntax);
- var token = default(SyntaxToken);
- switch (context.Node)
- {
- case IfStatementSyntax ifstate:
- body = ifstate.Statement;
- token = ifstate.IfKeyword;
- break;
- case ForEachStatementSyntax foreachstate:
- body = foreachstate.Statement;
- token = foreachstate.ForEachKeyword;
- break;
- case ForStatementSyntax forstate:
- body = forstate.Statement;
- token = forstate.ForKeyword;
- break;
- case WhileStatementSyntax whilestate:
- body = whilestate.Statement;
- token = whilestate.WhileKeyword;
- break;
- // Add more blocks type
- }
- if (!body.IsKind(SyntaxKind.Block))
- {
- var diagnostic = Diagnostic.Create(SyntaxRule, token.GetLocation(), context.Node.Kind().ToString());
- context.ReportDiagnostic(diagnostic);
- }
- }
- }



DeepikaPosted Dec 22, 2019, 11:55 AM
Please explain few complex cases as well, these are already available with either stylecop or re-sharper. thanks for writing.
AnandPosted Dec 21, 2019, 11:29 PM
Nice article ashutosh, very much in detail. good for developers to start with.