How to do it...

  1. Expand the PurchaseOrderAnalyzer (Portable) project and open the DiagnosticAnalyzer.cs file.
  1. As seen earlier, you will see your diagnostic analyzer class. It should read public class PurchaseOrderAnalyzerAnalyzer : DiagnosticAnalyzer. Add the following code to the top of this class, replacing the code for the DiagnosticId, Title, MessageFormat, Description, Category, and Rule variables. Note that I have added two enumerators called ClassTypesToCheck and MandatoryInterfaces to the class. I only want this analyzer to act on a class if it is called PurchaseOrder or SalesOrder. I also only want the IReceiptable interface to be mandatory on the classes defined in the ClassTypesToCheck enum. 
        public const string DiagnosticId = "PurchaseOrderAnalyzer";

public enum ClassTypesToCheck { PurchaseOrder, SalesOrder }
public enum MandatoryInterfaces { IReceiptable }

private static readonly LocalizableString Title =
"Interface Implementation Available";
private static readonly LocalizableString
MessageFormat = "IReceiptable Interface not Implemented";
private static readonly LocalizableString Description =
"You need to implement the IReceiptable interface";
private const string Category = "Naming";

private static DiagnosticDescriptor Rule = new
DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Warning,
isEnabledByDefault: true, description: Description);
  1. Make sure that the Initialize method contains the following code: 
        public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeSymbol,
SymbolKind.NamedType);
}
  1. Create the AnalyzeSymbol method. You can call this method anything you like. Just ensure that, whatever you call this method, it matches the method name in the RegisterSymbolAction() method inside Initialize.
        private static void AnalyzeSymbol(SymbolAnalysisContext context)
{

}
  1. Add to that a Boolean called blnInterfaceImplemented that will store a true or false if the interface is implemented or not. The next check we do is to ignore abstract classes. In reality, you would probably want to check an abstract class too, but I want to exclude it to show the flexibility of code analyzers.
        bool blnInterfaceImplemented = false;
if (!context.Symbol.IsAbstract)
{

}
  1. You now need to get the name of the symbol you are checking. To do this, create an object called namedTypeSymbol on which you can call the Name method to return the symbol name. On a class called PurchaseOrder, this should return PurchaseOrder as the name. Return the ClassTypesToCheck enum as a List<string> object called classesToCheck. Then, do a check on the class name and see if it is contained in the classesToCheck list. It is important to ignore the case by adding StringComparison.OrdinalIgnoreCase to the Equals check. This will ensure that the analyzer will analyze classes called purchaseorder, PURCHASEORDER, PurchaseOrder, Purchaseorder, or purchaseOrder. Add the code inside the if condition excluding abstract classes.
        var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
List<string> classesToCheck = Enum.GetNames(
typeof(ClassTypesToCheck)).ToList();

if (classesToCheck.Any(s => s.Equals(
namedTypeSymbol.Name, StringComparison.OrdinalIgnoreCase)))
{

}
The capitalization style recommended for class names is PascalCase. PascalCase consists of capitalizing the first letter of an identifier and each subsequent concatenated word. This is applied if the identifier has three or more characters. This means that the concatenated words purchase and order must we written in PascalCase when used in class names. This results in PurchaseOrder. Refer to the Capitalization Styles article in the MSDN at https://msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx.
  1. Inside the if condition, to check if the class name is PurchaseOrder or SalesOrder, add the following code. Here we are going to check the interfaces defined on the matched PurchaseOrder or SalesOrder class. We do this by calling the AllInterfaces() method and checking to see if it matches the nameof the IReceiptable enumerator. In reality, we would probably want to check more than one interface, but for our purposes we're only checking for the implementation of the IReceiptable interface. If we find the interface as implemented on the class name that was matched in the earlier check, we set blnInterfaceImplemented = true; (it is currently initialized to false). This means that, if the interface is not matched, then we will produce a diagnostic for the omission of the IReceiptable interface. This is done by creating and reporting the diagnostic that contains the Rule defined earlier and the location of the class name.
        string interfaceName = nameof(
MandatoryInterfaces.IReceiptable);

if (namedTypeSymbol.AllInterfaces.Any(s => s.Name.Equals(
interfaceName, StringComparison.OrdinalIgnoreCase)))
{
blnInterfaceImplemented = true;
}

