SymbolInfo of extension method

Viewed 499

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?

1 Answers

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.

  • Change your void TestMethod() to a static void Main(). This is not really necessary, but it's better to have a valid code without compiler errors.
  • Add a reference to 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.

Related