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
- Verifying that it is actually representing an interface,
- 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...