{"id":417172,"date":"2024-06-30T01:23:37","date_gmt":"2024-06-30T01:23:37","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=417172"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-30T01:23:37","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=417172","title":{"rendered":"<span>Creating Roslyn API-based static analyzer for C#<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>After you read this article, you&#8217;ll have the knowledge to create your  own static analyzer for C#. With the help of the analyzer, you can find  potential errors and vulnerabilities in the source code of your own and  other projects. Are you intrigued? Well, let&#8217;s get started.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/616\/a42\/a33\/616a42a33c69f03f580690b651bce761.png\" width=\"580\" height=\"327\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/616\/a42\/a33\/616a42a33c69f03f580690b651bce761.png\"\/><figcaption><\/figcaption><\/figure>\n<p>First, we will make your own static analyzer from the Visual  Studio templates, without going deeper into the Roslyn API. This allows  you to quickly get a working application and at least roughly see what  analyzers can do.<\/p>\n<p>And after that, we&#8217;ll take a closer look at Roslyn API, as well  as various tools that allow you to perform deeper and more complex  analysis.<\/p>\n<h2>Static analyzers: what are they and why do we need them?<\/h2>\n<p>I&#8217;m sure that many developers have some mistakes that they or  their friends often make when writing code. Most likely you would like  to have a tool that detects such errors without your participation. This  tool is called a static analyzer. <\/p>\n<p>A static analyzer is an automatic tool that searches for  potential errors and vulnerabilities in a program&#8217;s source code without  launching the app directly.<\/p>\n<p>However, what if the existing analyzers can&#8217;t find what you want?  The answer is simple \u2014 you create your own utility or even an entire  analyzer. C# developers are very lucky. Thanks to Roslyn they can create  their own static analyzer. This is exactly what this article is about.<\/p>\n<h2>Creating an analyzer based on Visual Studio templates<\/h2>\n<p>All our further static analyzer development will be based on the <a href=\"https:\/\/github.com\/dotnet\/roslyn\">.NET Compiler Platform<\/a>  aka Roslyn. Thanks to the capabilities this platform provides, we can  use C# to create our own static analysis tools. Here, the word &#8216;static&#8217;  means that the analyzed code doesn&#8217;t need to be executed.<\/p>\n<p>Since our analyzer is based on Roslyn, we should install the .NET  Compiler Platform SDK for Visual Studio. One of the ways to do so is to  open the Visual Studio Installer and select &#8216;Visual Studio extension  development&#8217; in the &#8216;Workloads&#8217; tab.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/51d\/89b\/095\/51d89b095538fb4aec45acfc3ff7e46e.png\" width=\"447\" height=\"466\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/51d\/89b\/095\/51d89b095538fb4aec45acfc3ff7e46e.png\"\/><figcaption><\/figcaption><\/figure>\n<p>After we install the necessary toolset, we can start creating the analyzer. <\/p>\n<p>Open Visual Studio, click on &#8216;Create a new project&#8217;, select C#.  Specify Windows as the platform and select Roslyn as the project type.  After this we should see three projects templates. We are interested in  two: &#8216;Analyzer with Code Fix (.NET Standard)&#8217; and &#8216;Standalone Code  Analysis Tool&#8217;.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/969\/494\/2c0\/9694942c09c7b9b6f75f70160c09d05a.png\" width=\"565\" height=\"448\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/969\/494\/2c0\/9694942c09c7b9b6f75f70160c09d05a.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Let&#8217;s study each of the templates.<\/p>\n<h3>Description of the &#171;Analyzer with Code Fix (.NET Standard)&#187; project and an example of its use<\/h3>\n<p>After we create a new project with the &#8216;Analyzer with Code Fix  (.NET Standard)&#8217; template, we get a solution with five projects inside.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aa4\/c7a\/dfc\/aa4c7adfc316d908004fc26c59f8aba3.png\" width=\"243\" height=\"360\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/aa4\/c7a\/dfc\/aa4c7adfc316d908004fc26c59f8aba3.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Now we pay our full attention to the first project called <em>TestAnalyzer<\/em>. The main work on the analyzer is performed in this exact project. Open the <em>TestAnalyzerAnalyzer.cs<\/em>  file. It already contains an example of a simple rule for a static  analyzer. The rule searches through all type(class) names in source  code. If a type&#8217;s name has lowercase characters, the rule underlines it  with a green wavy line. Besides, if you hover the cursor on the type  name marked with a wavy line, you see a familiar light bulb symbol. It  offers to automatically correct the type name and bring all the  characters to uppercase:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a1b\/cfc\/d4a\/a1bcfcd4a9ef706abddced07b80036f8.png\" width=\"426\" height=\"309\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a1b\/cfc\/d4a\/a1bcfcd4a9ef706abddced07b80036f8.png\"\/><figcaption><\/figcaption><\/figure>\n<p>The easiest way to see it is to launch a new VS instance, which  already has our sample diagnostic rule. You can use the same approach  for debugging. To do this, mark <em>TestAnalyzer.vsix<\/em> as a startup  project and launch the application. After that, a so-called experimental  Visual Studio instance window will open. A new diagnostic rule is  already added in this VS instance. It is integrated with the installed  VSIX extension that has the name of our test analyzer.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3f6\/dce\/f76\/3f6dcef7654fb5716eb4e7bae1cbbbcb.png\" width=\"508\" height=\"251\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3f6\/dce\/f76\/3f6dcef7654fb5716eb4e7bae1cbbbcb.png\"\/><figcaption><\/figcaption><\/figure>\n<p>Next, we create a new console project in the running VS instance. In this project, we see that the <em>Program<\/em>  class name is underlined with a green wavy line. This is the work of  our diagnostic rule, as the class name contains lowercase characters.<\/p>\n<h3>Create an analyzer based on the &#171;Standalone Code Analysis Tool&#187; project template<\/h3>\n<p>Now, let&#8217;s create a new project of the &#8216;Standalone Code Analysis  Tool&#8217; type. In fact, it&#8217;s a project of an ordinary console application  with links to the necessary DLLs for analysis:<\/p>\n<ul>\n<li>\n<p>Microsoft.CodeAnalysis.CSharp.Analyzers.dll; <\/p>\n<\/li>\n<li>\n<p>Microsoft.CodeAnalysis.Analyzers.dll; <\/p>\n<\/li>\n<li>\n<p>Microsoft.CodeAnalysis.Workspaces.MSBuild.dll; <\/p>\n<\/li>\n<li>\n<p>etc. <\/p>\n<\/li>\n<\/ul>\n<p>We can delete all methods except <em>Main,<\/em> from the <em>Program.cs<\/em> file.<\/p>\n<p>Let&#8217;s write the analyzer in such a way that it can find <em>if<\/em>  statements, in which true and false branches are identical. Would you  say that no one makes such mistakes? Surprisingly, this is a fairly  common pattern. Look at the <a href=\"https:\/\/pvs-studio.com\/en\/blog\/examples\/v3004\/\">list of similar errors<\/a> found in open source projects.<\/p>\n<p>Let&#8217;s say we are not satisfied if code contains a fragment like this:<\/p>\n<pre><code>public static void MyFunc1(int count) {   if (count > 100)   {     Console.WriteLine(\"Hello world!\");   }   else   {     Console.WriteLine(\"Hello world!\");   } }<\/code><\/pre>\n<p>So, we make the analyzer write the line number and the full path  to the source file into the log file. Let&#8217;s move on to writing code:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   if (args.Length != 2)     return;    string solutionPath = args[0];   string logPath = args[1];    StringBuilder warnings = new StringBuilder();    const string warningMessageFormat =     \"'if' with equal 'then' and 'else' blocks is found in file {0} at line {1}\";    MSBuildLocator.RegisterDefaults();   using (var workspace = MSBuildWorkspace.Create())   {     Project currProject = GetProjectFromSolution(solutionPath, workspace);     foreach (var document in currProject.Documents)     {       var tree = document.GetSyntaxTreeAsync().Result;       var ifStatementNodes = tree.GetRoot()                                  .DescendantNodesAndSelf()                                  .OfType&lt;IfStatementSyntax>();        foreach (var ifStatement in ifStatementNodes)       {         if (ApplyRule(ifStatement))         {           int lineNumber = ifStatement.GetLocation()                                       .GetLineSpan()                                       .StartLinePosition.Line + 1;            warnings.AppendLine(String.Format(warningMessageFormat,                                             document.FilePath,                                             lineNumber));         }       }     }      if (warnings.Length != 0)       File.AppendAllText(logPath, warnings.ToString());   } }<\/code><\/pre>\n<p>In our case, we use a console application and not a plugin for  VS. Thus, we need to specify the path to the solution file, which we are  going to analyze. In order to get the solution, we use the <em>MSBuildWorkspace<\/em> class and the <em>OpenSolutionAsync<\/em> method. In its turn, the <em>Solution<\/em> class contains the <em>Projects<\/em>  property, which stores the project entities. In my case, I created a  new solution with a single console application project. Therefore, to  get the project entity, I wrote the following method:<\/p>\n<pre><code class=\"cs\">static Project GetProjectFromSolution(String solutionPath,                                        MSBuildWorkspace workspace) {   MSBuildLocator.RegisterDefaults();   Solution currSolution = workspace.OpenSolutionAsync(solutionPath)                                    .Result;   return currSolution.Projects.Single(); }<\/code><\/pre>\n<p>When reviewing the &#8216;Analyzer with Code Fix&#8217; project template, we  did not change the provided template code. Now, we want to write a rule  according to which our analyzer would work. In this regard, it is  necessary to clarify several theoretical points.<\/p>\n<p>Roslyn itself stores source file representations as trees. Look at the following code example:<\/p>\n<pre><code class=\"cs\">if (number > 0) { }<\/code><\/pre>\n<p>Roslyn presents it as a tree with the following structure:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/qj\/ee\/eo\/qjeeeo4zzgxr8m0yukw1f2aphpu.png\" width=\"1519\" height=\"267\" data-src=\"https:\/\/habrastorage.org\/webt\/qj\/ee\/eo\/qjeeeo4zzgxr8m0yukw1f2aphpu.png\"\/><figcaption><\/figcaption><\/figure>\n<p>The tree nodes are blue in the picture. We will work with them specifically. In Roslyn, such trees are represented as the <em>SyntaxTree<\/em>  object types. As you can see in the picture, the tree nodes differ and  each of them is represented by its own type. For example, the <em>IfStatement<\/em> node is represented by the <em>IfStatementSyntax<\/em> class object. All the nodes in their inheritance hierarchy originate from the <em>SyntaxNode<\/em> class. And only then they add some specific properties and methods to the ones they&#8217;ve inherited from the <em>SyntaxNode<\/em> class. For example, the <em>IfStatementSyntax<\/em> contains the <em>Condition<\/em> property. <em>Condition<\/em>, in turn, is a node of the <em>ExpressionSyntax<\/em> type. This order is natural for an object that represents the conditional <em>if<\/em> construction.<\/p>\n<p>When we work with the necessary tree nodes, we can create logic  for rules, according to which our static analyzer will work. For  example, in order to determine in which <em>IfStatement<\/em> operators the true and false branches are completely identical, you need to do the following:<\/p>\n<ul>\n<li>\n<p>Look through all the <em>IfStatementSyntax<\/em> type tree nodes;<\/p>\n<\/li>\n<li>\n<p>When visiting a node, get the <em>Statement<\/em> property value of the <em>IfStatementSyntax<\/em> type object and save the value to the <em>thenBody<\/em> variable;<\/p>\n<\/li>\n<li>\n<p><em>IfStatementSyntax<\/em> has the <em>Else<\/em> property. Get its value and save it to the <em>elseBody<\/em> variable;<\/p>\n<\/li>\n<li>\n<p>The Microsoft.CodeAnalysis.CSharp.dll assembly has the <em>SyntaxFactory<\/em> class, which contains the <em>AreEquivalent<\/em> method. Pass the <em>thenBody<\/em> and <em>elseBody <\/em>variables to this method and the let the <em>AreEquivalent<\/em> method compare objects in those variables.<\/p>\n<\/li>\n<\/ul>\n<p>Based on the algorithm described above, you can write the <em>ApplyRule<\/em> method:<\/p>\n<pre><code class=\"cs\">static bool ApplyRule(IfStatementSyntax ifStatement) {   if (ifStatement?.Else == null)     return false;    StatementSyntax thenBody = ifStatement.Statement;   StatementSyntax elseBody = ifStatement.Else.Statement;   return SyntaxFactory.AreEquivalent(thenBody, elseBody); }<\/code><\/pre>\n<p>As a result we were able to write a rule that would allow us to no longer worry about copy-paste errors in <em>if-else<\/em> branches.<\/p>\n<h3>Which project type to choose for writing your own static analyzer?<\/h3>\n<p>In my opinion, you should base your choice on what you want to get from the analyzer.<\/p>\n<p>If you write a static analyzer that should monitor compliance  with the code style your company requires, then use a project like  &#8216;Analyzer with Code Fix&#8217;. Your analyzer will be conveniently integrated  into the VS environment as an extension. Developers will see the results  of its work right when writing code. Besides, with API from Roslyn, you  can turn on hints (how to change code) and even automatic correction.<\/p>\n<p>If you plan to use the analyzer as a separate application and not  as a plugin, choose the &#8216;Standalone Code Analysis Tool&#8217; project. Let&#8217;s  say that you want to incorporate the analyzer into your CI process and  test projects on a separate server. Another advantage \u2014 the analyzer in  the form of the extension for VS exists inside the 32-bit devenv.exe  process. This process can use only a limited amount of memory. The  analyzer as a separate application isn&#8217;t afraid of such restrictions.  However, Microsoft promises to make Visual Studio 2022 64-bit. If you  make your analyzer for this IDE version, these restrictions on memory  consumption shouldn&#8217;t affect you.<\/p>\n<p>The information in this this article can help you quickly write  your own static analyzer that will solve your problems. What if you want  not just to solve your problems, but detect a wide range of code  defects? Then you have to spend your time and energy on learning how to  use static flow analysis, symbolic calculations, method annotation, and  so on. Only after that your analyzer will be able to compete with the  paid ones and be useful for a large number of developers. If you don&#8217;t  want to spend that much time on this, you can use one of the existing  analyzers. There is a variety of them, both paid and free. Here&#8217;s <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_tools_for_static_code_analysis\">a list of tools for static code analysis<\/a>. If you want to see what these tools can do, read the article &#8216;<a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/csharp\/0787\/\">Top 10 bugs found in C# projects in 2020<\/a>&#8216;.<\/p>\n<p>Besides, don&#8217;t forget that such analyzers provide some part of  their functionality via additional extensions for various IDEs. It&#8217;s  convenient if plugin allows you to launch the analyzer within the IDE.  You do not need to collapse the editor and launch a separate  application. The plugin can also allow you to view the analysis results  inside the IDE.<\/p>\n<h2>Working with Roslyn<\/h2>\n<p>We have inspected the templates that Visual Studio provides to  create a new static code analyzer. Now let&#8217;s take a closer look at  Roslyn API so we can use it efficiently and correctly. The syntax tree  is the first thing we need to get acquainted with.<\/p>\n<h3>Syntax tree<\/h3>\n<p>A compilation object uses the source code to build a syntax tree  for each .cs file. You can see one of the trees in the Syntax Visualizer  window. If you have .NET Compiler Platform SDK for Visual Studio, you  can find this window in View -> Other Windows -> Syntax  Visualizer.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cf6\/91b\/2d9\/cf691b2d98d6d2a08d2fb048ad97b052.png\" width=\"367\" height=\"613\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cf6\/91b\/2d9\/cf691b2d98d6d2a08d2fb048ad97b052.png\"\/><figcaption><\/figcaption><\/figure>\n<p>This is a very useful tool. It is especially useful for those who  are just getting started with the tree structure and the element types  represented in it. When moving through code in the Visual Studio editor,  Syntax Visualizer goes to the corresponding tree element of the code  fragment and highlights it. The Syntax Visualizer window also shows some  properties for the currently selected element. For example, in the  screenshot above, we see a specific type <em>MethodDeclarationSyntax<\/em> for the MethodDeclaration highlighted element. <\/p>\n<p>For more visualization, you can select an element in the Syntax  Visualizer window and invoke this element&#8217;s context menu. As a result,  you get a window that visualizes the syntax tree built for the selected  element:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/y3\/y-\/j2\/y3y-j2kemnbaacoknflqy0vfuuu.png\" width=\"1495\" height=\"365\" data-src=\"https:\/\/habrastorage.org\/webt\/y3\/y-\/j2\/y3y-j2kemnbaacoknflqy0vfuuu.png\"\/><figcaption><\/figcaption><\/figure>\n<p>If you don&#8217;t see this element in the context menu, install DGML  editor. You can do it via the Visual Studio Installer. Open the Visual  Studio Installer and choose More -> Modify next to the desired VS  instance. Then, go to  Individual Component -> Code tools -> DGML  editor. <\/p>\n<p>However, this tool has its disadvantages:<\/p>\n<ul>\n<li>\n<p>If the Syntax Visualizer window is empty even though you chose the  necessary code, then add and erase a space. After this manipulation the  Syntax Visualizer window updates its contents and shows a tree for the  selected code.<\/p>\n<\/li>\n<li>\n<p>This window uses lots of resources, so unless you really need it, close it when working with large source code files.<\/p>\n<\/li>\n<\/ul>\n<p>Earlier in this article, we&#8217;ve mentioned a tree that Roslyn builds for C# code:<\/p>\n<pre><code class=\"cs\">if (number > 0) {  }<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/wz\/q_\/nj\/wzq_nj-6qi_j2kc48vgqi7mhyuq.png\" width=\"1519\" height=\"267\" data-src=\"https:\/\/habrastorage.org\/webt\/wz\/q_\/nj\/wzq_nj-6qi_j2kc48vgqi7mhyuq.png\"\/><figcaption><\/figcaption><\/figure>\n<p>This picture shows that the tree consists of elements represented  by four colors. We can divide all tree elements into three groups:<\/p>\n<ul>\n<li>\n<p>Blue \u2014 syntax tree nodes;<\/p>\n<\/li>\n<li>\n<p>Green \u2014 syntax tokens;<\/p>\n<\/li>\n<li>\n<p>White and grey \u2014 syntax trivia. It contains additional syntax information.<\/p>\n<\/li>\n<\/ul>\n<p>Let&#8217;s take a closer look at every group.<\/p>\n<h4>Syntax nodes<\/h4>\n<p>Syntax nodes represent syntactic constructions: declarations,  operators, expressions, etc. When a tool analyzes the code, the main  work falls on the node processing. The <em>SyntaxNode<\/em> abstract  class is the basic node type. Every node that represents a particular  language construction has a type, inherited from <em>SyntaxNode<\/em>. It  defines a number of properties that simplify working with the tree.  Here are some types along with their corresponding language constructs:<\/p>\n<ul>\n<li>\n<p><em>IfStatementSyntax<\/em> \u2014 the <em>if<\/em> statement;<\/p>\n<\/li>\n<li>\n<p><em>InvocationExpressionSyntax<\/em> \u2014 the method call;<\/p>\n<\/li>\n<li>\n<p><em>ReturnStatementSyntax<\/em> \u2013 the<em>  return <\/em>operator;<\/p>\n<\/li>\n<li>\n<p><em>MemberAccessExpressionSyntax<\/em> \u2014 access to class\/structure members<\/p>\n<\/li>\n<\/ul>\n<p>For example, the <em>IfStatementSyntax <\/em>class has a functionality that was inherited from the <em>SyntaxNode<\/em> class and has other useful properties, such as <em>Condition<\/em>, <em>Statement<\/em> and <em>Else<\/em>. The <em>Condition<\/em> node represents the operator condition; the <em>Statement<\/em> node represents the body of the <em>if<\/em> statement; and the <em>Else<\/em> node represents the <em>else<\/em> block.<\/p>\n<p>The <em>SyntaxNode<\/em> abstract class provides the developer with methods that are common for all nodes. Some of them are listed below:<\/p>\n<ul>\n<li>\n<p><em>ChildNodes<\/em> gets a sequence of nodes that are children of the current one.<\/p>\n<\/li>\n<li>\n<p><em>DescendantNodes<\/em> gets a sequence of all descendant nodes.<\/p>\n<\/li>\n<li>\n<p><em>Contains<\/em> determines whether the node, that was passed as an argument, is a descendant of the current node.<\/p>\n<\/li>\n<li>\n<p><em>IsKind<\/em> takes the <em>SyntaxKind<\/em> enumeration element as  a parameter and returns a boolean value. You can call IsKind for a tree  node. This method checks that the node type that you passed matches the  node type from which IsKind was called.<\/p>\n<\/li>\n<\/ul>\n<p>Besides, a number of properties are defined in the class. One of the most commonly used among them is <em>Parent<\/em>, which contains a reference to the parent node. <\/p>\n<h4>Creating a diagnostic rule with CSharpSyntaxWalker<\/h4>\n<p>When creating a rule based on the &#171;Standalone Code Analysis Tool&#187; project template, we got nodes of the <em>IfStatementSyntax<\/em>  type. Then we worked with them by accessing the tree root and executing  a LINQ query that selects nodes necessary for our analysis. A more  elegant solution is to use the <em>CSharpSyntaxWalker<\/em> class. <em>CSharpSyntaxWalker<\/em> is an abstract class. When we call the <em>Visit<\/em> method, the class traverses the node and its descendant nodes, which are passed to <em>Visit<\/em>. <em>CSharpSyntaxWalker<\/em> performs depth-first traversal. For each node encountered, it calls the <em>Visit<\/em> method corresponding to the node type. For example, for an instance of the <em>ClassDeclarationSyntax<\/em> type it calls the <em>VisitClassDeclaration<\/em> method that takes the node of this type as a parameter. In our case, we need to create a class inherited from the <em>CSharpSyntaxWalker<\/em>. Then we override the method, which is called when <em>CSharpSyntaxWalker<\/em> visits a particular C# construct.<\/p>\n<pre><code class=\"cs\">public class IfWalker : CSharpSyntaxWalker {   public StringBuilder Warnings { get; } = new StringBuilder();    const string warningMessageFormat =      \"'if' with equal 'then' and 'else' blocks is found in file {0} at line {1}\";    static bool ApplyRule(IfStatementSyntax ifStatement)   {     if (ifStatement.Else == null)       return false;      StatementSyntax thenBody = ifStatement.Statement;     StatementSyntax elseBody = ifStatement.Else.Statement;      return SyntaxFactory.AreEquivalent(thenBody, elseBody);   }    public override void VisitIfStatement(IfStatementSyntax node)   {     if (ApplyRule(node))     {       int lineNumber = node.GetLocation()                            .GetLineSpan()                            .StartLinePosition.Line + 1;        warnings.AppendLine(String.Format(warningMessageFormat,                                          node.SyntaxTree.FilePath,                                          lineNumber));     }     base.VisitIfStatement(node);    } }<\/code><\/pre>\n<p>Note that the overridden <em>VisitIfStatement<\/em> method internally calls the <em>base.VisitIfStatement<\/em>  method. This is necessary because the basic implementations of the  Visit methods initiate child nodes&#8217;s traversal. If you want to stop it,  then don&#8217;t call the basic implementation of this method when overriding  the method. <\/p>\n<p>Let&#8217;s create a method, that uses our <em>IfWalker<\/em> class instance to start tree traversal: <\/p>\n<pre><code class=\"cs\">public static void StartWalker(IfWalker ifWalker, SyntaxNode syntaxNode) {    ifWalker.Warnings.Clear();    ifWalker.Visit(syntaxNode); }<\/code><\/pre>\n<p>This is how the <em>Main<\/em> method looks like in this case:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   string solutionPath = @\"D:\\Test\\TestApp.sln\";   string logPath = @\"D:\\Test\\warnings.txt\";    MSBuildLocator.RegisterDefaults();   usng (var workspace = MSBuildWorkspace.Create())   {     Project project = GetProjectFromSolution(solutionPath, workspace);      foreach (var document in project.Documents)     {           var tree = document.GetSyntaxTreeAsync().Result;       var ifWalker = new IfWalker();       StartWalker(ifWalker, tree.GetRoot());        var warnings = ifWalker.Warnings;       if (warnings.Length != 0)         File.AppendAllText(logPath, warnings.ToString());     }   } }<\/code><\/pre>\n<p>It&#8217;s up to you to choose which approach is best for you to get  nodes for the analysis. You can write a LINQ query. You can override the  methods of the <em>CSharpSyntaxWalker<\/em> class that are called when <em>CSharpSyntaxWalker<\/em> visits certain C# nodes. Your choice depends only on what&#8217;s most suitable your task. I think traversal methods from the <em>CSharpSyntaxWalker<\/em>  should be overridden if we plan to add a lot of diagnostic rules to the  analyzer. If your utility is simple and aims at processing a specific  node type, you can use a LINQ query to collect all the necessary C#  nodes. <\/p>\n<h4>Syntax tokens<\/h4>\n<p>Syntax tokens are language grammar terminals. Syntax tokens are  elements that are not further analyzed \u2014 identifiers, keywords, special  characters. We barely work with them during the analysis. During the  analysis, we use tokens to obtain their textual representation or to  check the token type. Tokens are the tree leaves, they don&#8217;t have child  nodes. Besides, tokens are instances of the <em>SyntaxToken<\/em> structure, i.e. they are not inherited from <em>SyntaxNode.<\/em> However, tokens, just like nodes, may have syntax trivia. We&#8217;ll get back to it in one of the article sections.<\/p>\n<p>The main properties of the <em>SyntaxToken<\/em> are:<\/p>\n<ul>\n<li>\n<p><em>RawKind<\/em> &#8212; a numeric representation of the token&#8217;s <em>SyntaxKind<\/em> enumeration element;<\/p>\n<\/li>\n<li>\n<p><em>Value<\/em> &#8212; the token&#8217;s object representation. For example, if a token represents a numeric literal of the <em>int<\/em> type, then Value returns an object of the <em>int<\/em> type with a corresponding value.<\/p>\n<\/li>\n<li>\n<p><em>Text<\/em> &#8212; a text representation of a token.<\/p>\n<\/li>\n<\/ul>\n<p>Creating a diagnostic rule that analyzes syntax tokens<\/p>\n<p>Let&#8217;s create a simple diagnostic rule that uses syntax tokes.  This rule is triggered if a method name doesn&#8217;t start with a capital  letter:<\/p>\n<pre><code class=\"cs\">class Program {   const string warningMessageFormat =     \"Method name '{0}' does not start with capital letter \" +      \"in file {1} at {2} line\";    static void Main(string[] args)   {     if (args.Length != 2)       return;      string solutionPath = args[0];     string logPath = args[1];      StringBuilder warnings = new StringBuilder();      MSBuildLocator.RegisterDefaults();     using (var workspace = MSBuildWorkspace.Create())     {       Project project = GetProjectFromSolution(solutionPath, workspace);        foreach (var document in project.Documents)       {         var tree = document.GetSyntaxTreeAsync().Result;          var methods = tree.GetRoot()                           .DescendantNodes()                           .OfType&lt;MethodDeclarationSyntax>();          foreach (var method in methods)         {           if (ApplyRule(method, out var methodName))           {             int lineNumber = method.Identifier                                    .GetLocation()                                    .GetLineSpan()                                    .StartLinePosition.Line + 1;              warnings.AppendLine(String.Format(warningMessageFormat,                                                methodName,                                                document.FilePath,                                                lineNumber));           }         }       }     }      if (warnings.Length != 0)         File.WriteAllText(logPath, warnings.ToString());   }    static bool ApplyRule(MethodDeclarationSyntax node, out string methodName)   {     methodName = node.Identifier.Text;     return methodName.Length != 0 &amp;&amp; !char.IsUpper(methodName[0]);   } }<\/code><\/pre>\n<p>In this rule, the <em>Identifier<\/em> property of the <em>MethodDeclarationSyntax<\/em>  class determines whether a method name doesn&#8217;t start with a capital  letter. This property stores a token that checks the first character of  its text representation.<\/p>\n<h4>Syntax trivia<\/h4>\n<p>Syntax trivia (additional syntactic information) includes the  following tree elements: comments, preprocessor directives, various  formatting elements (spaces, newline characters). These tree nodes are  not descendants of the <em>SyntaxNode class.<\/em> The syntax trivia  elements don&#8217;t go into the IL code. However, they are represented in the  syntax tree. Thanks to this, you can get completely identical source  code from the existing tree, along with all the elements contained in  all instances of the <em>SyntaxTrivia<\/em> structure. This tree feature  is called full fidelity. The syntax trivia elements always belong to a  token. There are Leading trivia and Trailing trivia. Leading trivia is  additional syntactic information that precedes the token. Trailing  trivia is additional syntactic info that follows the token. All elements  of the additional syntactic information are of the <em>SyntaxTrivia<\/em> type. If you want to determine what exactly the element is, use the <em>SyntaxKind<\/em> enumeration along with the <em>Kind<\/em> and <em>IsKind<\/em> methods:<\/p>\n<p>Look at the following code:<\/p>\n<pre><code class=\"cs\">#if NETCOREAPP3_1   b = 10; #endif   \/\/Comment1   a = b;<\/code><\/pre>\n<p>Here&#8217;s what the directed syntax graph looks like for the code above:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/oq\/no\/jq\/oqnojqqavmy6phbjgzvemmapxgi.png\" width=\"1517\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/webt\/oq\/no\/jq\/oqnojqqavmy6phbjgzvemmapxgi.png\"\/><figcaption><\/figcaption><\/figure>\n<p>You can see that the &#8216;a&#8217; token includes such syntax trivia as the preprocessor directives <em>#if NETCOREAPP3_1<\/em> and <em>#endif<\/em>,  the text itself inside these directives, the space and end-of-line  characters, as well as a one-line comment. The &#8216;=&#8217; token has only one  syntax trivia element attached to it. It&#8217;s the space character. And the  &#8216;;&#8217; token corresponds to the end-of-line character.<\/p>\n<h4>Use of syntax trivia in comment analysis<\/h4>\n<p>In addition to diagnostic rules based on tree node analysis, you  can also create rules that analyze syntax trivia elements. Let&#8217;s imagine  that a company issued a new coding requirement: do not write the  comments that are longer than 130 characters. We decided to check our  project for such &#171;forbidden&#187; comments. We use a simple analyzer that  parses syntax trivia elements. The code structure of this rule is almost  identical to the rule that we created on the base of the &#171;Standalone  Code Analysis Tool&#187; project template. But now, since we need comments,  we call the <em>DescendantTrivia<\/em> method instead of calling the <em>DescendantNodes<\/em> method. After that we choose only those <em>SyntaxTrivia<\/em>, whose type is either <em>SingleLineCommentTrivia<\/em>, or <em>MultiLineCommentTrivia<\/em>, or <em>SingleLineDocumentationCommentTrivia<\/em>:<\/p>\n<pre><code class=\"cs\">.... var comTriv = tree.GetRoot().DescendantTrivia()                                                    .Where(n =>   n.IsKind(SyntaxKind.SingleLineCommentTrivia)                              || n.IsKind(SyntaxKind.                                          SingleLineDocumentationCommentTrivia)                              || n.IsKind(SyntaxKind.MultiLineCommentTrivia)); ....<\/code><\/pre>\n<p>We also added the new <em>SingleLineCommentFormatMessage<\/em> and <em>MultiLineCommentFormatMessage<\/em> format messages for single-line and multi-line comments:<\/p>\n<pre><code class=\"cs\">const string PleaseBreakUpMessage = \"Please, break up it on several lines.\";  string SingleLineCommentFormatMessage =      \"Length of a comment at line {0} in file {1} exceeds {2} characters. \"   + PleaseBreakUpMessage;  string MultiLineCommentFormatMessage =      \"Multiline comment or XML comment at line {0} in file {1} contains \"   + \"individual lines that exceeds {2} characters.\"    + PleaseBreakUpMessage;<\/code><\/pre>\n<p>The last thing that we changed was the <em>ApplyRule<\/em> method:<\/p>\n<pre><code class=\"cs\">void ApplyRule(SyntaxTrivia commentTrivia, StringBuilder warnings) {   const int MaxCommentLength = 130;    const string PleaseBreakUpMessage = ....;    string SingleLineCommentFormatMessage = ....;    string MultiLineCommentFormatMessage = ....;    switch (commentTrivia.Kind())   {     case SyntaxKind.SingleLineCommentTrivia:     case SyntaxKind.SingleLineDocumentationCommentTrivia:       {         if (commentTrivia.ToString().Length > MaxCommentLength)         {           int line = commentTrivia.GetLocation().GetLineSpan()                                   .StartLinePosition.Line + 1;            string filePath = commentTrivia.SyntaxTree.FilePath;           var message = String.Format(SingleLineCommentFormatMessage,                                       line,                                       filePath,                                       MaxCommentLength);           warnings.AppendLine(message);         }         break;       }     case SyntaxKind.MultiLineCommentTrivia:       {         var listStr = commentTrivia.ToString()                                    .Split(new string[] { Environment.NewLine },                                           StringSplitOptions.RemoveEmptyEntries                                           );          foreach (string str in listStr)         {           if (str.Length > MaxCommentLength)           {             int line = commentTrivia.GetLocation().GetLineSpan()                                     .StartLinePosition.Line + 1;              string filePath = commentTrivia.SyntaxTree.FilePath;             var message = String.Format(MultiLineCommentFormatMessage,                                         line,                                         filePath,                                         MaxCommentLength);              warnings.AppendLine(message);                     }         }         break;       }     } }<\/code><\/pre>\n<p>Now the <em>ApplyRule<\/em> method checks that single-line  comments do not exceed 130 characters. In the case of multi-line  comments, this method checks each comment line individually. If the  condition is met, we add the corresponding message to<em> warnings<\/em>.<\/p>\n<p>As a result, the <em>Main<\/em> method, which was designed to search for comments where strings exceed 130 characters, has the following code:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   string solutionPath = @\"D:\\Test\\TestForTrivia.sln\";   string logPath = @\"D:\\Test\\warnings.txt\";    MSBuildLocator.RegisterDefaults();   using (var workspace = MSBuildWorkspace.Create())   {     StringBuilder warnings = new StringBuilder();     Project project = GetProjectFromSolution(solutionPath, workspace);      foreach (var document in project.Documents)     {       var tree = document.GetSyntaxTreeAsync().Result;       var comTriv = tree.GetRoot()                         .DescendantTrivia()                         .Where(n =>                                      n.IsKind(SyntaxKind.SingleLineCommentTrivia)                               || n.IsKind( SyntaxKind                                           .SingleLineDocumentationCommentTrivia)                               || n.IsKind(SyntaxKind.MultiLineCommentTrivia));        foreach (var commentTrivia in comTriv)           ApplyRule(commentTrivia, warnings);     }      if (warnings.Length != 0)       File.AppendAllText(logPath, warnings.ToString());   } }<\/code><\/pre>\n<p>Beside comments, you can also write a rule that searches for preprocessor directives. You can use the same <em>IsKind<\/em> method to determine the contents of the preprocessor directives.<\/p>\n<pre><code>methodDeclaration.DescendantTrivia()                  .Any(trivia => trivia.IsKind(SyntaxKind.IfDirectiveTrivia));<\/code><\/pre>\n<h3>Semantic model and symbols<\/h3>\n<p>In the examples above, we used syntactic trees and traversed  their elements to analyzer projects. In many cases, traversing a syntax  tree with <em>CsharpSyntaxWalker<\/em> is insufficient \u2014 we need to use  additional methods. And here comes the semantic model. A compilation  uses a syntax tree to obtain an object of the <em>SemanticModel <\/em>type. The <em>Compilation.GetSemanticMode<\/em>l is used to do this. It takes an object of the <em>SyntaxTree<\/em> type as a required parameter.<\/p>\n<p>A semantic model provides information about various entities:  methods, local variables, fields, properties, etc. You need to compile  your project without errors in order to obtain a correct semantic model.<\/p>\n<p>So, to get a semantic model, we need an instance of the <em>Compilation<\/em> class. One of the ways to get a compilation object is to call the <em>GetCompilationAsync<\/em> method for the <em>Project<\/em> class instance. Earlier in this article we described how to get and use an instance of this class.<\/p>\n<pre><code class=\"cs\">Compilation compilation = project.GetCompilationAsync().Result;<\/code><\/pre>\n<p>If you want to get a semantic model, call the <em>GetSemanticModel<\/em> method for the compilation object and pass an object of the <em>SyntaxTree<\/em> type:<\/p>\n<pre><code class=\"cs\">SemanticModel model = compilation.GetSemanticModel(tree);<\/code><\/pre>\n<p>Another way to get a semantic model is to call the <em>Create<\/em> method from the <em>CSharpCompilation<\/em> class. We&#8217;ll use this method in examples further in this article.<\/p>\n<p>A semantic model provides access to the so-called symbols. They,  in turn, allow you to get the information about the entity itself (be it  a property, method, or something else). This information is necessary  for the analysis. We can divide symbols into two categories:<\/p>\n<ul>\n<li>\n<p>symbols for getting information about the entity itself;<\/p>\n<\/li>\n<li>\n<p>symbols for getting information about the entity type.<\/p>\n<\/li>\n<\/ul>\n<p>Every symbol contains the information about the type and  namespace, where a particular element is defined. We can find out  exactly where an element was defined: in the source code that you have  access to, or in an external library. Besides, you can get information  about whether the analyzed element is static, virtual, etc. All this  information is provided through the <em>ISymbol<\/em> base interface functionality.<\/p>\n<p>Let&#8217;s use the following situation as an example. Suppose, for the  analysis, you need to determine if a called method was overridden. In  other words, you need to determine if the called method was marked by  the <em>override<\/em> modifier during the declaration. In this case, we need a symbol:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   string codeStr =     @\"     using System;      public class ParentClass     {       virtual public void Mehtod1()       {         Console.WriteLine(\"\"Hello from Parent\"\");       }     }      public class ChildClass: ParentClass     {       public override void Method1()       {         Console.WriteLine(\"\"Hello from Child\"\");       }     }      class Program     {       static void Main(string[] args)         {           ChildClass childClass = new ChildClass();           childClass.Mehtod1();         }     }\";    static SemanticModel GetSemanticModelFromCodeString(string codeString)   {     SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(codeStr);      var msCorLibLocation = typeof(object).Assembly.Location;     var msCorLib = MetadataReference.CreateFromFile(msCorLibLocation);      var compilation = CSharpCompilation.Create(\"MyCompilation\",       syntaxTrees: new[] { tree }, references: new[] { msCorLib });      return compilation.GetSemanticModel(tree);   }    var model = GetSemanticModelFromCodeString(codeStr);    var methodInvocSyntax = model.SyntaxTree.GetRoot()                                .DescendantNodes()                                .OfType&lt;InvocationExpressionSyntax>();    foreach (var methodInvocation in methodInvocSyntax)   {     var methodSymbol = model.GetSymbolInfo(methodInvocation).Symbol;     if (methodSymbol.IsOverride)     {       \/\/Apply your additional logic for analyzing method.     }   } }<\/code><\/pre>\n<p>The <em>GetSemanticModelFromCodeString<\/em> method parses <em>codeStr<\/em> passed as the <em>codeString<\/em> parameter and gets a syntax tree for it. After that an object of the <em>CSharpCompilation<\/em> type is created. This object is a result of compiling a syntax tree, that was obtained from the <em>codeStr<\/em>. We call the <em>CSharpCompilation.Create<\/em>  method to run compilation. An array of syntax trees (source code to be  compiled) and links to libraries are passed to this method. To compile <em>codeStr<\/em>,  you need a reference only to the C# base class library &#8212; mscorlib.dll.  After that, a semantic model object is returned via the <em>CSharpCompilation.GetSemanticModel<\/em> method call. A semantic model is used to get the <em>SymbolInfo<\/em> structure for the node corresponding to the method call. We have the semantic model object returned by <em>CSharpCompilation.GetSemanticModel<\/em>. This object&#8217;s <em>GetSymbolInfo<\/em> method is called, with the node passed to it as a parameter. After we get <em>SymbolInfo<\/em>, we call its <em>Symbol<\/em> property. This property returns the symbol object, which contains the semantic information about the node passed to the <em>GetSymbolInfo<\/em> method. When we get the symbol, we can refer to its <em>IsOverride<\/em> property and determine if the method was obtained via the <em>override<\/em> modifier.<\/p>\n<p>Some readers may suggest another way to determine whether a method is overridden \u2013 without using the semantic model:<\/p>\n<pre><code class=\"cs\">.... var methodDeclarsSyntax = model.SyntaxTree.GetRoot()                                .DescendantNodes()                                .OfType&lt;MethodDeclarationSyntax>(); .... foreach(var methodDeclaration in methodDeclarsSyntax) {   var modifiers = methodDeclaration.Modifiers;   bool isOverriden =       modifiers.Any(modifier => modifier.IsKind(SyntaxKind.OverrideKeyword)); }<\/code><\/pre>\n<p>This way also works, but not in all cases. For example, if the  method isn&#8217;t declared in the source file for which the syntax tree was  obtained, we cannot get a declaration for the necessary method. A more  indicative case is when the called method was declared in an external  library: in this scenario successful analysis cannot do without the  semantic model.<\/p>\n<h4>Obtaining object information. Specifying symbol type<\/h4>\n<p>There are a number of derived types, from which we can get more specific information about an object. Such interfaces include <em>IFieldSymbol<\/em>, <em>IPropertySymbol<\/em>, <em>IMethodSymbol<\/em> and others. If we cast the <em>ISymbol<\/em> object to a more specific interface, we&#8217;ll get access to properties specific to this interface.<\/p>\n<p>For example, if we use the cast to <em>IFieldSymbol<\/em>, we can refer to the <em>IsConst <\/em>field and find out whether the node is a constant field. And if we use the <em>IMethodSymbol<\/em> interface, we can find out whether the method returns any value. <\/p>\n<p>For symbols the semantic model defines the <em>Kind<\/em> property, which returns the elements of the <em>SymbolKind<\/em>  enumeration. With this property we can find out what we are currently  working with: a local object, a field, an assembly, etc. Also, in most  cases, the value of the <em>Kind<\/em> property corresponds to a specific symbol type. This exact feature is used in the following code:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   string codeStr =     @\"     public class MyClass     {       public string MyProperty { get; }     }      class Program     {       static void Main(string[] args)       {         MyClass myClass = new MyClass();         myClass.MyProperty;       }     }\";    ....   var model = GetSemanticModelFromCodeString(codeStr);    var propertyAccessSyntax = model.SyntaxTree.GetRoot().DescendantNodes()                                   .OfType&lt;MemberAccessExpressionSyntax>()                                   .First();    var symbol = model.GetSymbolInfo(propertyAccessSyntax).Symbol;   if (symbol.Kind == SymbolKind.Property)   {     var pSymbol = (IPropertySymbol)symbol;      var isReadOnly = pSymbol.IsReadOnly; \/\/true     var type = pSymbol.Type;             \/\/ System.String   } }<\/code><\/pre>\n<p>After we cast a symbol to <em>IPropertySymbol<\/em>, we can access properties that help to obtain additional information. Again, a simple example: <em>MyProperty<\/em>  is accessed in the same source file where its declaration is located.  This means you can obtain information, that the property doesn&#8217;t have a  setter, without using a semantic model. If the property is declared in  another file or library, then the use of the semantic model is  inevitable.<\/p>\n<h4>Obtaining object type information<\/h4>\n<p>When you need to obtain object type information for an object represented by a node, you can use the <em>ITypeSymbol<\/em> interface. To get it, call the <em>GetTypeInfo<\/em> method for an object of the <em>SemanticModel<\/em> type. This method returns the <em>TypeInfo<\/em> structure, that contains 2 important properties:<\/p>\n<ul>\n<li>\n<p><em>ConvertedType<\/em> returns information about the type of the  expression after the compiler performs an implicit cast. If there was no  cast, the value returned is the same as that returned by the <em>Type<\/em> property;<\/p>\n<\/li>\n<li>\n<p><em>Type<\/em> returns the type of the expression represented in the node. If it is impossible to get the type of the expression, the <em>null<\/em> value is returned. If the type cannot be determined due to some error, the <em>IErrorTypeSymbol<\/em> interface is returned.<\/p>\n<\/li>\n<\/ul>\n<p>Here is an example of how you get the type of a property that is assigned a value:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {     string codeStr =     @\"     public class MyClass     {       public string MyProperty { get; set; }            public MyClass(string value)       {         MyProperty = value;       }     }\";    ....   var model = GetSemanticModelFromCodeString(codeStr);    var assignmentExpr = model.SyntaxTree.GetRoot().DescendantNodes()                             .OfType&lt;AssignmentExpressionSyntax>()                             .First();    ExpressionSyntax left = assignmentExpr.Left;    ITypeSymbol typeOfMyProperty = model.GetTypeInfo(left).Type; }<\/code><\/pre>\n<p>If you use the <em>ITypeSymbol<\/em> interface, returned by these  properties, you can get all the information about the necessary type.  This information is extracted by accessing the properties, some of which  are listed below:<\/p>\n<ul>\n<li>\n<p><em>AllInterfaces <\/em>is a list of all interfaces a type implements. The interfaces implemented by base types are also taken into account;<\/p>\n<\/li>\n<li>\n<p><em>BaseType<\/em> is the base type;<\/p>\n<\/li>\n<li>\n<p><em>Interfaces<\/em> is a list of interfaces implemented directly by this type;<\/p>\n<\/li>\n<li>\n<p><em>IsAnonymousType <\/em>is information about whether a type is anonymous.<\/p>\n<\/li>\n<\/ul>\n<h4>Some comments on the use of the semantic model  <\/h4>\n<p>Accessing the semantic model during the analysis has its price.  Tree traversal operations are faster than obtaining a semantic model.  Therefore, if you want to get different symbols for nodes belonging to  the same syntax tree, you need to get the semantic model only once.  Then, if necessary, refer to the same instance of the <em>SemanticModel<\/em> class.<\/p>\n<p>As additional information about using the semantic model, I also recommend using the following resources:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/joshvarty.com\/2014\/10\/30\/learn-roslyn-now-part-7-introducing-the-semantic-model\/\">Learn Roslyn Now: Part 7 Introducing the Semantic Model<\/a> is a great learning blog with examples of how to use Roslyn;<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/csharp\/0399\/\">Introduction to Roslyn. Using static analysis tools for development<\/a> is a good introduction to the general principles of Roslyn-based static analysis.<\/p>\n<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>Well, I think the information presented here is enough to start  an in-depth study of the capabilities of Roslyn. You can even write a  simple \u2013 or maybe complex \u2013 static analyzer. Undoubtedly, to create  serious tools, you need to take into account many different nuances and  learn much more about both static analysis in general and Roslyn. This  article, I hope, will be an excellent assistant at the beginning of your  journey.<\/p>\n<p>For a more detailed study of the Roslyn API, I advise you to study <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/roslyn-sdk\/\">the documentation on the Microsoft website<\/a>. If you want to improve, fix or study the source code of this API, then welcome to <a href=\"https:\/\/github.com\/dotnet\/roslyn\">its GitHub repository<\/a>. Believe me, there is still a lot to improve and fix in its API.  For example, here is one article: &#171;<a href=\"https:\/\/pvs-studio.com\/en\/blog\/posts\/csharp\/0622\/\">We check the source code of Roslyn<\/a>&#171;. There we checked the Roslyn API source code with the help of the PVS-Studio static analyzer and found a lot of errors.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/579736\/\"> https:\/\/habr.com\/ru\/articles\/579736\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>After you read this article, you&#8217;ll have the knowledge to create your  own static analyzer for C#. With the help of the analyzer, you can find  potential errors and vulnerabilities in the source code of your own and  other projects. Are you intrigued? Well, let&#8217;s get started.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>First, we will make your own static analyzer from the Visual  Studio templates, without going deeper into the Roslyn API. This allows  you to quickly get a working application and at least roughly see what  analyzers can do.<\/p>\n<p>And after that, we&#8217;ll take a closer look at Roslyn API, as well  as various tools that allow you to perform deeper and more complex  analysis.<\/p>\n<h2>Static analyzers: what are they and why do we need them?<\/h2>\n<p>I&#8217;m sure that many developers have some mistakes that they or  their friends often make when writing code. Most likely you would like  to have a tool that detects such errors without your participation. This  tool is called a static analyzer. <\/p>\n<p>A static analyzer is an automatic tool that searches for  potential errors and vulnerabilities in a program&#8217;s source code without  launching the app directly.<\/p>\n<p>However, what if the existing analyzers can&#8217;t find what you want?  The answer is simple \u2014 you create your own utility or even an entire  analyzer. C# developers are very lucky. Thanks to Roslyn they can create  their own static analyzer. This is exactly what this article is about.<\/p>\n<h2>Creating an analyzer based on Visual Studio templates<\/h2>\n<p>All our further static analyzer development will be based on the <a href=\"https:\/\/github.com\/dotnet\/roslyn\">.NET Compiler Platform<\/a>  aka Roslyn. Thanks to the capabilities this platform provides, we can  use C# to create our own static analysis tools. Here, the word &#8216;static&#8217;  means that the analyzed code doesn&#8217;t need to be executed.<\/p>\n<p>Since our analyzer is based on Roslyn, we should install the .NET  Compiler Platform SDK for Visual Studio. One of the ways to do so is to  open the Visual Studio Installer and select &#8216;Visual Studio extension  development&#8217; in the &#8216;Workloads&#8217; tab.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>After we install the necessary toolset, we can start creating the analyzer. <\/p>\n<p>Open Visual Studio, click on &#8216;Create a new project&#8217;, select C#.  Specify Windows as the platform and select Roslyn as the project type.  After this we should see three projects templates. We are interested in  two: &#8216;Analyzer with Code Fix (.NET Standard)&#8217; and &#8216;Standalone Code  Analysis Tool&#8217;.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>Let&#8217;s study each of the templates.<\/p>\n<h3>Description of the &#171;Analyzer with Code Fix (.NET Standard)&#187; project and an example of its use<\/h3>\n<p>After we create a new project with the &#8216;Analyzer with Code Fix  (.NET Standard)&#8217; template, we get a solution with five projects inside.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>Now we pay our full attention to the first project called <em>TestAnalyzer<\/em>. The main work on the analyzer is performed in this exact project. Open the <em>TestAnalyzerAnalyzer.cs<\/em>  file. It already contains an example of a simple rule for a static  analyzer. The rule searches through all type(class) names in source  code. If a type&#8217;s name has lowercase characters, the rule underlines it  with a green wavy line. Besides, if you hover the cursor on the type  name marked with a wavy line, you see a familiar light bulb symbol. It  offers to automatically correct the type name and bring all the  characters to uppercase:<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>The easiest way to see it is to launch a new VS instance, which  already has our sample diagnostic rule. You can use the same approach  for debugging. To do this, mark <em>TestAnalyzer.vsix<\/em> as a startup  project and launch the application. After that, a so-called experimental  Visual Studio instance window will open. A new diagnostic rule is  already added in this VS instance. It is integrated with the installed  VSIX extension that has the name of our test analyzer.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>Next, we create a new console project in the running VS instance. In this project, we see that the <em>Program<\/em>  class name is underlined with a green wavy line. This is the work of  our diagnostic rule, as the class name contains lowercase characters.<\/p>\n<h3>Create an analyzer based on the &#171;Standalone Code Analysis Tool&#187; project template<\/h3>\n<p>Now, let&#8217;s create a new project of the &#8216;Standalone Code Analysis  Tool&#8217; type. In fact, it&#8217;s a project of an ordinary console application  with links to the necessary DLLs for analysis:<\/p>\n<ul>\n<li>\n<p>Microsoft.CodeAnalysis.CSharp.Analyzers.dll; <\/p>\n<\/li>\n<li>\n<p>Microsoft.CodeAnalysis.Analyzers.dll; <\/p>\n<\/li>\n<li>\n<p>Microsoft.CodeAnalysis.Workspaces.MSBuild.dll; <\/p>\n<\/li>\n<li>\n<p>etc. <\/p>\n<\/li>\n<\/ul>\n<p>We can delete all methods except <em>Main,<\/em> from the <em>Program.cs<\/em> file.<\/p>\n<p>Let&#8217;s write the analyzer in such a way that it can find <em>if<\/em>  statements, in which true and false branches are identical. Would you  say that no one makes such mistakes? Surprisingly, this is a fairly  common pattern. Look at the <a href=\"https:\/\/pvs-studio.com\/en\/blog\/examples\/v3004\/\">list of similar errors<\/a> found in open source projects.<\/p>\n<p>Let&#8217;s say we are not satisfied if code contains a fragment like this:<\/p>\n<pre><code>public static void MyFunc1(int count) {   if (count > 100)   {     Console.WriteLine(\"Hello world!\");   }   else   {     Console.WriteLine(\"Hello world!\");   } }<\/code><\/pre>\n<p>So, we make the analyzer write the line number and the full path  to the source file into the log file. Let&#8217;s move on to writing code:<\/p>\n<pre><code class=\"cs\">static void Main(string[] args) {   if (args.Length != 2)     return;    string solutionPath = args[0];   string logPath = args[1];    StringBuilder warnings = new StringBuilder();    const string warningMessageFormat =     \"'if' with equal 'then' and 'else' blocks is found in file {0} at line {1}\";    MSBuildLocator.RegisterDefaults();   using (var workspace = MSBuildWorkspace.Create())   {     Project currProject = GetProjectFromSolution(solutionPath, workspace);     foreach (var document in currProject.Documents)     {       var tree = document.GetSyntaxTreeAsync().Result;       var ifStatementNodes = tree.GetRoot()                                  .DescendantNodesAndSelf()                                  .OfType&lt;IfStatementSyntax>();        foreach (var ifStatement in ifStatementNodes)       {         if (ApplyRule(ifStatement))         {           int lineNumber = ifStatement.GetLocation()                                       .GetLineSpan()                                       .StartLinePosition.Line + 1;            warnings.AppendLine(String.Format(warningMessageFormat,                                             document.FilePath,                                             lineNumber));         }       }     }      if (warnings.Length != 0)       File.AppendAllText(logPath, warnings.ToString());   } }<\/code><\/pre>\n<p>In our case, we use a console application and not a plugin for  VS. Thus, we need to specify the path to the solution file, which we are  going to analyze. In order to get the solution, we use the <em>MSBuildWorkspace<\/em> class and the <em>OpenSolutionAsync<\/em> method. In its turn, the <em>Solution<\/em> class contains the <em>Projects<\/em>  property, which stores the project entities. In my case, I created a  new solution with a single console application project. Therefore, to  get the project entity, I wrote the following method:<\/p>\n<pre><code class=\"cs\">static Project GetProjectFromSolution(String solutionPath,                                        MSBuildWorkspace workspace) {   MSBuildLocator.RegisterDefaults();   Solution currSolution = workspace.OpenSolutionAsync(solutionPath)                                    .Result;   return currSolution.Projects.Single(); }<\/code><\/pre>\n<p>When reviewing the &#8216;Analyzer with Code Fix&#8217; project template, we  did not change the provided template code. Now, we want to write a rule  according to which our analyzer would work. In this regard, it is  necessary to clarify several theoretical points.<\/p>\n<p>Roslyn itself stores source file representations as trees. Look at the following code example:<\/p>\n<pre><code class=\"cs\">if (number > 0) { }<\/code><\/pre>\n<p>Roslyn presents it as a tree with the following structure:<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>The tree nodes are blue in the picture. We will work with them specifically. In Roslyn, such trees are represented as the <em>SyntaxTree<\/em>  object types. As you can see in the picture, the tree nodes differ and  each of them is represented by its own type. For example, the <em>IfStatement<\/em> node is represented by the <em>IfStatementSyntax<\/em> class object. All the nodes in their inheritance hierarchy originate from the <em>SyntaxNode<\/em> class. And only then they add some specific properties and methods to the ones they&#8217;ve inherited from the <em>SyntaxNode<\/em> class. For example, the <em>IfStatementSyntax<\/em> contains the <em>Condition<\/em> property. <em>Condition<\/em>, in turn, is a node of the <em>ExpressionSyntax<\/em> type. This order is natural for an object that represents the conditional <em>if<\/em> construction.<\/p>\n<p>When we work with the necessary tree nodes, we can create logic  for rules, according to which our static analyzer will work. For  example, in order to determine in which <em>IfStatement<\/em> operators the true and false branches are completely identical, you need to do the following:<\/p>\n<ul>\n<li>\n<p>Look through all the <em>IfStatementSyntax<\/em> type tree nodes;<\/p>\n<\/li>\n<li>\n<p>When visiting a node, get the <em>Statement<\/em> property value of the <em>IfStatementSyntax<\/em> type object and save the value to the <em>thenBody<\/em> variable;<\/p>\n<\/li>\n<li>\n<p><em>IfStatementSyntax<\/em> has the <em>Else<\/em> property. Get its value and save it to the <em>elseBody<\/em> variable;<\/p>\n<\/li>\n<li>\n<p>The Microsoft.CodeAnalysis.CSharp.dll assembly has the <em>SyntaxFactory<\/em> class, which contains the <em>AreEquivalent<\/em> method. Pass the <em>thenBody<\/em> and <em>elseBody <\/em>variables to this method and the let the <em>AreEquivalent<\/em> method compare objects in those variables.<\/p>\n<\/li>\n<\/ul>\n<p>Based on the algorithm described above, you can write the <em>ApplyRule<\/em> method:<\/p>\n<pre><code class=\"cs\">static bool ApplyRule(IfStatementSyntax ifStatement) {   if (ifStatement?.Else == null)     return false;    StatementSyntax thenBody = ifStatement.Statement;   StatementSyntax elseBody = ifStatement.Else.Statement;   return SyntaxFactory.AreEquivalent(thenBody, elseBody); }<\/code><\/pre>\n<p>As a result we were able to write a rule that would allow us to no longer worry about copy-paste errors in <em>if-else<\/em> branches.<\/p>\n<h3>Which project type to choose for writing your own static analyzer?<\/h3>\n<p>In my opinion, you should base your choice on what you want to get from the analyzer.<\/p>\n<p>If you write a static analyzer that should monitor compliance  with the code style your company requires, then use a project like  &#8216;Analyzer with Code Fix&#8217;. Your analyzer will be<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-417172","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/417172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=417172"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/417172\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=417172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=417172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=417172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}