How to reference local assemblies in roslyn analyzer tests?
Asked Answered
G

3

7

I´m creating a roslyn analyzer to check the usage of an attribute from my framework code.
Example:

Framework.csproj

public class ModuleAttribute : Attribute { }

Framework.Analyzer.csproj

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class IsModuleAPublicClassAnalyzer : DiagnosticAnalyzer
{ ... }

Framework.Analyzer.Test.csproj

[Fact]
public async Task MyTestMethod()
{
    string test = @"
using Framework;

namespace MyNamespace;

[Module]
public class MyConcreteModule
{
}
";

    DiagnosticResult expected = VerifyCs
        .Diagnostic(AsyncPropertySetterAnalyzer.DiagnosticId)
        .WithLocation(line: 6, column: 0);

    await new CSharpAnalyzerTest<IsModuleAPublicClassAnalyzer, XUnitVerifier>
    {
        TestState =
        {
            Sources = { test  },
            ExpectedDiagnostics = { expected }
        }
    }
    .RunAsync();
}

How can I add a reference to Framework.dll in the test codesnippet? All projects are in the same solution.

Update 1

I noticed that it is possible to add additional MetadataReferences like this: Framework.Analyzer.Test.csproj

[Fact]
public async Task MyTestMethod()
{
    string test = @"
using Framework;

namespace MyNamespace;

[Module]
public class MyConcreteModule
{
}
";

    DiagnosticResult expected = VerifyCs
        .Diagnostic(AsyncPropertySetterAnalyzer.DiagnosticId)
        .WithLocation(line: 6, column: 0);

    await new CSharpAnalyzerTest<IsModuleAPublicClassAnalyzer, XUnitVerifier>
    {
        TestState =
        {
            Sources = { test  },
            ExpectedDiagnostics = { expected },
            AdditionalReferences =
            {
                MetadataReference.CreateFromFile(typeof(ModuleAttribute).Assembly.Location)
            }
        }
    }
    .RunAsync();
}

Now I get that error:

error CS1705: Assembly 'Framework' with identity 'Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=29fe1ef4929b04aa' uses 'System.Runtime, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' which has a higher version than referenced assembly 'System.Runtime' with identity 'System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

Framework.csproj and Framework.Analyzer.Test.cspoj have target framework net7.0
Framework.Analyzer.csproj is netstandard2.0

Guesthouse answered 24/10, 2022 at 9:4 Comment(0)
G
7

Solution

I solved it by adding ReferenceAssemblies like this:

[Fact]
public async Task MyTestMethod()
{
    string test = @"
using Framework;

namespace MyNamespace;

[Module]
public class MyConcreteModule
{
}
";

    DiagnosticResult expected = VerifyCs
        .Diagnostic(AsyncPropertySetterAnalyzer.DiagnosticId)
        .WithLocation(line: 6, column: 0);

    await new CSharpAnalyzerTest<IsModuleAPublicClassAnalyzer, XUnitVerifier>
    {
        TestState =
        {
            Sources = { test  },
            ExpectedDiagnostics = { expected },
            AdditionalReferences =
            {
                MetadataReference.CreateFromFile(typeof(ModuleAttribute).Assembly.Location)
            },
            // ReferenceAssemblies = ReferenceAssemblies.Net.Net60 // the default from Microsoft
            ReferenceAssemblies = Net.Net70 // custom because there is no net70 version in the provided Nuget yet.
        }
    }
    .RunAsync();
}

My custom net70 ReferenceAssemblies:

using Microsoft.CodeAnalysis.Testing;
using System.Collections.Immutable;

namespace Framework.Analyzers.Test;

internal static class Net
{
    private static readonly Lazy<ReferenceAssemblies> _lazyNet70 = new(() =>
        new ReferenceAssemblies(
            "net7.0",
            new PackageIdentity(
                "Microsoft.NETCore.App.Ref",
                "7.0.0-preview.5.22301.12"),
            Path.Combine("ref", "net7.0")));
    public static ReferenceAssemblies Net70 => _lazyNet70.Value;

    private static readonly Lazy<ReferenceAssemblies> _lazyNet70Windows = new(() =>
        Net70.AddPackages(
            ImmutableArray.Create(
                new PackageIdentity("Microsoft.WindowsDesktop.App.Ref", "7.0.0-preview.5.22302.5"))));
    public static ReferenceAssemblies Net70Windows => _lazyNet70Windows.Value;
}
Guesthouse answered 27/10, 2022 at 7:11 Comment(1)
it can be now updated to "7.0.7" version and works perfectly fine, thanks! it's crazy how it's still not available in the original class and now little docs on that is availableMichelle
A
1

Update for latest syntax:

var ut = new VerifyCS.Test
{
    TestState =
    {
        Sources = { test },
    },
    ExpectedDiagnostics = { VerifyCS.Diagnostic(MyAnalyzer.DiagnosticId) },
};

ut.TestState.AdditionalReferences.Add(typeof(IMyInterface).Assembly);
await ut.RunAsync();

Be sure to include a ProjectReference to the Project in your sln that contains your interface.

Annunciation answered 25/9, 2023 at 17:13 Comment(0)
H
0

Don't try that, it's not supported in a nice way in Analyzers currently. You need to reference / resolve the type / namespace / assembly names via strings.

You can get your attribute's Symbol (!) from the compilation. A Symbol is not the same as the actual Type. e.g.

public override void Initialize(AnalysisContext ac)
{
  ac.RegisterCompilationStartAction(start=> {
    // Avoid calling this often!
    // Can be null if the currently complied project
    // doesn't know about this type!
    var moduleAttribute = csa.Compilation.GetTypeByMetadataName("Framework.ModuleAttribute");

    // Analyze all the declared classes types in your solution
    start.RegisterSymbolAction(
      symbolKinds: new[] { SymbolKind.NamedType },
      action: ctx => {
        var currentType = ctx.Symbol as INamedTypeSymbol;

        // <check if currentType must have an [ModuleAttribute]>

        // if yes, then
        var attributes = currentType.GetAttributes();
        var hasModuleAttribute = attributes.Any(
          attr => attr.AttributeClass.Equals(moduleAttribute));

        // emit diagnostics if needed...
      });
  });
}
Herve answered 26/10, 2022 at 16:12 Comment(1)
Thank you for your answer. But I think you misunderstood the question. I was not asking about how to write the analyzer - that is more or less how you suggested. The Question was about how to write the test. In my opinion, it must be possible to reference the 'Framework.dll' in the test compilation... In the meantime I noticed that it is possible to add the path to the assembly to the AdditionalReferences - but there I geht an Assembly missmatch error between System.Runtime.dll 7.0.0 & 4.2.0...Guesthouse

© 2022 - 2024 — McMap. All rights reserved.