Roslyn 2.x CodeFix that implements a missing interface, delegated to a member, VS 2017

Viewed 358

BACKGROUND

I am seeking to create a Roslyn CodeFix that will respond to a Diagnostic warning from the built in Analyzer shipped with Visual Studio, that will identify the interface that is not - or is partially - implemented, allowing me to loop through the missing members, and generate custom code that delegates a method call to a Member field of a type that implements the interface.

(The Roslyn Analysers and CodeFixes that ship with Visual Studio do offer this functionality, but I need to customise and extend the generation of code, which is not possible as the Microsoft implementations are all marked as internal.)

Please note: The interface is almost always going to be located in an external, third-party assembly, to which I do not have access to the source.

e.g. starting from:

public class CustomDocumentDBClient : IDocumentClient
{
}

The desired outcome would be similar to the following (in practice I will be creating multiple versions that add additional code to wrap the method calls, once the basic principals are working):

public class CustomDocumentDBClient : IDocumentClient
{
    // Field to which calls are delegated, initialised by the constructor
    private readonly IDocumentClient _documentClientImplementation;

    // Constructor
    public CustomDocumentDBClient (IDocumentClient documentClientImplementation)
    {
        _documentClientImplementation = documentClientImplementation;
    }

    // Interface method that delegates to private field for invocation 
    public Task<ResourceResponse<Attachment>> CreateAttachmentAsync(string documentLink, object attachment, RequestOptions options = null)
    {
        return _documentClientImplementation.CreateAttachmentAsync(documentLink, attachment, options);
    }

    // Interface method that delegates to private field for invocation    
    public Task<ResourceResponse<Attachment>> CreateAttachmentAsync(string documentLink, Stream mediaStream, MediaOptions options = null, RequestOptions requestOptions = null)
    {
        return _documentClientImplementation.CreateAttachmentAsync(documentLink, mediaStream, options, requestOptions);
    }
    ...
    other methods
    ...
}

WHAT I HAVE TRIED

I have spent some time reading tutorials regarding the Syntax Tree and Semantic Model functionality of Roslyn.

I have also examined the Roslyn source code from GitHub - which does include the exact feature that I wish to implement; however, the code is heavily interwoven throughout various complex classes, and is implemented as internal methods, which cannot be overridden or extended, or indeed extracted into a standalone project.

From investigating multiple samples, and also a related SO question How to see if a class has implemented the interface with Roslyn I have concluded that I must use the Roslyn Semantic Model to obtain information about the interface, and it's declared members.

Once I can obtain the interface Members, I am able to build the required output code using the SyntaxFactory, and I have used the 'Roslyn Quoter' for guidance.

Creating a CodeFix from the default template, that responds to the correct Diagnostic codes is straightforward, and this is functioning.

ISSUES

The problem I am having, is taking the token identified by the Diagnostics Location, which appears to be a SimpleBaseTypeSyntax token, and

  1. Verifying that it is actually representing an interface,
  2. Obtaining a Symbol definition that allows me to enumerate the Members of the third-party interface.

The Syntax Visualizer indicates that the interface declaration node is of type SimpleBaseType.

I am therefore using the following code to obtain the token from the Syntax Tree as SimpleBaseTypeSyntax-

    // Find the interface Syntax Token detected by the diagnostic.
    var interfaceBaseTypeSyntax =
        root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf()
            .OfType<SimpleBaseTypeSyntax>().First();

a) This does return a token that contains the information of the relevant node in the Syntax Tree - however, I cannot see any 'InterfaceTypeSyntax' type or IsInterface method to validate that it is actually an interface.

b) I believe I should be able to use semanticModel.GetSymbolInfo(interfaceBaseTypeSyntax), however this always returns null - bear in mind the interface is declared in an external assembly.

Is there something I need to do to make that information available through GetSymbolInfo, or is there another approach I should be taking...?

Many thanks for your advice...

2 Answers
Related