From d2f6f5a752a88349c80a37e34e55357c0309628b Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Tue, 28 Jul 2020 10:08:58 -0700 Subject: [PATCH] Add testing for attribute discovery in cases where the user prefixes the attribute name. Add test cases for compilation failures. --- .../DllImportGenerator.Test/CodeSnippets.cs | 29 +++++++++ .../DllImportGenerator.Test/CompileFails.cs | 39 ++++++++++++ .../DllImportGenerator.Test/Compiles.cs | 36 +---------- .../DllImportGenerator.Test/TestUtils.cs | 61 +++++++++++++++++++ .../DllImportGenerator/DllImportGenerator.cs | 18 +++++- .../DllImportGenerator/DllImportStub.cs | 4 +- 6 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator.Test/CompileFails.cs create mode 100644 DllImportGenerator/DllImportGenerator.Test/TestUtils.cs diff --git a/DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs b/DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs index 4094f9f66c62..18888cf58795 100644 --- a/DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs +++ b/DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs @@ -181,6 +181,35 @@ partial class Test [GeneratedDllImport(""DoesNotExist"")] public static partial void Method(int t = 0); } +"; + + /// + /// Declaration with user defined attributes with prefixed name. + /// + public static readonly string UserDefinedPrefixedAttributes = @" +using System; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + // Prefix with ATTRIBUTE so the lengths will match during check. + sealed class ATTRIBUTEGeneratedDllImportAttribute : Attribute + { + public ATTRIBUTEGeneratedDllImportAttribute(string a) { } + } +} + +partial class Test +{ + [ATTRIBUTEGeneratedDllImportAttribute(""DoesNotExist"")] + public static partial void Method1(); + + [ATTRIBUTEGeneratedDllImport(""DoesNotExist"")] + public static partial void Method2(); + + [System.Runtime.InteropServices.ATTRIBUTEGeneratedDllImport(""DoesNotExist"")] + public static partial void Method3(); +} "; } } diff --git a/DllImportGenerator/DllImportGenerator.Test/CompileFails.cs b/DllImportGenerator/DllImportGenerator.Test/CompileFails.cs new file mode 100644 index 000000000000..75e8847232dc --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.Test/CompileFails.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using Xunit; + +namespace DllImportGenerator.Test +{ + public class CompileFails + { + public static IEnumerable CodeSnippetsToCompile() + { + yield return new object[] { CodeSnippets.UserDefinedPrefixedAttributes, 3 }; + } + + [Theory] + [MemberData(nameof(CodeSnippetsToCompile))] + public void ValidateSnippets(string source, int failCount) + { + Compilation comp = TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + Assert.Empty(generatorDiags); + + var newCompDiags = newComp.GetDiagnostics(); + + // Verify the compilation failed with missing impl. + int missingImplCount = 0; + foreach (var diag in newCompDiags) + { + if ("CS8795".Equals(diag.Id)) + { + missingImplCount++; + } + } + + Assert.Equal(failCount, missingImplCount); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator.Test/Compiles.cs b/DllImportGenerator/DllImportGenerator.Test/Compiles.cs index b41bdc1912b5..5375ab26a7e7 100644 --- a/DllImportGenerator/DllImportGenerator.Test/Compiles.cs +++ b/DllImportGenerator/DllImportGenerator.Test/Compiles.cs @@ -1,9 +1,5 @@ using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reflection; using Xunit; namespace DllImportGenerator.Test @@ -26,40 +22,14 @@ public static IEnumerable CodeSnippetsToCompile() [MemberData(nameof(CodeSnippetsToCompile))] public void ValidateSnippets(string source) { - Compilation comp = CreateCompilation(source); - var compDiags = comp.GetDiagnostics(); - foreach (var diag in compDiags) - { - Assert.True( - "CS8795".Equals(diag.Id) // Partial method impl missing - || "CS0234".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute - || "CS0246".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute - || "CS8019".Equals(diag.Id)); // Unnecessary using - } + Compilation comp = TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); - var newComp = RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); Assert.Empty(generatorDiags); var newCompDiags = newComp.GetDiagnostics(); Assert.Empty(newCompDiags); } - - private static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) - => CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, - new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) }, - new CSharpCompilationOptions(outputKind)); - - private static GeneratorDriver CreateDriver(Compilation c, params ISourceGenerator[] generators) - => new CSharpGeneratorDriver(c.SyntaxTrees.First().Options, - ImmutableArray.Create(generators), - null, - ImmutableArray.Empty); - - private static Compilation RunGenerators(Compilation c, out ImmutableArray diagnostics, params ISourceGenerator[] generators) - { - CreateDriver(c, generators).RunFullGeneration(c, out var d, out diagnostics); - return d; - } } } diff --git a/DllImportGenerator/DllImportGenerator.Test/TestUtils.cs b/DllImportGenerator/DllImportGenerator.Test/TestUtils.cs new file mode 100644 index 000000000000..0afb4144787a --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.Test/TestUtils.cs @@ -0,0 +1,61 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace DllImportGenerator.Test +{ + internal static class TestUtils + { + /// + /// Assert the pre-srouce generator compilation has only + /// the expected failure diagnostics. + /// + /// + public static void AssertPreSourceGeneratorCompilation(Compilation comp) + { + var compDiags = comp.GetDiagnostics(); + foreach (var diag in compDiags) + { + Assert.True( + "CS8795".Equals(diag.Id) // Partial method impl missing + || "CS0234".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute + || "CS0246".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute + || "CS8019".Equals(diag.Id)); // Unnecessary using + } + } + + /// + /// Create a compilation given source + /// + /// Source to compile + /// Output type + /// The resulting compilation + public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + => CSharpCompilation.Create("compilation", + new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, + new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) }, + new CSharpCompilationOptions(outputKind)); + + /// + /// Run the supplied generators on the compilation. + /// + /// Compilation target + /// Resulting diagnostics + /// Source generator instances + /// The resulting compilation + public static Compilation RunGenerators(Compilation comp, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + { + CreateDriver(comp, generators).RunFullGeneration(comp, out var d, out diagnostics); + return d; + } + + private static GeneratorDriver CreateDriver(Compilation c, params ISourceGenerator[] generators) + => new CSharpGeneratorDriver(c.SyntaxTrees.First().Options, + ImmutableArray.Create(generators), + null, + ImmutableArray.Empty); + } +} diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index e7cdc0b6d6d3..801c8b059770 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -188,8 +188,22 @@ public void Initialize(InitializationContext context) private static bool IsGeneratedDllImportAttribute(AttributeSyntax attrSyntaxMaybe) { var attrName = attrSyntaxMaybe.Name.ToString(); - return attrName.EndsWith(GeneratedDllImport) - || attrName.EndsWith(GeneratedDllImportAttribute); + + if (attrName.Length == GeneratedDllImport.Length) + { + return attrName.Equals(GeneratedDllImport); + } + else if (attrName.Length == GeneratedDllImportAttribute.Length) + { + return attrName.Equals(GeneratedDllImportAttribute); + } + + // Handle the case where the user defines an attribute with + // the same name but adds a prefix. + const string PrefixedGeneratedDllImport = "." + GeneratedDllImport; + const string PrefixedGeneratedDllImportAttribute = "." + GeneratedDllImportAttribute; + return attrName.EndsWith(PrefixedGeneratedDllImport) + || attrName.EndsWith(PrefixedGeneratedDllImportAttribute); } private IEnumerable ProcessAttributes( diff --git a/DllImportGenerator/DllImportGenerator/DllImportStub.cs b/DllImportGenerator/DllImportGenerator/DllImportStub.cs index e80b10a538dd..511e75862d42 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStub.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStub.cs @@ -91,9 +91,9 @@ public static DllImportStub Create(IMethodSymbol method, CancellationToken token // Forward call to generated P/Invoke var returnMaybe = method.ReturnType.SpecialType == SpecialType.System_Void ? string.Empty - : "return"; + : "return "; - var dispatchCall = new StringBuilder($"{returnMaybe} {dllImportName}"); + var dispatchCall = new StringBuilder($"{returnMaybe}{dllImportName}"); if (!dllImportParams.Any()) { dispatchCall.Append("();");