if (!blnInterfaceImplemented)
{
// Produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule,
namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
  1. If all the code is added to AnalyzeSymbol() the method should look as follows:
        private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
bool blnInterfaceImplemented = false;
if (!context.Symbol.IsAbstract)
{
var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
List<string> classesToCheck = Enum.GetNames(
typeof(ClassTypesToCheck)).ToList();

if (classesToCheck.Any(s => s.Equals(namedTypeSymbol.Name,
StringComparison.OrdinalIgnoreCase)))
{
string interfaceName = nameof(
MandatoryInterfaces.IReceiptable);

if (namedTypeSymbol.AllInterfaces.Any(s => s.Name.Equals(
interfaceName, StringComparison.OrdinalIgnoreCase)))
{
blnInterfaceImplemented = true;
}

if (!blnInterfaceImplemented)
{
// Produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule,
namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
}
}
  1. We now need to create a fix for the code analyzer. If we see that the class does not implement our interface, we want to provide a quick fix for the developer with the lightbulb feature. Open the file called CodeFixProvider.cs. You will see that it contains a class called public class PurchaseOrderAnalyzerCodeFixProvider : CodeFixProvider. The first thing to do is locate the title string constant and change it to a more suitable title. This is the menu flyout displayed when you click on the lightbulb in Visual Studio.
        private const string title = "Implement IReceiptable";
  1. I have left most of the code-fix code the same except for the code that does the actual fix. Locate the method called RegisterCodeFixesAsync(). I renamed the method to call in the RegisterCodeFix() method to ImplementRequiredInterfaceAsync(). The code should look as follows:
        public sealed override async Task RegisterCodeFixesAsync(
CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(
context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

// Find the type declaration identified by the diagnostic.
var declaration = root.FindToken(diagnosticSpan.Start)
.Parent.AncestorsAndSelf().OfType
<TypeDeclarationSyntax>().First();

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedSolution: c =>
ImplementRequiredInterfaceAsync(context.Document,
declaration, c),
equivalenceKey: title),
diagnostic);
}
  1. You will notice that I have re-purposed the fix used to make the symbol an uppercase to implement the interface. The rest of the code is left as-is. In reality, you would most likely want to check if any other interfaces are implemented on the class and maintain those implementations. For this demonstration, we're just assuming a new class being created called PurchaseOrder or SalesOrder without existing interfaces.
        private async Task<Solution> ImplementRequiredInterfaceAsync(
Document document, TypeDeclarationSyntax typeDecl,
CancellationToken cancellationToken)
{
// Get the text of the PurchaseOrder class and return one
implementing the IPurchaseOrder interface
var identifierToken = typeDecl.Identifier;

var newName = $"{identifierToken.Text} : IReceiptable";

// Get the symbol representing the type to be renamed.
var semanticModel = await document.GetSemanticModelAsync(
cancellationToken);
var typeSymbol = semanticModel.GetDeclaredSymbol(
typeDecl, cancellationToken);

// Produce a new solution that has all references to
that type renamed, including the declaration.
var originalSolution = document.Project.Solution;
var optionSet = originalSolution.Workspace.Options;
var newSolution = await Renamer.RenameSymbolAsync(
document.Project.Solution, typeSymbol, newName,
optionSet, cancellationToken).ConfigureAwait(false);

return newSolution;
}
  1. Ensure that the PurchaseOrderAnalyzer.Vsix project is set as the start-up project and click on Debug. A new instance of Visual Studio will be launched. Create a new console application in this Visual Studio instance and call it PurchaseOrderConsole. To this project, add a new interface called IReceiptable and add the following code.
        interface IReceiptable
{
void MarkAsReceipted(int orderNumber);
}
  1. Now, add a new class called PurchaseOrder to the project with the following code.
        public class PurchaseOrder 
{

}
  1. After you have done this, your project might look as follows if you added separate files for IReceiptable and PurchaseOrder.
  1. Viewing the PurchaseOrder class, you will notice a squiggly line under the class name PurchaseOrder.
  1. Hovering the mouse over the squiggly line, you will see the lightbulb displayed notifying you that the IReceiptable interface is not implemented.
  1. When you view potential fixes, you will see that the title we changed earlier in the CodeFixProvider.cs file to read private const string title = "Implement IReceiptable"; is displayed as the flyout menu text. The suggested code is then shown as implementing the correct interface IReceiptable.
  1. Clicking on this modifies our PurchaseOrder class to produce the following code:
        public class PurchaseOrder : IReceiptable 
{

}
  1. Once the code fix has been applied, you will see that the squiggly line under the class name has disappeared. As expected, Visual Studio is now telling us that we need to implement the interface member IReceiptable.MarkAsReceipted(int) by underlining the interface name IReceiptable with a red squiggly line.
  1. Hovering over the IReceiptable interface name, you will see the lightbulb  the code fix. This is the standard Visual Studio analyzer at work here.
  1. Clicking on the fix to be applied implements the IReceiptable member and the PurchaseOrder class is correctly defined in the code.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.187