diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index dc1eb31e4..f35085ccf 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -2062,7 +2062,15 @@ private async Task SubstituteVisualBasicMethodOrNullAsync(VBSy } } - if (SimpleMethodReplacement.TryGet(symbol, out var methodReplacement) && + if (cSharpSyntaxNode == null && symbol is IMethodSymbol methodSymbol && + Replacements.MsgBoxReplacement.IsBestMsgBoxMatch(methodSymbol)) + { + var arguments = await ConvertArgumentsAsync(node.ArgumentList); + var msgBoxReplacement = new Replacements.MsgBoxReplacement(methodSymbol, arguments.ToList(), _semanticModel, _extraUsingDirectives, _visualBasicEqualityComparison); + cSharpSyntaxNode = msgBoxReplacement.Replace(); + } + + if (cSharpSyntaxNode == null && SimpleMethodReplacement.TryGet(symbol, out var methodReplacement) && methodReplacement.ReplaceIfMatches(symbol, await ConvertArgumentsAsync(node.ArgumentList), false) is {} csExpression) { cSharpSyntaxNode = csExpression; } diff --git a/CodeConverter/CSharp/Replacements/MsgBoxReplacement.cs b/CodeConverter/CSharp/Replacements/MsgBoxReplacement.cs new file mode 100644 index 000000000..40b97b201 --- /dev/null +++ b/CodeConverter/CSharp/Replacements/MsgBoxReplacement.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ICSharpCode.CodeConverter.CSharp.Replacements +{ + internal class MsgBoxReplacement + { + private readonly IMethodSymbol _symbol; + private readonly IReadOnlyCollection _arguments; + private readonly SemanticModel _semanticModel; + private readonly HashSet _extraUsingDirectives; + private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison; + + public MsgBoxReplacement(IMethodSymbol symbol, IReadOnlyCollection arguments, SemanticModel semanticModel, HashSet extraUsingDirectives, VisualBasicEqualityComparison visualBasicEqualityComparison) + { + _symbol = symbol; + _arguments = arguments; + _semanticModel = semanticModel; + _extraUsingDirectives = extraUsingDirectives; + _visualBasicEqualityComparison = visualBasicEqualityComparison; + } + + public ExpressionSyntax Replace() + { + var (prompt, style, title) = GetMsgBoxArguments(); + if (prompt == null) return null; // Prompt is required + + var (messageBoxButtons, messageBoxIcon) = ConvertStyle(style); + if (messageBoxButtons == null && messageBoxIcon == null && style != null) + { + // Could not convert style, fallback to default + return null; + } + + _extraUsingDirectives.Add("System.Windows.Forms"); + + var showExpression = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("MessageBox"), + SyntaxFactory.IdentifierName("Show") + ); + + var newArguments = new List + { + prompt + }; + + if (title != null) + { + newArguments.Add(title); + } else { + newArguments.Add(SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("")))); + } + + newArguments.Add(SyntaxFactory.Argument(messageBoxButtons ?? MemberAccess("OK"))); + + if (messageBoxIcon != null) + { + newArguments.Add(SyntaxFactory.Argument(messageBoxIcon)); + } + + return SyntaxFactory.InvocationExpression(showExpression, SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(newArguments))); + } + + private (ArgumentSyntax Prompt, ArgumentSyntax Buttons, ArgumentSyntax Title) GetMsgBoxArguments() + { + var args = _arguments.ToList(); + var prompt = args.Count > 0 ? args[0] : null; + var buttons = args.Count > 1 ? args[1] : null; + var title = args.Count > 2 ? args[2] : null; + return (prompt, buttons, title); + } + + private (ExpressionSyntax Buttons, ExpressionSyntax Icon) ConvertStyle(ArgumentSyntax styleArgument) + { + if (styleArgument == null) + { + return (MemberAccess("OK"), null); + } + + var styleExpression = styleArgument.Expression; + var constantValue = _semanticModel.GetConstantValue(styleExpression); + + if (!constantValue.HasValue) + { + // If we can't determine the style at compile time, we can't convert it. + // Fallback to Interaction.MsgBox will be handled by the caller. + return (null, null); + } + + var styleValue = (int)constantValue.Value; + + var buttons = GetMessageBoxButtons(styleValue); + var icon = GetMessageBoxIcon(styleValue); + + return (buttons, icon); + } + + private ExpressionSyntax GetMessageBoxButtons(int style) + { + if ((style & 0x1) == 0x1) return MemberAccess("OKCancel"); + if ((style & 0x2) == 0x2) return MemberAccess("AbortRetryIgnore"); + if ((style & 0x3) == 0x3) return MemberAccess("YesNoCancel"); + if ((style & 0x4) == 0x4) return MemberAccess("YesNo"); + if ((style & 0x5) == 0x5) return MemberAccess("RetryCancel"); + return MemberAccess("OK"); + } + + private ExpressionSyntax GetMessageBoxIcon(int style) + { + if ((style & 0x10) == 0x10) return MemberAccess("Critical", "MessageBoxIcon"); + if ((style & 0x20) == 0x20) return MemberAccess("Question", "MessageBoxIcon"); + if ((style & 0x30) == 0x30) return MemberAccess("Exclamation", "MessageBoxIcon"); + if ((style & 0x40) == 0x40) return MemberAccess("Information", "MessageBoxIcon"); + return null; + } + + private ExpressionSyntax MemberAccess(string memberName, string typeName = "MessageBoxButtons") + { + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName(typeName), + SyntaxFactory.IdentifierName(memberName) + ); + } + + public static bool IsBestMsgBoxMatch(IMethodSymbol symbol) + { + return symbol?.ContainingType?.Name == "Interaction" && + symbol.Name == "MsgBox" && + symbol.ContainingNamespace?.ToDisplayString() == "Microsoft.VisualBasic"; + } + } +} diff --git a/CodeConverter/CSharp/TypeMappings.cs b/CodeConverter/CSharp/TypeMappings.cs new file mode 100644 index 000000000..f8588cd13 --- /dev/null +++ b/CodeConverter/CSharp/TypeMappings.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace ICSharpCode.CodeConverter.CSharp +{ + internal static class TypeMappings + { + public static readonly IReadOnlyDictionary VbToCs = new Dictionary + { + { "Microsoft.VisualBasic.MsgBoxResult", "System.Windows.Forms.DialogResult" } + }; + } +} diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index b2f39e433..6a3e093dd 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1671,6 +1671,33 @@ public object Func() return FuncRet; } +}"); + } + + [Fact] + public async Task TestMsgBoxToMessageBoxConversionAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Class TestClass + Private Sub TestMethod(ByVal sMLS As String) + Dim MsgBoxRet As MsgBoxResult + MsgBoxRet = MsgBox(sMLS, MsgBoxStyle.Question Or MsgBoxStyle.YesNo, ""Title"") + If MsgBoxRet = MsgBoxResult.Yes Then + End If + End Sub +End Class", + @"using System.Windows.Forms; + +internal partial class TestClass +{ + private void TestMethod(string sMLS) + { + DialogResult MsgBoxRet; + MsgBoxRet = MessageBox.Show(sMLS, ""Title"", MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (MsgBoxRet == DialogResult.Yes) + { + } + } }"); } } \ No newline at end of file