Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Testing;
using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<TUnit.Analyzers.MissingPolyfillAnalyzer>;

Expand Down Expand Up @@ -48,4 +50,78 @@ public class MyClass
.WithArguments("System.Runtime.CompilerServices.ModuleInitializerAttribute")
);
}

[Test]
public async Task No_Error_When_ModuleInitializerAttribute_Provided_By_Polyfill()
{
var net48References = new ReferenceAssemblies(
"net48",
new PackageIdentity("Microsoft.NETFramework.ReferenceAssemblies.net48", "1.0.3"),
System.IO.Path.Combine("ref", "net48"));

await Verifier
.VerifyAnalyzerAsync(
"""
namespace System.Runtime.CompilerServices
{
internal sealed class ModuleInitializerAttribute : Attribute
{
}
}

public class MyClass
{
}
""",
test =>
{
test.ReferenceAssemblies = net48References;
test.TestState.AdditionalReferences.Clear();
test.CompilerDiagnostics = CompilerDiagnostics.None;
}
);
}

[Test]
public async Task No_Error_When_ModuleInitializerAttribute_In_Multiple_Referenced_Assemblies()
{
// On modern TFMs, ModuleInitializerAttribute is in the runtime.
// When a referenced library also embeds it via Polyfill,
// GetTypeByMetadataName returns null due to ambiguity.
// The analyzer should not report TUnit0073 in this case.
var refs = await ReferenceAssemblies.Net.Net90.ResolveAsync(LanguageNames.CSharp, CancellationToken.None);

var attributeSource = CSharpSyntaxTree.ParseText("""
namespace System.Runtime.CompilerServices
{
internal sealed class ModuleInitializerAttribute : Attribute
{
}
}
""");

using var stream = new MemoryStream();
var result = CSharpCompilation.Create("PolyfillLibrary",
[attributeSource],
refs,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.Emit(stream);
await TUnit.Assertions.Assert.That(result.Success).IsTrue();

await Verifier
.VerifyAnalyzerAsync(
"""
public class MyClass
{
}
""",
test =>
{
test.TestState.AdditionalReferences.Clear();
test.TestState.AdditionalReferences.Add(
MetadataReference.CreateFromImage(stream.ToArray()));
test.CompilerDiagnostics = CompilerDiagnostics.None;
}
);
}
}
25 changes: 24 additions & 1 deletion TUnit.Analyzers/MissingPolyfillAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,34 @@ private static void AnalyzeCompilation(CompilationAnalysisContext context)
{
foreach (var typeName in RequiredTypes)
{
if (context.Compilation.GetTypeByMetadataName(typeName) is null)
if (!HasType(context.Compilation, typeName))
{
context.ReportDiagnostic(
Diagnostic.Create(Rules.MissingPolyfillPackage, Location.None, typeName));
}
}
}

private static bool HasType(Compilation compilation, string typeName)
{
// GetTypeByMetadataName returns null when the type exists in multiple
// referenced assemblies (e.g. when multiple libraries embed
// ModuleInitializerAttribute via Polyfill). Check each assembly
// individually to avoid this ambiguity problem.
if (compilation.GetTypeByMetadataName(typeName) is not null)
{
return true;
}

foreach (var reference in compilation.References)
{
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly &&
assembly.GetTypeByMetadataName(typeName) is not null)
{
return true;
}
}

return false;
}
}
Loading