{"action":"created","comment":{"url":"https://api.github.com/repos/dotnet/docs/pulls/comments/216844978","pull_request_review_id":154403714,"id":216844978,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNjg0NDk3OA==","diff_hunk":"@@ -0,0 +1,505 @@\n+---\n+title: \"Tutorial: Write your first analyzer and code fix\"\n+description: This tutorial provides step-by-step instructions to build an analyzer and code fix using the .NET Compiler SDK (Roslyn APIs).\n+ms.date: 08/01/2018\n+ms.custom: mvc\n+---\n+# Tutorial: Write your first analyzer and code fix\n+\n+The .NET Compiler Platform SDK provides the tools you need to create custom warnings that target C# or Visual Basic code. Your **analyzer** contains code that recognizes violations of your rule. Your **code fix** contains the code that fixes the violation. The rules you implement can be anything from code structure to coding style to naming conventions and more. The .NET Compiler Platform provides the framework for running analysis as developers are writing code, and all the Visual Studio UI features for fixing code: showing squiggles in the editor, populating the Visual Studio Error List, creating the \"light bulb\" suggestions and showing the rich preview of the suggested fixes.\n+\n+In this tutorial, you'll explore the creation of an **analyzer** and an accompanying **code fix** using the Roslyn APIs. An analyzer is a way to perform source code analysis and report a problem to the user. Optionally, an analyzer can also provide a code fix that represents a modification to the user's source code. This tutorial creates an analyzer that finds local variable declarations that could be declared using the `const` modifier but are not. The accompanying code fix modifies those declarations to add the `const` modifier.\n+\n+## Prerequisites\n+\n+* [Visual Studio 2017](https://www.visualstudio.com/downloads)\n+\n+You'll need to install the **.NET Compiler Platform SDK**:\n+\n+[!INCLUDE[interactive-note](~/includes/roslyn-installation.md)]\n+\n+There are several steps to creating and validating your analyzer:\n+\n+1. Create the solution.\n+1. Register the analyzer name and description.\n+1. Report analyzer warnings and recommendations.\n+1. Implement the code fix to accept recommendations.\n+1. Improve the analysis through unit tests.\n+\n+## Explore the analyzer template\n+\n+Your analyzer reports to the user any local variable declarations that can be converted to local constants. For example, consider the following code:\n+\n+```csharp\n+int x = 0;\n+Console.WriteLine(x);\n+```\n+\n+In the code above, `x` is assigned a constant value and is never modified. It can be declared using the `const` modifier:\n+\n+```csharp\n+const int x = 0;\n+Console.WriteLine(x);\n+```\n+\n+The analysis to determine whether a variable can be made constant is involved, requiring syntactic analysis, constant analysis of the initializer expression and dataflow analysis to ensure that the variable is never written to. The .NET Compiler Platform provides APIs that make it easier to perform this analysis. The first step is to create a new C# **Analyzer with code fix** project.\n+\n+* In Visual Studio, choose **File > New > Project...** to display the New Project dialog.\n+* Under **Visual C# > Extensibility**, choose **Analyzer with code fix (.NET Standard)**.\n+* Name your project \"**MakeConst**\" and click OK.\n+\n+The analyzer with code fix template creates three projects: one contains the analyzer and code fix, the second is a unit test project, and the third is the VSIX project. The default startup project is the VSIX project. Press **F5** to start the VSIX project. This starts a second instance of Visual Studio that has loaded your new analyzer.\n+\n+> [!TIP]\n+> When you run your analyzer, you start a second copy of Visual Studio. This second copy uses a different registry hive to store settings. That enables you to differentiate the visual settings in the two copies of Visual Studio. You can pick a different theme for the experimental run of Visual Studio. In addition, don't roam your settings or login to your Visual Studio account using the experimental run of Visual Studio. That keeps the settings different.\n+\n+In the second Visual Studio instance that you just started, create a new C# Console Application project (either .NET Core or .NET Framework project will work -- analyzers work at the source level.) Hover over the token with a wavy underline, and the warning text provided by an analyzer appears.\n+\n+The template creates an analyzer that reports a warning on each type declaration where the type name contains lowercase letters, as shown in the following figure:\n+\n+![Analyzer reporting warning](media/how-to-write-csharp-analyzer-code-fix/report-warning.png)\n+\n+The template also provides a code fix that changes any type name containing lower case characters to all upper case. You can click on the light bulb displayed with the warning to see the suggested changes. Accepting the suggested changes updates the type name and all references to that type in the solution. Now that you've seen the initial analyzer in action, close the second Visual Studio instance and return to your analyzer project.\n+\n+You don't have to start a second copy of Visual Studio and create new code to test every change in your analyzer. The template also creates a unit test project for you. That project contains two tests. `Test1` shows the typical format of a test that analyzes code without triggering a diagnostic. `Test2` shows the format of a test that triggers a diagnostic, and then applies a suggested code fix. As you build your analyzer and code fix, you'll write tests for different code structures to verify your work. Unit tests for analyzers are much quicker than testing them interactively with Visual Studio.\n+\n+> [!TIP]\n+> Analyzer unit tests are a great tool when you know what code constructs should and shouldn't trigger your analyzer. Loading your analyzer in another copy of Visual Studio is a great tool to explore and find constructs you may not have thought about yet.\n+\n+## Create analyzer registrations\n+\n+The template creates the initial `DiagnosticAnalyzer` class, in the **MakeConstAnalyzer.cs** file. This initial analyzer shows two important properties of every analyzer.\n+\n+* Every diagnostic analyzer must provide a `[DiagnosticAnalyzer]` attribute that describes the language it operates on.\n+* Every diagnostic analyzer must derive from the <xref:Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer> class.\n+\n+The template also shows the basic features that are part of any analyzer:\n+\n+1. Register actions. The actions represent code changes that should trigger your analyzer to examine code for violations. When Visual Studio detects code edits that match a registered action, it calls your analyzer's registered method.\n+1. Create diagnostics. When your analyzer detects a violation, it creates a diagnostic object that Visual Studio uses to notify the user of the violation.\n+\n+You register actions in your override of <xref:Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext)?displayProperty=nameWithType> method. In this tutorial, you'll visit **syntax nodes** looking for local declarations, and see which of those have constant values. If a declaration could be constant, your analyzer will create and report a diagnostic.\n+\n+The first step is to update the registration constants and `Initialize` method so these constants indicate your \"Make Const\" analyzer. Most of the string constants are defined in the string resource file. You should follow that practice for easier localization. Open the **Resources.resx** file for the **MakeConst** analyzer project. This displays the resource editor. Update the string resources as follows:\n+\n+* Change `AnalyzerTitle` to \"Variable can be made constant\".\n+* Change `AnalyzerMessageFormat` to \"Can be made constant\".\n+* Change `AnalyzerDescription` to \"Make Constant\".\n+\n+Also, change the **Access Modifier** drop-down to `public`. That makes it easier to use these constants in unit tests. When you have finished, the resource editor should appear as follow figure shows:\n+\n+![Update string resources](media/how-to-write-csharp-analyzer-code-fix/update-string-resources.png)\n+\n+The remaining changes are in the analyzer file. Open **MakeConstAnalyzer.cs** in Visual Studio. Change the registered action from one that acts on symbols to one that acts on syntax. In the `MakeConstAnalyzerAnalyzer.Initialize` method, find the line that registers the action on symbols:\n+\n+```csharp\n+context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);\n+```\n+\n+Replace it with the following line:\n+\n+[!code-csharp[Register the node action](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstAnalyzer.cs#RegisterNodeAction \"Register a node action\")]\n+\n+After that change, you can delete the `AnalyzeSymbol` method. This analyzer examines <xref:Microsoft.CodeAnalysis.CSharp.SyntaxKind.LocalDeclarationStatement?displayProperty=nameWithType>, not <xref:Microsoft.CodeAnalysis.SymbolKind.NamedType?displayProperty=nameWithType> statements. Notice that `AnalyzeNode` has red squiggles under it. The code you just added references an `AnalyzeNode` method that hasn't been declared. Declare that method using the following code:\n+\n+```csharp\n+private void AnalyzeNode(SyntaxNodeAnalysisContext context)\n+{\n+}\n+```\n+\n+Change the `Category` to \"Usage\" in **MakeConstAnalyzer.cs** as shown in the following code:\n+\n+```csharp\n+private const string Category = \"Usage\";\n+```\n+\n+## Find local declarations that could be const\n+\n+It's time to write the first version of the `AnalyzeNode` method. It should look for a single local declaration that could be `const` but is not, like the following code:\n+\n+```csharp\n+int x = 0;\n+Console.WriteLine(x);\n+```\n+\n+The first step is to find local declarations. Change the parameter name from `obj` to `context`. Add the following code to `AnalyzeNode` in **MakeConstAnalyzer.cs**:\n+\n+```csharp\n+var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;\n+```\n+\n+This cast always succeeds because your analyzer registered for changes to local declarations, and only local declarations. No other node type triggers a call to your `AnalyzeNode` method. Next, check the declaration for any `const` modifiers. If you find them, return immediately. The following code looks for any `const` modifiers on the local declaration:\n+\n+```csharp\n+// make sure the declaration isn't already const:\n+if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))\n+{\n+    return;\n+}\n+```\n+\n+Finally, you need to check that the variable could be `const`. That means making sure it is never assigned after it is initialized.\n+\n+You'll perform some semantic analysis using the <xref:Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext>. You use the `context` argument to determine whether the local variable declaration can be made `const`. A <xref:Microsoft.CodeAnalysis.SemanticModel?displayProperty=nameWithType> represents of all semantic information in a single source file. You can learn more in the article that covers [semantic models](../work-with-semantics.md). You'll use the <xref:Microsoft.CodeAnalysis.SemanticModel?displayProperty=nameWithType> to perform data flow analysis on the local declaration statement. Then, you use the results of this data flow analysis to ensure that the local variable isn't written with a new value anywhere else. Call the <xref:Microsoft.CodeAnalysis.ModelExtensions.GetDeclaredSymbol%2A> extension method to retrieve the <xref:Microsoft.CodeAnalysis.ILocalSymbol> for the variable and check that it isn't contained with the <xref:Microsoft.CodeAnalysis.DataFlowAnalysis.WrittenOutside%2A?displayProperty=nameWithType> collection of the data flow analysis. Add the following code to the end of the `AnalyzeNode` method:\n+\n+```csharp\n+// Perform data flow analysis on the local declaration.\n+var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);\n+\n+// Retrieve the local symbol for each variable in the local declaration\n+// and ensure that it is not written outside of the data flow analysis region.\n+var variable = localDeclaration.Declaration.Variables.Single();\n+var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);\n+if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))\n+{\n+    return;\n+}\n+```\n+\n+The code just added ensures that the variable isn't modified, and can therefore be made `const`. It's time to raise the diagnostic. Add the following code as the last line in `AnalyzeNode`:\n+\n+```csharp\n+context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));\n+```\n+\n+You can check your progress by pressing **F5** to run your analyzer. You can load the console application you created earlier and then add the following test code:\n+\n+```csharp\n+int x = 0;\n+Console.WriteLine(x);\n+```\n+\n+The light bulb should appear, and your analyzer should report a diagnostic. However, the light bulb still uses the template generated code fix, and tells you it can be made upper case. The next section explains how to write the code fix.\n+\n+## Write the code fix\n+\n+An analyzer can provide one or more code fixes. A code fix defines an edit that addresses the reported issue. For the analyzer that you created, you can provide a code fix that inserts the const keyword:\n+\n+```csharp\n+const int x = 0;\n+Console.WriteLine(x);\n+```\n+\n+The user chooses it from the light bulb UI in the editor and Visual Studio changes the code.\n+\n+Open the **CodeFixProvider.cs** file added by the template.  This code fix is already wired up to the Diagnostic ID produced by your diagnostic analyzer, but it doesn't yet implement the right code transform. First you should remove some of the template code. Change the title string to \"Make constant\":\n+\n+[!code-csharp[Update the CodeFix title](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#CodeFixTitle \"Update the CodeFix title\")]\n+\n+Next, delete the `MakeUppercaseAsync` method. It no longer applies.\n+\n+All code fixes derive from <xref:Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider>. They all override <xref:Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider.RegisterCodeFixesAsync(Microsoft.CodeAnalysis.CodeFixes.CodeFixContext)?displayProperty=nameWithType> to report available code fixes. In `RegisterCodeFixesAsync`, change the ancestor node type you're searching for to a <xref:Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax> to match the diagnostic:\n+\n+[!code-csharp[Find local declaration node](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#FindDeclarationNode  \"Find the local declaration node that raised the diagnostic\")]\n+\n+Next, change the last line to register a code fix. Your fix will create a new document that results from adding the `const` modifier to an existing declaration:  \n+\n+[!code-csharp[Register the new code fix](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#RegisterCodeFix  \"Register the new code fix\")]\n+\n+You'll notice red squiggles in the code you just added on the symbol `MakeConstAsync`. Add a declaration for `MakeConstAsync` like the following code:\n+\n+```csharp\n+private async Task<Document> MakeConstAsync(Document document,\n+   LocalDeclarationStatementSyntax localDeclaration,\n+   CancellationToken cancellationToken)\n+{\n+}\n+```\n+\n+Your new `MakeConstAsync` method will transform the <xref:Microsoft.CodeAnalysis.Document> representing the user's source file into a new <xref:Microsoft.CodeAnalysis.Document> that now contains a `const` declaration.\n+\n+You create a new `const` keyword token to insert at the front of the declaration statement. Be careful to first remove any leading trivia from the first token of the declaration statement and attach it to the `const` token. Add the following code to the `MakeConstAsync` method:\n+\n+[!code-csharp[Create a new const keyword token](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#CreateConstToken  \"Create the new const keyword token\")]\n+\n+Next, add the `const` token to the declaration using the following code:\n+\n+```csharp\n+// Insert the const token into the modifiers list, creating a new modifiers list.\n+var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);\n+// Produce the new local declaration.\n+var newLocal = trimmedLocal\n+    .WithModifiers(newModifiers)\n+    .WithDeclaration(localDeclaration.Declaration);\n+```\n+\n+Next, format the new declaration to match C# formatting rules. Formatting your changes to match existing code creates a better experience. Add the following statement immediately after the existing code:\n+\n+[!code-csharp[Format the new declaration](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#FormatLocal  \"Format the new declaration\")]\n+\n+A new namespace is required for this code. Add the following `using` statement to the top of the file:\n+\n+```csharp\n+using Microsoft.CodeAnalysis.Formatting;\n+```\n+\n+The final step is to make your edit. There are three steps to this process:\n+\n+1. Get a handle to the existing document.\n+1. Create a new document by replacing the existing declaration with the new declaration.\n+1. Return the new document.\n+\n+Add the following code to the end of the `MakeConstAsync` method:\n+\n+[!code-csharp[replace the declaration](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#ReplaceDocument  \"Generate a new document by replacing the declaration\")]\n+\n+Your code fix is ready to try.  Press F5 to run the analyzer project in a second instance of Visual Studio. In the second Visual Studio instance, create a new C# Console Application project and add a few local variable declarations initialized with constant values to the Main method. You'll see that they are reported as warnings as below.\n+\n+![Can make const warnings](media/how-to-write-csharp-analyzer-code-fix/make-const-warning.png)\n+\n+Notice that if you type const before each variable, the warnings are automatically removed. Additionally, changing a variable to const can affect the reporting of other variables. Add the `const` modifier to both `i` and `j`, and you get a new warning on `k` because it can now be `const`.\n+\n+## Build data driven tests\n+\n+Your analyzer and code fix work on a simple case of a single declaration that can be made const. There are numerous possible declaration statements where this implementation makes mistakes. You'll address these cases by working with the unit test library written by the template. It's much faster than repeatedly opening a second copy of Visual Studio.\n+\n+Open the **MakeConstUnitTests.cs** file in the unit test project. The template created two tests that follow the two common patterns for an analyzer and code fix unit test. `Test1` shows the pattern for a test that ensures the analyzer doesn't report a diagnostic when it shouldn't. `Test2` shows the pattern for reporting a diagnostic and running the code fix.\n+\n+The code for almost every test for your analyzer follows one of these two patterns. For the first step, you can rework these tests as data driven tests. Then, it will be easy to create new tests by adding new string constants to represent different test inputs.\n+\n+For efficiency, the first step is to refactor the two tests into data driven tests. Then, you only need to define a couple string constants for each new test. While your refactoring, rename both methods to better names. Replace `Test1` with this test that ensures no diagnostic is raised:\n+\n+```csharp\n+[DataTestMethod]\n+[DataRow(\"\")]\n+public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)\n+{\n+    VerifyCSharpDiagnostic(testCode);\n+}\n+```\n+\n+You can create a new data row for this test by defining any code fragment that should not cause your diagnostic to trigger a warning. This overload of `VerifyCSharpDiagnostic` passes when there are no diagnostics triggered for the source code fragment.  \n+\n+Next, replace `Test2` with this test that ensures a diagnostic is raised and a code fix applied for the source code fragment:\n+\n+```csharp\n+[DataTestMethod]\n+[DataRow(LocalIntCouldBeConstant, LocalIntCouldBeConstantFixed, 10, 13)]\n+public void WhenDiagosticIsRaisedFixUpdatesCode(\n+    string test,\n+    string fixTest,\n+    int line,\n+    int column)\n+{\n+    var expected = new DiagnosticResult\n+    {\n+        Id = MakeConstAnalyzer.DiagnosticId,\n+        Message = new LocalizableResourceString(nameof(MakeConst.Resources.AnalyzerMessageFormat), MakeConst.Resources.ResourceManager, typeof(MakeConst.Resources)).ToString(),\n+        Severity = DiagnosticSeverity.Warning,\n+        Locations =\n+            new[] {\n+                    new DiagnosticResultLocation(\"Test0.cs\", line, column)\n+                }\n+    };\n+\n+    VerifyCSharpDiagnostic(test, expected);\n+\n+    VerifyCSharpFix(test, fixTest);\n+}\n+```\n+\n+The preceding code also made a couple changes to the code that builds the expected diagnostic result. It uses the public constants registered in the `MakeConst` analyzer. In addition, it uses two string constants for the input and fixed source:\n+\n+[!code-csharp[string constants for fix test](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#FirstFixTest \"string constants for fix test\")]\n+\n+Run these two tests to make sure they pass. In Visual Studio, open the **Test Explorer** by selecting **Test** > **Windows** > **Test Explorer**.  The press the **Run All** link.\n+\n+## Create tests for valid declarations\n+\n+As a general rule, analyzers should exist as quickly as possible, doing minimal work. Visual Studio calls registered analyzers as the user edits code. Responsiveness is a key requirement.  There are a number of tests to add for declarations that should not raise your diagnostic. There are several test cases for code that should not raise your diagnostic. Your analyzer already handles one of those tests, the case where a variable is assigned after being initialized. Add the following string constant to your tests to represent that case:\n+\n+[!code-csharp[variable assigned](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#VariableAssigned \"a variable that is assigned after being initialized won't raise the diagnostic\")]\n+\n+Then, add a data row for this test as shown in the snippet below:\n+\n+```csharp\n+[DataTestMethod]\n+[DataRow(\"\"),\n+ DataRow(VariableAssigned)]\n+public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)\n+```\n+\n+This test passes as well. Next, add constants for conditions you haven't handled yet:\n+\n+* Declarations that are already `const`, because they are already const:\n+\n+   [!code-csharp[already const declaration](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#AlreadyConst \"a declaration that is already const should not raise the diagnostic\")]\n+\n+* Declarations that have no initializer, because there is no value to use:\n+\n+   [!code-csharp[declarations that have no initializer](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#NoInitializer \"a declaration that has no initializer should not raise the diagnostic\")]\n+\n+* Declarations where the initializer is not a constant, because they can't be compile-time constants:\n+\n+   [!code-csharp[declarations where the initializer isn't const](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#InitializerNotConstant \"a declaration where the initializer is not a compile-time constant should not raise the diagnostic\")]\n+\n+All of the preceding conditions are complicated a C# rule that allows multiple declarations as part of one statement. Consider the following test case string constant:\n+\n+[!code-csharp[multiple initializers](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#MultipleInitializers \"A declaration can be made constant only if all variables in that statement can be made constant\")]\n+\n+The variable `i` can be made constant, but the variable `j` cannot. Therefore, this statement cannot be made a const declaration. Add the `DataRow` declarations for all these tests:\n+\n+```csharp\n+[DataTestMethod]\n+[DataRow(\"\"),\n+    DataRow(VariableAssigned),\n+    DataRow(AlreadyConst),\n+    DataRow(NoInitializer),\n+    DataRow(InitializerNotConstant),\n+    DataRow(MultipleInitializers)]\n+public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)\n+```\n+\n+Run your tests again, and you'll see these new test cases fail.\n+\n+## Update your analyzer to ignore correct declarations\n+\n+You need some enhancements to your analyzer's `AnalyzeNode` method to filter out code that matches these conditions. They are all related conditions, so similar changes will fix all these conditions. Make the following changes to `AnalyzeNode`:\n+\n+* Your semantic analysis examined a single variable declaration. This code needs to be in a `foreach` loop that examines all the variables declared in the same statement.\n+* Each declared variable needs to have an initializer.\n+* Each declared variable's initializer must be a compile-time constant.\n+\n+In your `AnalyzeNode` method, replace the original semantic analysis:\n+\n+```csharp\n+// Perform data flow analysis on the local declaration.\n+var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);\n+\n+// Retrieve the local symbol for each variable in the local declaration\n+// and ensure that it is not written outside of the data flow analysis region.\n+var variable = localDeclaration.Declaration.Variables.Single();\n+var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);\n+if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))\n+{\n+    return;\n+}\n+```\n+\n+with the following code snippet:\n+\n+```csharp\n+// Ensure that all variables in the local declaration have initializers that\n+// are assigned with constant values.\n+foreach (var variable in localDeclaration.Declaration.Variables)\n+{\n+    var initializer = variable.Initializer;\n+    if (initializer == null)\n+    {\n+        return;\n+    }\n+\n+    var constantValue = context.SemanticModel.GetConstantValue(initializer.Value);\n+    if (!constantValue.HasValue)\n+    {\n+        return;\n+    }\n+}\n+\n+// Perform data flow analysis on the local declaration.\n+var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);\n+\n+foreach (var variable in localDeclaration.Declaration.Variables)\n+{\n+    // Retrieve the local symbol for each variable in the local declaration\n+    // and ensure that it is not written outside of the data flow analysis region.\n+    var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);\n+    if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))\n+    {\n+        return;\n+    }\n+}\n+```\n+\n+The first `foreach` loop examines each variable declaration using syntactic analysis. The first check guarantees that the variable has an initializer. The second check guarantees that the initializer is a constant. The second loop has the original semantic analysis. The semantic checks are in a separate loop because it has a greater impact on performance. Run your tests again, and you should see them all pass.\n+\n+## Add the final polish\n+\n+You're almost done. There are a few more conditions for your analyzer to handle. Visual Studio calls analyzers while the user is writing code. It's often the case that your analyzer will be called for code that doesn't compile. The diagnostic analyzer's `AnalyzeNode` method does not check to see if the constant value is convertible to the variable type. So, the current implementation will happily convert an incorrect declaration such as int i = \"abc\"' to a local constant. Add a source string constant for that condition:\n+\n+[!code-csharp[Mismatched types don't raise diagnostics](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#DeclarationIsInvalid \"When the variable type and the constant type don't match, there's no diagnostic\")]\n+\n+In addition, reference types are not handled properly. The only constant value allowed for a reference type is `null`, except in this case of <xref:System.String?displayPropert=nameWIthType>, which allows string literals. In other words, `const string s = \"abc\"` is legal, but `const object s = \"abc\"` is not. This code snippet verifies that condition:\n+\n+[!code-csharp[Reference types don't raise diagnostics](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#DeclarationIsntString \"When the variable type is a reference type other than string, there's no diagnostic\")]\n+\n+To be thorough, you need to add another test to make sure that you can create a constant declaration for a string. The following snippet defines both the code that raises the diagnostic, and the code after the fix has been applied:\n+\n+[!code-csharp[string reference types raise diagnostics](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#ConstantIsString \"When the variable type is string, it can be constant\")]\n+\n+Finally, if a variable is declared with the `var` keyword, the code fix does the wrong thing and generates a `const var` declaration, which is not supported by the C# language. To fix this bug, the code fix must replace the `var` keyword with the inferred type's name:\n+\n+[!code-csharp[var references need to use the inferred types](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#VarDeclarations \"Declarations made using var must have the type replaced with the inferred type\")]\n+\n+These changes update the data row declarations for both tests. The following code shows these tests with all data row attributes:\n+\n+[!code-csharp[The finished tests](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst.Test/MakeConstUnitTests.cs#FinishedTests \"The finished tests for the make const analyzer\")]\n+\n+Fortunately, all of the above bugs can be addressed using the same techniques that you just learned.\n+\n+To fix the first bug, first open **DiagnosticAnalyzer.cs** and locate the foreach loop where each of the local declaration's initializers are checked to ensure that they're assigned with constant values. Immediately _before_ the first foreach loop, call `context.SemanicModel.GetTypeInfo()` to retrieve detailed information about the declared type of the local declaration:\n+\n+```csharp\n+var variableTypeName = localDeclaration.Declaration.Type;\n+var variableType = context.SemanticModel.GetTypeInfo(variableTypeName).ConvertedType;\n+```\n+\n+Then, inside your `foreach` loop, check each initializer to make sure it's convertible to the variable type. Add the following check after ensuring that the initializer is a constant:\n+\n+```csharp\n+// Ensure that the initializer value can be converted to the type of the\n+// local declaration without a user-defined conversion.\n+var conversion = context.SemanticModel.ClassifyConversion(initializer.Value, variableType);\n+if (!conversion.Exists || conversion.IsUserDefined)\n+{\n+    return;\n+}\n+```\n+\n+The next change builds upon the last one. Before the closing curly brace of the first foreach loop, add the following code to check the type of the local declaration when the constant is a string or null.\n+\n+```csharp\n+// Special cases:\n+//  * If the constant value is a string, the type of the local declaration\n+//    must be System.String.\n+//  * If the constant value is null, the type of the local declaration must\n+//    be a reference type.\n+if (constantValue.Value is string)\n+{\n+    if (variableType.SpecialType != SpecialType.System_String)\n+    {\n+        return;\n+    }\n+}\n+else if (variableType.IsReferenceType && constantValue.Value != null)\n+{\n+    return;\n+}\n+```\n+\n+You must write a bit more code in your code fix provider to replace the var' keyword with the correct type name. Return to **CodeFixProvider.cs**. The code you'll add does the following steps:\n+\n+* Check if the declaration is a `var` declaration, and if it is:\n+* Create a new type for the inferred type.\n+* Make sure the type declaration is not an alias. If so, it is legal to declare `const var`.\n+* Make sure that `var` isn't a type name in this program. (If so, `const var` is legal).\n+* Simplify the full type name\n+\n+That sounds like a lot of code. It's not. Add the following code immediately after the initialization of `newModifiers`:\n+\n+[!code-csharp[Replace Var designations](~/samples/csharp/roslyn-sdk/Tutorials/MakeConst/MakeConst/MakeConstCodeFixProvider.cs#ReplaceVar \"Replace a var designation with the explicit type\")]","path":"docs/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix.md","position":491,"original_position":491,"commit_id":"91a352274d09acdf33c2c56000cff68d334171e8","original_commit_id":"91a352274d09acdf33c2c56000cff68d334171e8","user":{"login":"Thraka","id":2672110,"node_id":"MDQ6VXNlcjI2NzIxMTA=","avatar_url":"https://avatars0.githubusercontent.com/u/2672110?v=4","gravatar_id":"","url":"https://api.github.com/users/Thraka","html_url":"https://github.com/Thraka","followers_url":"https://api.github.com/users/Thraka/followers","following_url":"https://api.github.com/users/Thraka/following{/other_user}","gists_url":"https://api.github.com/users/Thraka/gists{/gist_id}","starred_url":"https://api.github.com/users/Thraka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Thraka/subscriptions","organizations_url":"https://api.github.com/users/Thraka/orgs","repos_url":"https://api.github.com/users/Thraka/repos","events_url":"https://api.github.com/users/Thraka/events{/privacy}","received_events_url":"https://api.github.com/users/Thraka/received_events","type":"User","site_admin":false},"body":"I needed to import the following namespace\r\n\r\n```csharp\r\nusing Microsoft.CodeAnalysis.Simplification;\r\n```","created_at":"2018-09-11T22:31:39Z","updated_at":"2018-09-11T22:33:37Z","html_url":"https://github.com/dotnet/docs/pull/6824#discussion_r216844978","pull_request_url":"https://api.github.com/repos/dotnet/docs/pulls/6824","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/dotnet/docs/pulls/comments/216844978"},"html":{"href":"https://github.com/dotnet/docs/pull/6824#discussion_r216844978"},"pull_request":{"href":"https://api.github.com/repos/dotnet/docs/pulls/6824"}}},"pull_request":{"url":"https://api.github.com/repos/dotnet/docs/pulls/6824","id":206147247,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA2MTQ3MjQ3","html_url":"https://github.com/dotnet/docs/pull/6824","diff_url":"https://github.com/dotnet/docs/pull/6824.diff","patch_url":"https://github.com/dotnet/docs/pull/6824.patch","issue_url":"https://api.github.com/repos/dotnet/docs/issues/6824","number":6824,"state":"open","locked":false,"title":"Get started analyzer code fix","user":{"login":"BillWagner","id":493969,"node_id":"MDQ6VXNlcjQ5Mzk2OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/493969?v=4","gravatar_id":"","url":"https://api.github.com/users/BillWagner","html_url":"https://github.com/BillWagner","followers_url":"https://api.github.com/users/BillWagner/followers","following_url":"https://api.github.com/users/BillWagner/following{/other_user}","gists_url":"https://api.github.com/users/BillWagner/gists{/gist_id}","starred_url":"https://api.github.com/users/BillWagner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BillWagner/subscriptions","organizations_url":"https://api.github.com/users/BillWagner/orgs","repos_url":"https://api.github.com/users/BillWagner/repos","events_url":"https://api.github.com/users/BillWagner/events{/privacy}","received_events_url":"https://api.github.com/users/BillWagner/received_events","type":"User","site_admin":false},"body":"Fixes #4836 \r\nFixes #6994 \r\n\r\nRelies on dotnet/samples#220\r\n\r\n@Thraka Do you want to look at this? It might be a fun way to learn the Roslyn APIs, if you are interested.\r\n\r\n[Internal review link](https://review.docs.microsoft.com/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix?branch=pr-en-us-6824)","created_at":"2018-08-03T21:53:17Z","updated_at":"2018-09-11T22:33:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"dbb074cac4bb95fb8895f152ebce708a26ff119f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/dotnet/docs/pulls/6824/commits","review_comments_url":"https://api.github.com/repos/dotnet/docs/pulls/6824/comments","review_comment_url":"https://api.github.com/repos/dotnet/docs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dotnet/docs/issues/6824/comments","statuses_url":"https://api.github.com/repos/dotnet/docs/statuses/91a352274d09acdf33c2c56000cff68d334171e8","head":{"label":"BillWagner:get-started-analyzer-code-fix","ref":"get-started-analyzer-code-fix","sha":"91a352274d09acdf33c2c56000cff68d334171e8","user":{"login":"BillWagner","id":493969,"node_id":"MDQ6VXNlcjQ5Mzk2OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/493969?v=4","gravatar_id":"","url":"https://api.github.com/users/BillWagner","html_url":"https://github.com/BillWagner","followers_url":"https://api.github.com/users/BillWagner/followers","following_url":"https://api.github.com/users/BillWagner/following{/other_user}","gists_url":"https://api.github.com/users/BillWagner/gists{/gist_id}","starred_url":"https://api.github.com/users/BillWagner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BillWagner/subscriptions","organizations_url":"https://api.github.com/users/BillWagner/orgs","repos_url":"https://api.github.com/users/BillWagner/repos","events_url":"https://api.github.com/users/BillWagner/events{/privacy}","received_events_url":"https://api.github.com/users/BillWagner/received_events","type":"User","site_admin":false},"repo":{"id":127573416,"node_id":"MDEwOlJlcG9zaXRvcnkxMjc1NzM0MTY=","name":"docs","full_name":"BillWagner/docs","owner":{"login":"BillWagner","id":493969,"node_id":"MDQ6VXNlcjQ5Mzk2OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/493969?v=4","gravatar_id":"","url":"https://api.github.com/users/BillWagner","html_url":"https://github.com/BillWagner","followers_url":"https://api.github.com/users/BillWagner/followers","following_url":"https://api.github.com/users/BillWagner/following{/other_user}","gists_url":"https://api.github.com/users/BillWagner/gists{/gist_id}","starred_url":"https://api.github.com/users/BillWagner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BillWagner/subscriptions","organizations_url":"https://api.github.com/users/BillWagner/orgs","repos_url":"https://api.github.com/users/BillWagner/repos","events_url":"https://api.github.com/users/BillWagner/events{/privacy}","received_events_url":"https://api.github.com/users/BillWagner/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/BillWagner/docs","description":"This repository contains .NET Documentation.","fork":true,"url":"https://api.github.com/repos/BillWagner/docs","forks_url":"https://api.github.com/repos/BillWagner/docs/forks","keys_url":"https://api.github.com/repos/BillWagner/docs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BillWagner/docs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BillWagner/docs/teams","hooks_url":"https://api.github.com/repos/BillWagner/docs/hooks","issue_events_url":"https://api.github.com/repos/BillWagner/docs/issues/events{/number}","events_url":"https://api.github.com/repos/BillWagner/docs/events","assignees_url":"https://api.github.com/repos/BillWagner/docs/assignees{/user}","branches_url":"https://api.github.com/repos/BillWagner/docs/branches{/branch}","tags_url":"https://api.github.com/repos/BillWagner/docs/tags","blobs_url":"https://api.github.com/repos/BillWagner/docs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BillWagner/docs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BillWagner/docs/git/refs{/sha}","trees_url":"https://api.github.com/repos/BillWagner/docs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BillWagner/docs/statuses/{sha}","languages_url":"https://api.github.com/repos/BillWagner/docs/languages","stargazers_url":"https://api.github.com/repos/BillWagner/docs/stargazers","contributors_url":"https://api.github.com/repos/BillWagner/docs/contributors","subscribers_url":"https://api.github.com/repos/BillWagner/docs/subscribers","subscription_url":"https://api.github.com/repos/BillWagner/docs/subscription","commits_url":"https://api.github.com/repos/BillWagner/docs/commits{/sha}","git_commits_url":"https://api.github.com/repos/BillWagner/docs/git/commits{/sha}","comments_url":"https://api.github.com/repos/BillWagner/docs/comments{/number}","issue_comment_url":"https://api.github.com/repos/BillWagner/docs/issues/comments{/number}","contents_url":"https://api.github.com/repos/BillWagner/docs/contents/{+path}","compare_url":"https://api.github.com/repos/BillWagner/docs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BillWagner/docs/merges","archive_url":"https://api.github.com/repos/BillWagner/docs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BillWagner/docs/downloads","issues_url":"https://api.github.com/repos/BillWagner/docs/issues{/number}","pulls_url":"https://api.github.com/repos/BillWagner/docs/pulls{/number}","milestones_url":"https://api.github.com/repos/BillWagner/docs/milestones{/number}","notifications_url":"https://api.github.com/repos/BillWagner/docs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BillWagner/docs/labels{/name}","releases_url":"https://api.github.com/repos/BillWagner/docs/releases{/id}","deployments_url":"https://api.github.com/repos/BillWagner/docs/deployments","created_at":"2018-03-31T21:38:54Z","updated_at":"2018-09-07T18:38:31Z","pushed_at":"2018-09-07T18:38:20Z","git_url":"git://github.com/BillWagner/docs.git","ssh_url":"git@github.com:BillWagner/docs.git","clone_url":"https://github.com/BillWagner/docs.git","svn_url":"https://github.com/BillWagner/docs","homepage":"https://docs.microsoft.com/dotnet","size":280191,"stargazers_count":0,"watchers_count":0,"language":"PowerShell","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"dotnet:master","ref":"master","sha":"f656b8b26e85dd33fefa2fc90b117d5aa4c62148","user":{"login":"dotnet","id":9141961,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNDE5NjE=","avatar_url":"https://avatars0.githubusercontent.com/u/9141961?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"repo":{"id":35890081,"node_id":"MDEwOlJlcG9zaXRvcnkzNTg5MDA4MQ==","name":"docs","full_name":"dotnet/docs","owner":{"login":"dotnet","id":9141961,"node_id":"MDEyOk9yZ2FuaXphdGlvbjkxNDE5NjE=","avatar_url":"https://avatars0.githubusercontent.com/u/9141961?v=4","gravatar_id":"","url":"https://api.github.com/users/dotnet","html_url":"https://github.com/dotnet","followers_url":"https://api.github.com/users/dotnet/followers","following_url":"https://api.github.com/users/dotnet/following{/other_user}","gists_url":"https://api.github.com/users/dotnet/gists{/gist_id}","starred_url":"https://api.github.com/users/dotnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dotnet/subscriptions","organizations_url":"https://api.github.com/users/dotnet/orgs","repos_url":"https://api.github.com/users/dotnet/repos","events_url":"https://api.github.com/users/dotnet/events{/privacy}","received_events_url":"https://api.github.com/users/dotnet/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dotnet/docs","description":"This repository contains .NET Documentation.","fork":false,"url":"https://api.github.com/repos/dotnet/docs","forks_url":"https://api.github.com/repos/dotnet/docs/forks","keys_url":"https://api.github.com/repos/dotnet/docs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dotnet/docs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dotnet/docs/teams","hooks_url":"https://api.github.com/repos/dotnet/docs/hooks","issue_events_url":"https://api.github.com/repos/dotnet/docs/issues/events{/number}","events_url":"https://api.github.com/repos/dotnet/docs/events","assignees_url":"https://api.github.com/repos/dotnet/docs/assignees{/user}","branches_url":"https://api.github.com/repos/dotnet/docs/branches{/branch}","tags_url":"https://api.github.com/repos/dotnet/docs/tags","blobs_url":"https://api.github.com/repos/dotnet/docs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dotnet/docs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dotnet/docs/git/refs{/sha}","trees_url":"https://api.github.com/repos/dotnet/docs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dotnet/docs/statuses/{sha}","languages_url":"https://api.github.com/repos/dotnet/docs/languages","stargazers_url":"https://api.github.com/repos/dotnet/docs/stargazers","contributors_url":"https://api.github.com/repos/dotnet/docs/contributors","subscribers_url":"https://api.github.com/repos/dotnet/docs/subscribers","subscription_url":"https://api.github.com/repos/dotnet/docs/subscription","commits_url":"https://api.github.com/repos/dotnet/docs/commits{/sha}","git_commits_url":"https://api.github.com/repos/dotnet/docs/git/commits{/sha}","comments_url":"https://api.github.com/repos/dotnet/docs/comments{/number}","issue_comment_url":"https://api.github.com/repos/dotnet/docs/issues/comments{/number}","contents_url":"https://api.github.com/repos/dotnet/docs/contents/{+path}","compare_url":"https://api.github.com/repos/dotnet/docs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dotnet/docs/merges","archive_url":"https://api.github.com/repos/dotnet/docs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dotnet/docs/downloads","issues_url":"https://api.github.com/repos/dotnet/docs/issues{/number}","pulls_url":"https://api.github.com/repos/dotnet/docs/pulls{/number}","milestones_url":"https://api.github.com/repos/dotnet/docs/milestones{/number}","notifications_url":"https://api.github.com/repos/dotnet/docs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dotnet/docs/labels{/name}","releases_url":"https://api.github.com/repos/dotnet/docs/releases{/id}","deployments_url":"https://api.github.com/repos/dotnet/docs/deployments","created_at":"2015-05-19T15:13:32Z","updated_at":"2018-09-11T22:02:56Z","pushed_at":"2018-09-11T22:00:50Z","git_url":"git://github.com/dotnet/docs.git","ssh_url":"git@github.com:dotnet/docs.git","clone_url":"https://github.com/dotnet/docs.git","svn_url":"https://github.com/dotnet/docs","homepage":"https://docs.microsoft.com/dotnet","size":537979,"stargazers_count":1299,"watchers_count":1299,"language":"PowerShell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2680,"mirror_url":null,"archived":false,"open_issues_count":998,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":2680,"open_issues":998,"watchers":1299,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dotnet/docs/pulls/6824"},"html":{"href":"https://github.com/dotnet/docs/pull/6824"},"issue":{"href":"https://api.github.com/repos/dotnet/docs/issues/6824"},"comments":{"href":"https://api.github.com/repos/dotnet/docs/issues/6824/comments"},"review_comments":{"href":"https://api.github.com/repos/dotnet/docs/pulls/6824/comments"},"review_comment":{"href":"https://api.github.com/repos/dotnet/docs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dotnet/docs/pulls/6824/commits"},"statuses":{"href":"https://api.github.com/repos/dotnet/docs/statuses/91a352274d09acdf33c2c56000cff68d334171e8"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"Thraka"}}