I need to analyze some extension method. For example Enumerable.ToList
.
Code sample to analyze:
var test = @"
using System.Linq;
namespace Test
{
public class TestType
{
void TestMethod()
{
var empty = new[] {0};
var test = empty.ToList();
}
}
}";
Diagnostic:
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeSymbol, SyntaxKind.InvocationExpression);
}
private static void AnalyzeSymbol(SyntaxNodeAnalysisContext context)
{
var symbolInfo = context.SemanticModel.GetSymbolInfo(context.Node);
}
However symbolInfo.Symbol
is null and there are no any candidates. If I change the code sample like that:
var test = @"
using System.Linq;
namespace Test
{
public class TestType
{
void TestMethod()
{
var empty = new[] {0};
var test = Enumerable.ToList(empty);
}
}
}";
then symbolInfo
has a candidate but still doesn't have Symbol. How to get a symbol info of extension method invocation?
Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type.
What is extension method? Extension methods in C# are methods applied to some existing class and they look like regular instance methods. This way we can "extend" existing classes we cannot change. Perhaps the best example of extension methods are HtmlHelper extensions used in ASP.NET MVC.
In C#, the extension method concept allows you to add new methods in the existing class or in the structure without modifying the source code of the original type and you do not require any kind of special permission from the original type and there is no need to re-compile the original type.
According to Microsoft, In fact, extension methods cannot access private variables in the type they are extending.
If you are using the default unit tests helper class that is automatically created from an 'Analyzer with Code Fix' project template, then you should be aware of the following.
The GetSortedDiagnosticsFromDocuments
method tries to run the analyzers even if there are compilation errors when processing the code you provide as input. Of course, when there are compilation errors, the semantic model might be incomplete or missing.
You can change this method in this way:
// old version: no compilation errors detection
var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer));
// new version: detect the compilation errors
var compilation = project.GetCompilationAsync().Result;
var compilerErrors = compilation.GetDiagnostics().Where(i => i.Severity == DiagnosticSeverity.Error);
if (compilerErrors.Any())
{
return compilerErrors.ToArray();
}
var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer));
If you then try to run the unit tests with this code and with your input string, you will notice at least 2 compilation errors:
error CS0012: The type 'List<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Collections, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
(the version number might be different)
and
error CS5001: Program does not contain a static 'Main' method suitable for an entry point
You need to solve these errors to get your code compiled, so that you can access a valid semantic model.
void TestMethod()
to a static void Main()
. This is not really necessary, but it's better to have a valid code without compiler errors.System.Collections
and System.Runtime
to your dynamically generated project. There is no convenient way to do this in unit tests when you use the helper code from the project template. But to test it, you could change the CreateProject
method. By default, 4 additional references are configured. Add the missing references:
var collectionsReference = MetadataReference.CreateFromFile(typeof(Stack<>).Assembly.Location);
var runtimeReference = MetadataReference.CreateFromFile(typeof(ISet<>).Assembly.Location);
var solution = new AdhocWorkspace()
.CurrentSolution
.AddProject(projectId, TestProjectName, TestProjectName, language)
.AddMetadataReference() //...
// ...extend the AddMetadataReference chain
.AddMetadataReference(projectId, collectionsReference)
.AddMetadataReference(projectId, runtimeReference);
Then, you should be able to compile the code in your unit tests and to get a valid semantic model.
You might be willing to implement that dynamic references functionality e.g. by exposing the project object being created on-the-fly to the unit test caller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With