/AgodaAnalyzers

A set of opinionated Roslyn analyzers for C#

Primary LanguageC#Apache License 2.0Apache-2.0

alt text

This project is used by Agoda internally for analysis of our C# projects. We have opened it for community contribution.

Dive straight in?

Take a look at the analyzer for our test method names which is fairly simple but not trivial.

Anatomy of a Roslyn analyzer

An analyzer inherits from the abstract class DiagnosticAnalyzer. It requires us to override two members:

A property specifying one or more DiagnosticDescriptors representing the metadata of your analyzer, for instance the title, error message, severity, etc.

Called by the hosting environment (eg. Visual Studio) to bootstrap your analyzer. In this method, we tell the Roslyn compiler:

  • which SyntaxKinds we are interested in analyzing (eg. MethodDeclaration, ClassDeclaration, Parameter)
  • for each of these SyntaxKinds, what method should be called to perform the actual analysis.

Here is an example:

public override void Initialize(AnalysisContext context)
{
    // Each time the compiler encounters a method declaration, call the AnalyzeNode method. 
    context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration);
}

The AnalyzeNode method

This is where the fun begins. Each time the Roslyn compiler encounters a node with a registered SyntaxKind it will call our AnalyzeNode method with the context. For example:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
    // Upcast the node to the type that corresponds to the specific SyntaxKind we registered: 
    var methodDeclarationSyntax = (MethodDeclarationSyntax) context.Node;
    
    // We can now do syntactic ("textual") analysis, such as checking if the method has a public modifier:
    if (methodDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword))
    {
        // ...
    }
    
    // We can also do semantic analysis, which gives us far deeper inspection opportunities than just 
    // looking at the syntax.
    var methodDeclarationSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax);
    
    // For example, we can check if it's an extension method: 
    if (methodDeclarationSymbol.IsExtensionMethod)
    {
        // ...
    }
    
    // Or we can get its attributes:
    var methodAttributes = methodDeclarationSymbol.GetAttributes();
    
    // Or anything else that the compiler can possibly know.

    // If we find a problem, we report it like this. The Descriptor here refers to one of the descriptors
    // we passed to the SupportedDiagnostics property above.
    if (weFoundAnError) 
    {
        context.ReportDiagnostic(Diagnostic.Create(Descriptor, methodDeclaration.GetLocation()));
    }
}

Debugging

In order to debug this project:

  • Run the Agoda.Analyzers.Vsix as the start up project. It will open and attach to a second copy of VS2017.
  • Set a breakpoint in your analyzer in the first VS.
  • Edit some code in the second VS that should cause your analyzer to fire. The breakpoint in the first VS should be hit and you can now step through.

Deploying

Merged rules will be automatically added to our internal SonarQube instance. They must be activated manually, however.

To generate a jar file from this project for use with SonarQube we have prepared a fork of the Sonar team's project that has been updated to 1.3 here.

Contributing

Please read the contributing guidelines