未验证 提交 d9eafd0c 编写于 作者: E Elinor Fung 提交者: GitHub

Move diagnostics for invalid GeneratedDllImportAttribute usage to generator...

Move diagnostics for invalid GeneratedDllImportAttribute usage to generator instead of analyzer (#65915)
上级 b4c746b7
# Generator Diagnostics
For all [Roslyn diagnostics](https://docs.microsoft.com/dotnet/api/microsoft.codeanalysis.diagnostic) reported by the P/Invoke source generator:
| Setting | Value |
| -------- | ------------------ |
| Category | SourceGeneration |
| Severity | Error |
| Enabled | True |
The P/Invoke source generator emits the following diagnostics.
## `DLLIMPORTGEN001`: Specified type is not supported by source-generated P/Invokes
A method marked `GeneratedDllImport` has a parameter or return type that is not supported by source-generated P/Invokes.
```C#
// 'object' without any specific marshalling configuration
[GeneratedDllImport("NativeLib")]
public static partial void Method(object o);
```
## `DLLIMPORTGEN002`: Specified configuration is not supported by source-generated P/Invokes
A method marked `GeneratedDllImport` has configuration that is not supported by source-generated P/Invokes. This may be configuration of the method itself or its parameter or return types.
```C#
// MarshalAs value that does not map to an UnmanagedType
[GeneratedDllImport("NativeLib")]
public static partial void Method([MarshalAs(1)] int i);
// Unsupported field on MarshalAs (SafeArraySubType, SafeArrayUserDefinedSubType, IidParameterIndex)
[GeneratedDllImport("NativeLib")]
public static partial void Method([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BOOL)] bool[] bArr);
// Unsupported combination of MarshalAs and type being marshalled
[GeneratedDllImport("NativeLib")]
public static partial void Method([MarshalAs(UnmanagedType.LPStr)] bool b);
```
## `DLLIMPORTGEN003`: Current target framework is not supported by source-generated P/Invokes
The `GeneratedDllImport` is being used when targeting a framework that is not supported by source-generated P/Invokes. The generated code is currently only compatible with .NET 5.0 or above.
# Analyzer Diagnostics
The P/Invoke source generator library also contains analyzers that emit diagnostics. These diagnostics flag issues around usage (i.e. of P/Invoke and marshalling attributes) rather than the source generation scenario support issues flagged by the generator itself.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics;
namespace Microsoft.Interop.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class GeneratedDllImportAnalyzer : DiagnosticAnalyzer
{
private const string Category = "Usage";
public static readonly DiagnosticDescriptor GeneratedDllImportMissingModifiers =
new DiagnosticDescriptor(
Ids.GeneratedDllImportMissingRequiredModifiers,
GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersTitle)),
GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersMessage)),
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersDescription)));
public static readonly DiagnosticDescriptor GeneratedDllImportContainingTypeMissingModifiers =
new DiagnosticDescriptor(
Ids.GeneratedDllImportContaiingTypeMissingRequiredModifiers,
GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersTitle)),
GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersMessage)),
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersDescription)));
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(GeneratedDllImportMissingModifiers, GeneratedDllImportContainingTypeMissingModifiers);
public override void Initialize(AnalysisContext context)
{
// Don't analyze generated code
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(
compilationContext =>
{
INamedTypeSymbol? generatedDllImportAttributeType = compilationContext.Compilation.GetTypeByMetadataName(TypeNames.GeneratedDllImportAttribute);
if (generatedDllImportAttributeType == null)
return;
compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, generatedDllImportAttributeType), SymbolKind.Method);
});
}
private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol generatedDllImportAttributeType)
{
var methodSymbol = (IMethodSymbol)context.Symbol;
// Check if method is marked with GeneratedDllImportAttribute
ImmutableArray<AttributeData> attributes = methodSymbol.GetAttributes();
if (!attributes.Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, generatedDllImportAttributeType)))
return;
if (!methodSymbol.IsStatic)
{
// Must be marked static
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(GeneratedDllImportMissingModifiers, methodSymbol.Name));
}
else
{
// Make sure declarations are marked partial. Technically, we can just check one
// declaration, since Roslyn would error on inconsistent partial declarations.
foreach (SyntaxReference reference in methodSymbol.DeclaringSyntaxReferences)
{
SyntaxNode syntax = reference.GetSyntax(context.CancellationToken);
if (syntax is MethodDeclarationSyntax methodSyntax && !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword))
{
// Must be marked partial
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(GeneratedDllImportMissingModifiers, methodSymbol.Name));
break;
}
}
for (INamedTypeSymbol? typeSymbol = methodSymbol.ContainingType; typeSymbol is not null; typeSymbol = typeSymbol.ContainingType)
{
foreach (SyntaxReference reference in typeSymbol.DeclaringSyntaxReferences)
{
SyntaxNode syntax = reference.GetSyntax(context.CancellationToken);
if (syntax is TypeDeclarationSyntax typeSyntax && !typeSyntax.Modifiers.Any(SyntaxKind.PartialKeyword))
{
// Must be marked partial
context.ReportDiagnostic(typeSymbol.CreateDiagnostic(GeneratedDllImportContainingTypeMissingModifiers, typeSymbol.Name));
break;
}
}
}
}
}
}
}
......@@ -75,23 +75,31 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.CreateSyntaxProvider(
static (node, ct) => ShouldVisitNode(node),
static (context, ct) =>
new
{
MethodDeclarationSyntax syntax = (MethodDeclarationSyntax)context.Node;
if (context.SemanticModel.GetDeclaredSymbol(syntax, ct) is IMethodSymbol methodSymbol
&& methodSymbol.GetAttributes().Any(static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute))
{
Syntax = (MethodDeclarationSyntax)context.Node,
Symbol = (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!
})
return new { Syntax = syntax, Symbol = methodSymbol };
}
return null;
})
.Where(
static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any(
static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute)
);
static modelData => modelData is not null);
var methodsToGenerate = attributedMethods.Where(static data => !data.Symbol.ReturnsByRef && !data.Symbol.ReturnsByRefReadonly);
var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) =>
{
Diagnostic? diagnostic = GetDiagnosticIfInvalidMethodForGeneration(data.Syntax, data.Symbol);
return new { Syntax = data.Syntax, Symbol = data.Symbol, Diagnostic = diagnostic };
});
var refReturnMethods = attributedMethods.Where(static data => data.Symbol.ReturnsByRef || data.Symbol.ReturnsByRefReadonly);
var methodsToGenerate = methodsWithDiagnostics.Where(static data => data.Diagnostic is null);
var invalidMethodDiagnostics = methodsWithDiagnostics.Where(static data => data.Diagnostic is not null);
context.RegisterSourceOutput(refReturnMethods, static (context, refReturnMethod) =>
context.RegisterSourceOutput(invalidMethodDiagnostics, static (context, invalidMethod) =>
{
context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, refReturnMethod.Syntax.GetLocation(), "ref return", refReturnMethod.Symbol.ToDisplayString()));
context.ReportDiagnostic(invalidMethod.Diagnostic);
});
IncrementalValueProvider<(Compilation compilation, TargetFramework targetFramework, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider
......@@ -739,8 +747,12 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode)
return false;
}
var methodSyntax = (MethodDeclarationSyntax)syntaxNode;
// Filter out methods with no attributes early.
return ((MethodDeclarationSyntax)syntaxNode).AttributeLists.Count > 0;
}
private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method)
{
// Verify the method has no generic types or defined implementation
// and is marked static and partial.
if (methodSyntax.TypeParameterList is not null
......@@ -748,7 +760,7 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode)
|| !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)
|| !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword))
{
return false;
return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, methodSyntax.Identifier.GetLocation(), method.Name);
}
// Verify that the types the method is declared in are marked partial.
......@@ -756,17 +768,17 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode)
{
if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
return false;
return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, methodSyntax.Identifier.GetLocation(), method.Name, typeDecl.Identifier);
}
}
// Filter out methods with no attributes early.
if (methodSyntax.AttributeLists.Count == 0)
// Verify the method does not have a ref return
if (method.ReturnsByRef || method.ReturnsByRefReadonly)
{
return false;
return Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, methodSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString());
}
return true;
return null;
}
}
}
......@@ -28,6 +28,26 @@ public class Ids
private const string Category = "SourceGeneration";
public static readonly DiagnosticDescriptor InvalidAttributedMethodSignature =
new DiagnosticDescriptor(
Ids.InvalidGeneratedDllImportAttributeUsage,
GetResourceString(nameof(Resources.InvalidLibraryImportAttributeUsageTitle)),
GetResourceString(nameof(Resources.InvalidAttributedMethodSignatureMessage)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.InvalidAttributedMethodDescription)));
public static readonly DiagnosticDescriptor InvalidAttributedMethodContainingTypeMissingModifiers =
new DiagnosticDescriptor(
Ids.InvalidGeneratedDllImportAttributeUsage,
GetResourceString(nameof(Resources.InvalidLibraryImportAttributeUsageTitle)),
GetResourceString(nameof(Resources.InvalidAttributedMethodContainingTypeMissingModifiersMessage)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(nameof(Resources.InvalidAttributedMethodDescription)));
public static readonly DiagnosticDescriptor InvalidStringMarshallingConfiguration =
new DiagnosticDescriptor(
Ids.InvalidGeneratedDllImportAttributeUsage,
......
......@@ -331,92 +331,65 @@ internal class Resources {
}
/// <summary>
/// Looks up a localized string similar to Types that contain methods marked with &apos;GeneratedDllImportAttribute&apos; must be &apos;partial&apos;. P/Invoke source generation will ignore methods contained within non-partial types..
/// </summary>
internal static string GeneratedDllImportContainingTypeMissingModifiersDescription {
get {
return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; contains methods marked with &apos;GeneratedDllImportAttribute&apos; and should be &apos;partial&apos;. P/Invoke source generation will ignore methods contained within non-partial types..
/// </summary>
internal static string GeneratedDllImportContainingTypeMissingModifiersMessage {
get {
return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Types that contain methods marked with &apos;GeneratedDllImportAttribute&apos; must be &apos;partial&apos;..
/// </summary>
internal static string GeneratedDllImportContainingTypeMissingModifiersTitle {
get {
return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Methods marked with &apos;GeneratedDllImportAttribute&apos; should be &apos;static&apos; and &apos;partial&apos;. P/Invoke source generation will ignore methods that are not &apos;static&apos; and &apos;partial&apos;..
/// Looks up a localized string similar to The return type of &apos;GetPinnableReference&apos; (after accounting for &apos;ref&apos;) must be blittable..
/// </summary>
internal static string GeneratedDllImportMissingModifiersDescription {
internal static string GetPinnableReferenceReturnTypeBlittableDescription {
get {
return ResourceManager.GetString("GeneratedDllImportMissingModifiersDescription", resourceCulture);
return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Method &apos;{0}&apos; should be &apos;static&apos; and &apos;partial&apos; when marked with &apos;GeneratedDllImportAttribute&apos;. P/Invoke source generation will ignore methods that are not &apos;static&apos; and &apos;partial&apos;..
/// Looks up a localized string similar to The dereferenced type of the return type of the &apos;GetPinnableReference&apos; method must be blittable.
/// </summary>
internal static string GeneratedDllImportMissingModifiersMessage {
internal static string GetPinnableReferenceReturnTypeBlittableMessage {
get {
return ResourceManager.GetString("GeneratedDllImportMissingModifiersMessage", resourceCulture);
return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Method marked with &apos;GeneratedDllImportAttribute&apos; should be &apos;static&apos; and &apos;partial&apos;.
/// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible..
/// </summary>
internal static string GeneratedDllImportMissingModifiersTitle {
internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription {
get {
return ResourceManager.GetString("GeneratedDllImportMissingModifiersTitle", resourceCulture);
return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The return type of &apos;GetPinnableReference&apos; (after accounting for &apos;ref&apos;) must be blittable..
/// Looks up a localized string similar to Type &apos;{0}&apos; has a &apos;GetPinnableReference&apos; method but its native type does not support marshalling in scenarios where pinning is impossible.
/// </summary>
internal static string GetPinnableReferenceReturnTypeBlittableDescription {
internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage {
get {
return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture);
return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The dereferenced type of the return type of the &apos;GetPinnableReference&apos; method must be blittable.
/// Looks up a localized string similar to Method &apos;{0}&apos; is contained in a type &apos;{1}&apos; that is not marked &apos;partial&apos;. P/Invoke source generation will ignore method &apos;{0}&apos;..
/// </summary>
internal static string GetPinnableReferenceReturnTypeBlittableMessage {
internal static string InvalidAttributedMethodContainingTypeMissingModifiersMessage {
get {
return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture);
return ResourceManager.GetString("InvalidAttributedMethodContainingTypeMissingModifiersMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible..
/// Looks up a localized string similar to Methods marked with &apos;GeneratedDllImportAttribute&apos; should be &apos;static&apos;, &apos;partial&apos;, and non-generic. P/Invoke source generation will ignore methods that are non-&apos;static&apos;, non-&apos;partial&apos;, or generic..
/// </summary>
internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription {
internal static string InvalidAttributedMethodDescription {
get {
return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture);
return ResourceManager.GetString("InvalidAttributedMethodDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type &apos;{0}&apos; has a &apos;GetPinnableReference&apos; method but its native type does not support marshalling in scenarios where pinning is impossible.
/// Looks up a localized string similar to Method &apos;{0}&apos; should be &apos;static&apos;, &apos;partial&apos;, and non-generic when marked with &apos;GeneratedDllImportAttribute&apos;. P/Invoke source generation will ignore method &apos;{0}&apos;..
/// </summary>
internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage {
internal static string InvalidAttributedMethodSignatureMessage {
get {
return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture);
return ResourceManager.GetString("InvalidAttributedMethodSignatureMessage", resourceCulture);
}
}
......
......@@ -208,24 +208,6 @@
<data name="CustomTypeMarshallingNativeToManagedUnsupported" xml:space="preserve">
<value>The specified parameter needs to be marshalled from native to managed, but the native type '{0}' does not support it.</value>
</data>
<data name="GeneratedDllImportContainingTypeMissingModifiersDescription" xml:space="preserve">
<value>Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.</value>
</data>
<data name="GeneratedDllImportContainingTypeMissingModifiersMessage" xml:space="preserve">
<value>Type '{0}' contains methods marked with 'GeneratedDllImportAttribute' and should be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.</value>
</data>
<data name="GeneratedDllImportContainingTypeMissingModifiersTitle" xml:space="preserve">
<value>Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'.</value>
</data>
<data name="GeneratedDllImportMissingModifiersDescription" xml:space="preserve">
<value>Methods marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'.</value>
</data>
<data name="GeneratedDllImportMissingModifiersMessage" xml:space="preserve">
<value>Method '{0}' should be 'static' and 'partial' when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'.</value>
</data>
<data name="GeneratedDllImportMissingModifiersTitle" xml:space="preserve">
<value>Method marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'</value>
</data>
<data name="GetPinnableReferenceReturnTypeBlittableDescription" xml:space="preserve">
<value>The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.</value>
</data>
......@@ -238,6 +220,15 @@
<data name="GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage" xml:space="preserve">
<value>Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible</value>
</data>
<data name="InvalidAttributedMethodContainingTypeMissingModifiersMessage" xml:space="preserve">
<value>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</value>
</data>
<data name="InvalidAttributedMethodDescription" xml:space="preserve">
<value>Methods marked with 'GeneratedDllImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic.</value>
</data>
<data name="InvalidAttributedMethodSignatureMessage" xml:space="preserve">
<value>Method '{0}' should be 'static', 'partial', and non-generic when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore method '{0}'.</value>
</data>
<data name="InvalidLibraryImportAttributeUsageTitle" xml:space="preserve">
<value>Invalid 'LibraryImportAttribute' usage</value>
</data>
......
......@@ -426,6 +426,164 @@ struct Native
Assert.Empty(newCompDiags);
}
[ConditionalFact]
public async Task NonPartialMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public static void Method() { }
[GeneratedDllImport(""DoesNotExist"")]
public static extern void ExternMethod();
}
";
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature))
.WithSpan(6, 24, 6, 30)
.WithArguments("Method"),
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature))
.WithSpan(9, 31, 9, 43)
.WithArguments("ExternMethod"),
};
VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags));
var newCompDiags = newComp.GetDiagnostics();
Assert.Empty(newCompDiags);
}
[ConditionalFact]
public async Task NonStaticMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public partial void Method();
}
";
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature))
.WithSpan(6, 25, 6, 31)
.WithArguments("Method")
};
VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags));
// Generator ignores the method
TestUtils.AssertPreSourceGeneratorCompilation(newComp);
}
[ConditionalFact]
public async Task GenericMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method1<T>();
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method2<T, U>();
}
";
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature))
.WithSpan(6, 32, 6, 39)
.WithArguments("Method1"),
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature))
.WithSpan(9, 32, 9, 39)
.WithArguments("Method2"),
};
VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags));
// Generator ignores the method
TestUtils.AssertPreSourceGeneratorCompilation(newComp);
}
[ConditionalTheory]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
public async Task NonPartialParentType_Diagnostic(string typeKind)
{
string source = $@"
using System.Runtime.InteropServices;
{typeKind} Test
{{
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method();
}}
";
Compilation comp = await TestUtils.CreateCompilation(source);
// Also expect CS0751: A partial method must be declared within a partial type
string additionalDiag = "CS0751";
TestUtils.AssertPreSourceGeneratorCompilation(comp, additionalDiag);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers))
.WithSpan(6, 32, 6, 38)
.WithArguments("Method", "Test"),
};
VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags));
// Generator ignores the method
TestUtils.AssertPreSourceGeneratorCompilation(newComp, additionalDiag);
}
[ConditionalTheory]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
public async Task NonPartialGrandparentType_Diagnostic(string typeKind)
{
string source = $@"
using System.Runtime.InteropServices;
{typeKind} Test
{{
partial class TestInner
{{
[GeneratedDllImport(""DoesNotExist"")]
static partial void Method();
}}
}}
";
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);
var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator());
DiagnosticResult[] expectedDiags = new DiagnosticResult[]
{
(new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers))
.WithSpan(8, 29, 8, 35)
.WithArguments("Method", "Test"),
};
VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags));
// Generator ignores the method
TestUtils.AssertPreSourceGeneratorCompilation(newComp);
}
private static void VerifyDiagnostics(DiagnosticResult[] expectedDiagnostics, Diagnostic[] actualDiagnostics)
{
Assert.True(expectedDiagnostics.Length == actualDiagnostics.Length,
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading.Tasks;
using Xunit;
using static Microsoft.Interop.Analyzers.GeneratedDllImportAnalyzer;
using VerifyCS = DllImportGenerator.UnitTests.Verifiers.CSharpAnalyzerVerifier<Microsoft.Interop.Analyzers.GeneratedDllImportAnalyzer>;
namespace DllImportGenerator.UnitTests
{
[ActiveIssue("https://github.com/dotnet/runtime/issues/60650", TestRuntimes.Mono)]
public class GeneratedDllImportAnalyzerTests
{
[ConditionalFact]
public async Task NonPartialMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public static void {|#0:Method1|}() { }
[GeneratedDllImport(""DoesNotExist"")]
static void {|#1:Method2|}() { }
[GeneratedDllImport(""DoesNotExist"")]
public static extern void {|#2:ExternMethod1|}();
[GeneratedDllImport(""DoesNotExist"")]
static extern void {|#3:ExternMethod2|}();
}
";
await VerifyCS.VerifyAnalyzerAsync(
source,
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(0)
.WithArguments("Method1"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(1)
.WithArguments("Method2"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(2)
.WithArguments("ExternMethod1"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(3)
.WithArguments("ExternMethod2"));
}
[ConditionalFact]
public async Task NonStaticMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public partial void {|#0:Method1|}();
[GeneratedDllImport(""DoesNotExist"")]
partial void {|#1:Method2|}();
}
partial class Test
{
public partial void {|#3:Method1|}() { }
}
";
await VerifyCS.VerifyAnalyzerAsync(
source,
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(0)
.WithArguments("Method1"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(1)
.WithArguments("Method2"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(3)
.WithArguments("Method1"));
}
[ConditionalFact]
public async Task NonPartialNonStaticMethod_ReportsDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public void {|#0:Method1|}() { }
[GeneratedDllImport(""DoesNotExist"")]
void {|#1:Method2|}() { }
[GeneratedDllImport(""DoesNotExist"")]
public extern void {|#2:ExternMethod1|}();
[GeneratedDllImport(""DoesNotExist"")]
extern void {|#3:ExternMethod2|}();
}
";
await VerifyCS.VerifyAnalyzerAsync(
source,
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(0)
.WithArguments("Method1"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(1)
.WithArguments("Method2"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(2)
.WithArguments("ExternMethod1"),
VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers)
.WithLocation(3)
.WithArguments("ExternMethod2"));
}
[ConditionalFact]
public async Task NotGeneratedDllImport_NoDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
public void Method1() { }
partial void Method2();
}
";
await VerifyCS.VerifyAnalyzerAsync(source);
}
[ConditionalFact]
public async Task StaticPartialMethod_NoDiagnostic()
{
string source = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
static partial void Method2();
}
";
await VerifyCS.VerifyAnalyzerAsync(source);
}
[ConditionalTheory]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
public async Task NonPartialParentType_Diagnostic(string typeKind)
{
string source = $@"
using System.Runtime.InteropServices;
{typeKind} {{|#0:Test|}}
{{
[GeneratedDllImport(""DoesNotExist"")]
static partial void {{|CS0751:Method2|}}();
}}
";
await VerifyCS.VerifyAnalyzerAsync(
source,
VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers)
.WithLocation(0)
.WithArguments("Test"));
}
[ConditionalTheory]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
public async Task NonPartialGrandparentType_Diagnostic(string typeKind)
{
string source = $@"
using System.Runtime.InteropServices;
{typeKind} {{|#0:Test|}}
{{
partial class TestInner
{{
[GeneratedDllImport(""DoesNotExist"")]
static partial void Method2();
}}
}}
";
await VerifyCS.VerifyAnalyzerAsync(
source,
VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers)
.WithLocation(0)
.WithArguments("Test"));
}
}
}
......@@ -70,7 +70,7 @@ public static class TestUtils
/// the expected failure diagnostics.
/// </summary>
/// <param name="comp"></param>
public static void AssertPreSourceGeneratorCompilation(Compilation comp)
public static void AssertPreSourceGeneratorCompilation(Compilation comp, params string[] additionalAllowedDiagnostics)
{
var allowedDiagnostics = new HashSet<string>()
{
......@@ -79,6 +79,12 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp)
"CS0246", // Missing type or namespace - GeneratedDllImportAttribute
"CS8019", // Unnecessary using
};
foreach (string diagnostic in additionalAllowedDiagnostics)
{
allowedDiagnostics.Add(diagnostic);
}
var compDiags = comp.GetDiagnostics();
Assert.All(compDiags, diag =>
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册