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
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...
It's rather embarrassing to have found this so quickly after posting, but the solution seems to be to refer to the Identifier
which is a descendant of the SimpleBaseTypeSyntax
.
var interfaceSymbolInfo =
semanticModel.GetSymbolInfo(interfaceBaseTypeSyntax.DescendantNodes().First());
And, by calling:
var interfaceTypeInfo =
semanticModel.GetTypeInfo(interfaceBaseTypeSyntax.DescendantNodes().First());
I can then use interfaceTypeInfo.Type.IsInterface
to verify I have indeed found an interface type, and also access interfaceTypeInfo.Type.GetMembers()
.
The answer was staring me in the face via the Syntax Visualizer.
I'm leaving this open for now, in case others have a better solution... thanks!