Skip to content

Commit

Permalink
Merge pull request #814 from icsharpcode/issue/174
Browse files Browse the repository at this point in the history
Simple examples that always work and have the same args
  • Loading branch information
GrahamTheCoder authored Feb 6, 2022
2 parents 97ea8f3 + ead15f4 commit 068c7dd
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)


### VB -> C#

* Fix access modifiers for explicit interface implementations. [#819](https://github.com/icsharpcode/CodeConverter/issues/819)
* Fix code generation for explicit interface implementations. [#813](https://github.com/icsharpcode/CodeConverter/issues/813)
* Replace VB-specific library methods with idiomatic framework alternatives [#814](https://github.com/icsharpcode/CodeConverter/pull/814)

### C# -> VB


Expand Down
28 changes: 21 additions & 7 deletions CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Xml.Linq;
using ICSharpCode.CodeConverter.CSharp.Replacements;
using ICSharpCode.CodeConverter.Shared;
using ICSharpCode.CodeConverter.Util;
using ICSharpCode.CodeConverter.Util.FromRoslyn;
Expand Down Expand Up @@ -384,9 +385,16 @@ public override async Task<CSharpSyntaxNode> VisitParenthesizedExpression(VBasic

public override async Task<CSharpSyntaxNode> VisitMemberAccessExpression(VBasic.Syntax.MemberAccessExpressionSyntax node)
{
var nodeSymbol = GetSymbolInfoInDocument<ISymbol>(node.Name);

if (!node.IsParentKind(VBasic.SyntaxKind.InvocationExpression) &&
SimpleMethodReplacement.TryGet(nodeSymbol, out var methodReplacement) &&
methodReplacement.ReplaceIfMatches(nodeSymbol, Array.Empty<ArgumentSyntax>(), node.IsParentKind(VBasic.SyntaxKind.AddressOfExpression)) is {} replacement) {
return replacement;
}

var simpleNameSyntax = await node.Name.AcceptAsync<SimpleNameSyntax>(TriviaConvertingExpressionVisitor);

var nodeSymbol = GetSymbolInfoInDocument<ISymbol>(node.Name);
var isDefaultProperty = nodeSymbol is IPropertySymbol p && VBasic.VisualBasicExtensions.IsDefault(p);
ExpressionSyntax left = null;
if (node.Expression is VBasic.Syntax.MyClassExpressionSyntax && nodeSymbol != null) {
Expand Down Expand Up @@ -843,7 +851,8 @@ public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax

private async Task<CSharpSyntaxNode> WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol)
{
if (invocationSymbol.ContainingType.Name != nameof(Conversions) ||
if (invocationSymbol?.ContainingNamespace.MetadataName != nameof(Microsoft.VisualBasic) ||
invocationSymbol.ContainingType.Name != nameof(Conversions) ||
!invocationSymbol.Name.StartsWith("To") ||
conversionNode.ArgumentList.Arguments.Count != 1) {
return null;
Expand Down Expand Up @@ -895,8 +904,8 @@ public override async Task<CSharpSyntaxNode> VisitInvocationExpression(
private async Task<CSharpSyntaxNode> ConvertOrReplaceInvocationAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol)
{
var expressionSymbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch<ISymbol>();
if (expressionSymbol?.ContainingNamespace.MetadataName == nameof(Microsoft.VisualBasic) &&
(await SubstituteVisualBasicMethodOrNullAsync(node, invocationSymbol, expressionSymbol) ?? await WithRemovedRedundantConversionOrNullAsync(node, expressionSymbol)) is { } csEquivalent) {
if ((await SubstituteVisualBasicMethodOrNullAsync(node, invocationSymbol, expressionSymbol) ??
await WithRemovedRedundantConversionOrNullAsync(node, expressionSymbol)) is { } csEquivalent) {
return csEquivalent;
}

Expand Down Expand Up @@ -1710,11 +1719,10 @@ private ArgumentListSyntax CreateArgList(ISymbol invocationSymbol)
);
}

private async Task<CSharpSyntaxNode> SubstituteVisualBasicMethodOrNullAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol, ISymbol expressionSymbol)
private async Task<CSharpSyntaxNode> SubstituteVisualBasicMethodOrNullAsync(VBSyntax.InvocationExpressionSyntax node, ISymbol invocationSymbol, ISymbol symbol)
{
ExpressionSyntax cSharpSyntaxNode = null;
var symbol = _semanticModel.GetSymbolInfo(node.Expression).ExtractBestMatch<ISymbol>();
if (symbol?.Name == "ChrW" || symbol?.Name == "Chr") {
if (symbol?.ContainingNamespace.MetadataName == nameof(Microsoft.VisualBasic) && symbol?.Name == "ChrW" || symbol?.Name == "Chr") {
var vbArg = node.ArgumentList.Arguments.Single().GetExpression();
var constValue = _semanticModel.GetConstantValue(vbArg);
if (IsCultureInvariant(symbol, constValue)) {
Expand All @@ -1723,9 +1731,15 @@ private async Task<CSharpSyntaxNode> SubstituteVisualBasicMethodOrNullAsync(VBSy
}
}

if (SimpleMethodReplacement.TryGet(symbol, out var methodReplacement) &&
methodReplacement.ReplaceIfMatches(symbol, await ConvertArgumentsAsync(node.ArgumentList), false) is {} csExpression) {
cSharpSyntaxNode = csExpression;
}

return cSharpSyntaxNode;
}


private static bool RequiresStringCompareMethodToBeAppended(ISymbol symbol) =>
symbol?.ContainingType.Name == nameof(Strings) &&
symbol.ContainingType.ContainingNamespace.Name == nameof(Microsoft.VisualBasic) &&
Expand Down
147 changes: 147 additions & 0 deletions CodeConverter/CSharp/Replacements/SimpleMethodReplacement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.CodeConverter.Util;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ICSharpCode.CodeConverter.CSharp.Replacements;

internal class SimpleMethodReplacement
{
private const string AnnotationKind = nameof(SimpleMethodReplacement) + "placeholder";
private static readonly IdentifierNameSyntax FirstArgPlaceholder = SyntaxFactory.IdentifierName("placeholder0").WithAdditionalAnnotations(new SyntaxAnnotation(AnnotationKind, "0"));

private static readonly IReadOnlyDictionary<string, SimpleMethodReplacement> MethodReplacements = new SimpleMethodReplacement[] {
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.CurrentDirectory", "System.IO.Directory.GetCurrentDirectory"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.CombinePath", "System.IO.Path.Combine"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.GetDirectoryInfo", "System.IO.DirectoryInfo", true),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.GetDriveInfo", "System.IO.DriveInfo", true),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.GetFileInfo", "System.IO.FileInfo", true),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.GetName", "System.IO.Path.GetFileName"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.GetTempFileName", "System.IO.Path.GetTempFileName"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.ReadAllBytes", "System.IO.File.ReadAllBytes"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.ReadAllText", "System.IO.File.ReadAllText"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.DirectoryExists", "System.IO.Directory.Exists"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.FileExists", "System.IO.File.Exists"),
new("Microsoft.VisualBasic.MyServices.FileSystemProxy.DeleteFile", "System.IO.File.Delete"),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.Temp", "System.IO.Path.GetTempPath"),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.MyDocuments", SpecialFolderSyntax("Personal")),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.MyMusic", SpecialFolderSyntax("MyMusic")),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.MyPictures", SpecialFolderSyntax("MyPictures")),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.Desktop", SpecialFolderSyntax("Desktop")),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.Programs", SpecialFolderSyntax("Programs")),
new("Microsoft.VisualBasic.MyServices.SpecialDirectoriesProxy.ProgramFiles", SpecialFolderSyntax("ProgramFiles")),
new("Microsoft.VisualBasic.Devices.ComputerInfo.InstalledUICulture", "System.Globalization.CultureInfo.InstalledUICulture", replaceWithProperty: true),
new("Microsoft.VisualBasic.Devices.ComputerInfo.OSFullName", "System.Runtime.InteropServices.RuntimeInformation.OSDescription", replaceWithProperty: true),
new("Microsoft.VisualBasic.Devices.ComputerInfo.SPlatform", "System.Environment.OSVersion.Platform.ToString"),
new("Microsoft.VisualBasic.Devices.ComputerInfo.OSVersion", "System.Environment.OSVersion.Version.ToString"),
new("Microsoft.VisualBasic.DateAndTime.Now", "System.DateTime.Now", replaceWithProperty: true),
new("Microsoft.VisualBasic.DateAndTime.Today", "System.DateTime.Today", replaceWithProperty: true),
new("Microsoft.VisualBasic.DateAndTime.Year", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetYear"),
new("Microsoft.VisualBasic.DateAndTime.Month", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetMonth"),
new("Microsoft.VisualBasic.DateAndTime.Day", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetDayOfMonth"),
new("Microsoft.VisualBasic.DateAndTime.Hour", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetHour"),
new("Microsoft.VisualBasic.DateAndTime.Minute", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetMinute"),
new("Microsoft.VisualBasic.DateAndTime.Second", "System.Threading.Thread.CurrentThread.CurrentCulture.Calendar.GetSecond"),
new ("Microsoft.VisualBasic.Information.IsArray", IsSystemType(nameof(Array))),
new ("Microsoft.VisualBasic.Information.IsDBNull", IsSystemType(nameof(DBNull))),
new("Microsoft.VisualBasic.Information.IsError", IsSystemType(nameof(Exception))),
new("Microsoft.VisualBasic.Information.IsNothing", Equals(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))),
// Can use IsNotExpression in CodeAnalysis 3+
new("Microsoft.VisualBasic.Information.IsReference", SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, IsSystemType(nameof(ValueType)))),
}.ToDictionary(x => x._toReplace.Last(), StringComparer.OrdinalIgnoreCase);

private static ExpressionSyntax SpecialFolderSyntax(string specialFolderEnumName)
{
return SyntaxFactory.InvocationExpression(
ValidSyntaxFactory.MemberAccess(nameof(System), nameof(Environment), nameof(Environment.GetFolderPath)),
ValidSyntaxFactory.MemberAccess(nameof(System), nameof(Environment), nameof(Environment.SpecialFolder), specialFolderEnumName).Yield().CreateCsArgList()
);
}

private static ExpressionSyntax IsSystemType(string isTypeName)
{
var binaryExpressionSyntax = Is(SystemType(isTypeName));
return SyntaxFactory.ParenthesizedExpression(binaryExpressionSyntax);
}

private static ExpressionSyntax Equals(ExpressionSyntax rhsExpression) =>
SyntaxFactory.ParenthesizedExpression(SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, FirstArgPlaceholder, rhsExpression));

private static BinaryExpressionSyntax Is(ExpressionSyntax typeSyntax) =>
SyntaxFactory.BinaryExpression(SyntaxKind.IsExpression, FirstArgPlaceholder, typeSyntax);

private static QualifiedNameSyntax SystemType(string isTypeName) =>
SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName(nameof(System)), SyntaxFactory.IdentifierName(isTypeName));

private readonly string[] _toReplace;
private readonly ExpressionSyntax _replaceWith;

private SimpleMethodReplacement(string toReplace, string replaceWith, bool replaceWithObjectCreation = false, bool replaceWithProperty = false)
{
_toReplace = toReplace.Split('.');
_replaceWith = replaceWithObjectCreation ? SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(replaceWith))
: replaceWithProperty ? ValidSyntaxFactory.MemberAccess(replaceWith.Split('.'))
: SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(replaceWith.Split('.')));
}

private SimpleMethodReplacement(string toReplace, ExpressionSyntax replaceWith)
{
_toReplace = toReplace.Split('.');
_replaceWith = replaceWith;
}

public static bool TryGet(ISymbol symbol, out SimpleMethodReplacement r)
{
r = null;
return symbol != null && MethodReplacements.TryGetValue(symbol.Name, out r);
}

public ExpressionSyntax ReplaceIfMatches(ISymbol symbol, IEnumerable<ArgumentSyntax> args, bool isAddressOf)
{
if (QualifiedMethodNameMatches(symbol, _toReplace))
{
if (isAddressOf) {
if (_replaceWith is InvocationExpressionSyntax ies) return ies.Expression;
return null;
}

var argumentListSyntax = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(args));
if (argumentListSyntax.Arguments.Any()) {
return _replaceWith switch {
ObjectCreationExpressionSyntax oces => oces.WithArgumentList(argumentListSyntax),
InvocationExpressionSyntax ies => ies.WithArgumentList(argumentListSyntax),
var zeroOrSingleArgExpression => ReplacePlaceholderArgs(zeroOrSingleArgExpression, argumentListSyntax)
};
}

return _replaceWith;
}

return null;
}

private static ExpressionSyntax ReplacePlaceholderArgs(ExpressionSyntax zeroOrSingleArgExpression, ArgumentListSyntax argumentListSyntax)
{
for (var index = 0; index < argumentListSyntax.Arguments.Count; index++) {
var expression = argumentListSyntax.Arguments[index].Expression;
var nodeToReplace = zeroOrSingleArgExpression.GetAnnotatedNodes(AnnotationKind).Single(x => x.GetAnnotations(AnnotationKind).Single().Data == index.ToString());
zeroOrSingleArgExpression = zeroOrSingleArgExpression.ReplaceNode(nodeToReplace, expression);
}

return zeroOrSingleArgExpression;
}

private static bool QualifiedMethodNameMatches(ISymbol symbol, params string[] parts)
{
if (symbol == null) return false;
foreach (var part in parts.Reverse()) {
if (!part.Equals(symbol.Name, StringComparison.OrdinalIgnoreCase)) return false;
symbol = symbol.ContainingSymbol;
}

return !symbol.ContainingSymbol.CanBeReferencedByName;
}
}
5 changes: 3 additions & 2 deletions Tests/CSharp/ExpressionTests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1716,13 +1716,14 @@ await TestConversionVisualBasicToCSharpAsync(@"Public Class Class1
Sub Foo()
Dim x = DateAdd(""m"", 5, Now)
End Sub
End Class", @"using Microsoft.VisualBasic; // Install-Package Microsoft.VisualBasic
End Class", @"using System;
using Microsoft.VisualBasic; // Install-Package Microsoft.VisualBasic
public partial class Class1
{
public void Foo()
{
var x = DateAndTime.DateAdd(""m"", 5d, DateAndTime.Now);
var x = DateAndTime.DateAdd(""m"", 5d, DateTime.Now);
}
}");
}
Expand Down
Loading

0 comments on commit 068c7dd

Please sign in to comment.