diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportFixer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportFixer.cs index 14059607d30e51..14cbd2217559e1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportFixer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportFixer.cs @@ -30,6 +30,10 @@ public sealed class ConvertToLibraryImportFixer : CodeFixProvider public override FixAllProvider GetFixAllProvider() => CustomFixAllProvider.Instance; private const string ConvertToLibraryImportKey = "ConvertToLibraryImport,"; + private static string AppendSuffix(string entryPoint, char? entryPointSuffix) + => entryPointSuffix.HasValue && entryPoint.LastOrDefault() == entryPointSuffix.Value + ? entryPoint + : entryPoint + entryPointSuffix; private static string AddSuffixKey(string baseKey, char suffix) => $"{baseKey}{suffix},"; private static string AddUnsafeKey(string baseKey) => baseKey + "AddUnsafe,"; private static string AddMayRequireAdditionalWorkKey(string baseKey) => baseKey + $"{ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork},"; @@ -563,7 +567,7 @@ private static SyntaxNode GetLibraryImportAttribute( // Update attribute arguments for LibraryImport bool hasEntryPointAttributeArgument = false; - List argumentsToAdd= new List(); + List argumentsToAdd = new List(); List argumentsToRemove = new List(); foreach (SyntaxNode argument in generator.GetAttributeArguments(libraryImportSyntax)) { @@ -647,17 +651,20 @@ private static SyntaxNode GetLibraryImportAttribute( argumentsToRemove.Add(attrArg); argumentsToAdd.Add(attrArg.WithExpression( SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(entryPoint + entryPointSuffix)))); + SyntaxFactory.Literal(AppendSuffix(entryPoint, entryPointSuffix))))); } } else { - argumentsToRemove.Add(attrArg); - argumentsToAdd.Add(attrArg.WithExpression( - SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, - attrArg.Expression, - SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(entryPointSuffix.ToString()))))); + if (dllImportData.EntryPointName!.LastOrDefault() != entryPointSuffix.Value) + { + argumentsToRemove.Add(attrArg); + argumentsToAdd.Add(attrArg.WithExpression( + SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, + attrArg.Expression, + SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(entryPointSuffix.ToString()))))); + } } } } @@ -671,7 +678,7 @@ private static SyntaxNode GetLibraryImportAttribute( if (entryPointSuffix.HasValue && !hasEntryPointAttributeArgument) { argumentsToAdd.Add(generator.AttributeArgument("EntryPoint", - generator.LiteralExpression(methodName + entryPointSuffix.Value))); + generator.LiteralExpression(AppendSuffix(methodName, entryPointSuffix.Value)))); } libraryImportSyntax = generator.RemoveNodes(libraryImportSyntax, argumentsToRemove); diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs index e9ef9e0fe7d585..e930c5d1493389 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs @@ -490,6 +490,222 @@ partial class Test await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,"); } + [InlineData(CharSet.Ansi, 'A')] + [InlineData(CharSet.Unicode, 'W')] + [Theory] + public async Task SuffixPresent_ExactSpelling_False_NoAutoCharSet_Provides_No_Suffix_And_Suffix_Fix(CharSet charSet, char suffix) + { + string source = $$""" + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", EntryPoint = "Entry{{suffix}}", ExactSpelling = false, CharSet = CharSet.{{charSet}})] + public static extern void [|Method|](); + } + """; + string fixedSourceNoSuffix = $$""" + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "Entry{{suffix}}")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey); + } + + [Fact] + public async Task SuffixWPresent_ExactSpelling_False_AutoCharSet_Provides_No_Suffix_And_Both_Suffix_Fixes() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", EntryPoint = "EntryW", ExactSpelling = false, CharSet = CharSet.Auto)] + public static extern void [|Method|](); + } + """; + string fixedSourceNoSuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "EntryW")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey); + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "EntryWA")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,"); + } + + [Fact] + public async Task SuffixAPresent_ExactSpelling_False_AutoCharSet_Provides_No_Suffix_And_Both_Suffix_Fixes() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", EntryPoint = "EntryA", ExactSpelling = false, CharSet = CharSet.Auto)] + public static extern void [|Method|](); + } + """; + string fixedSourceNoSuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "EntryA")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey); + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "EntryAW")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}W,"); + } + + [Fact] + public async Task SuffixPresent_ExactSpelling_False_ImplicitAnsiCharSet_Provides_No_Suffix_And_Suffix_Fix() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", EntryPoint = "EntryA", ExactSpelling = false)] + public static extern void [|Method|](); + } + """; + string fixedSourceWithNoAdditionalSuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", EntryPoint = "EntryA")] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithNoAdditionalSuffix, $"{ConvertToLibraryImportKey}A,"); + } + + [Fact] + public async Task SuffixPresent_ExactSpelling_False_ConstantNonLiteralEntryPoint() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + private const string EntryPoint = "EntryA"; + [DllImport("DoesNotExist", EntryPoint = EntryPoint, CharSet = CharSet.Ansi, ExactSpelling = false)] + public static extern void [|Method|](); + } + """; + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + private const string EntryPoint = "EntryA"; + [LibraryImport("DoesNotExist", EntryPoint = EntryPoint)] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, ConvertToLibraryImportKey); + } + + [Fact] + public async Task SuffixPresent_Implicit_ExactSpelling_False_Offers_Suffix_Fix() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", CharSet = CharSet.Ansi)] + public static extern void [|MethodA|](); + } + """; + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist")] + public static partial void {|CS8795:MethodA|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, ConvertToLibraryImportKey); + } + + [Fact] + public async Task SuffixPresent_ExactSpelling_False_NameOfEntryPoint() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + private const string FooA = "BarA"; + [DllImport("DoesNotExist", EntryPoint = nameof(FooA), CharSet = CharSet.Ansi, ExactSpelling = false)] + public static extern void [|Method|](); + } + """; + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + private const string FooA = "BarA"; + [LibraryImport("DoesNotExist", EntryPoint = nameof(FooA))] + public static partial void {|CS8795:Method|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, ConvertToLibraryImportKey); + } + + [Fact] + public async Task SuffixPresent_ExactSpelling_False_ImplicitEntryPointName() + { + string source = """ + + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", CharSet = CharSet.Ansi, ExactSpelling = false)] + public static extern void [|MethodA|](); + } + """; + string fixedSourceWithASuffix = """ + + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist")] + public static partial void {|CS8795:MethodA|}(); + } + """; + await VerifyCodeFixAsync(source, fixedSourceWithASuffix, ConvertToLibraryImportKey); + } + [Fact] public async Task PreserveSigFalseSignatureModified() {