From 18f8528b7adfcf5939b936dcb27a664270dab52f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 02:24:38 +0000 Subject: [PATCH 01/16] Fix CS0206 when converting ReDim Preserve on a property When `ReDim Preserve` is used on a property in VB.NET, the C# converter was generating `Array.Resize(ref Property, ...)` which causes CS0206 because properties cannot be passed as `ref` or `out`. This change updates `MethodBodyExecutableStatementVisitor.cs` to detect when the target of `ReDim Preserve` is a property or member access. In such cases, it generates a temporary variable, assigns the property value to it, resizes the temporary variable using `ref`, and then assigns the temporary variable back to the property. Example: VB.NET: ```vb Public Property NumArray1 As Integer() ... ReDim Preserve NumArray1(4) ``` Converted C#: ```csharp var argNumArray1 = NumArray1; Array.Resize(ref argNumArray1, 5); NumArray1 = argNumArray1; ``` This ensures valid C# code is generated. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com> --- .../MethodBodyExecutableStatementVisitor.cs | 8 +++++ .../StatementTests/RedimPreserveTests.cs | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 Tests/CSharp/StatementTests/RedimPreserveTests.cs diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 6d8eed5e..5895bb83 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -320,6 +320,14 @@ private async Task> ConvertRedimClauseAsync(VBSyntax var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); if (preserve && convertedBounds.Count == 1) { + if (node.Expression is VBSyntax.MemberAccessExpressionSyntax || node.Expression is VBSyntax.IdentifierNameSyntax identifier && _semanticModel.GetSymbolInfo(identifier).Symbol.IsKind(SymbolKind.Property)) { + var (tempVarDecl, tempVar) = CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression); + var resizeArgs = new[] { (ExpressionSyntax)tempVar, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); + var resizeCall = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), resizeArgs); + var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, tempVar); + return SyntaxFactory.List(new StatementSyntax[] { tempVarDecl, SyntaxFactory.ExpressionStatement(resizeCall), SyntaxFactory.ExpressionStatement(assignment) }); + } + var argumentList = new[] { csTargetArrayExpression, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); return SingleStatement(arrayResize); diff --git a/Tests/CSharp/StatementTests/RedimPreserveTests.cs b/Tests/CSharp/StatementTests/RedimPreserveTests.cs new file mode 100644 index 00000000..b54930ed --- /dev/null +++ b/Tests/CSharp/StatementTests/RedimPreserveTests.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.StatementTests; + +public class RedimPreserveTests : ConverterTestBase +{ + [Fact] + public async Task RedimPreserveOnPropertyAsync() + { + await TestConversionVisualBasicToCSharpAsync( + @"Public Class TestClass + Public Property NumArray1 As Integer() + + Public Sub New() + ReDim Preserve NumArray1(4) + End Sub +End Class", @"using System; + +public partial class TestClass +{ + public int[] NumArray1 { get; set; } + + public TestClass() + { + var argNumArray1 = NumArray1; + Array.Resize(ref argNumArray1, 5); + NumArray1 = argNumArray1; + } +}"); + } +} From 1b5ac8e0d3eda83863e590309007811a92c47a26 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 12:59:12 +0000 Subject: [PATCH 02/16] Fix CS0206 when converting ReDim Preserve on a property When `ReDim Preserve` is used on a property in VB.NET, the C# converter was generating `Array.Resize(ref Property, ...)` which causes CS0206 because properties cannot be passed as `ref` or `out`. This change updates `MethodBodyExecutableStatementVisitor.cs` to detect when the target of `ReDim Preserve` is a property or member access. In such cases, it generates a temporary variable, assigns the property value to it, resizes the temporary variable using `ref`, and then assigns the temporary variable back to the property. Example: VB.NET: ```vb Public Property NumArray1 As Integer() ... ReDim Preserve NumArray1(4) ``` Converted C#: ```csharp var argNumArray1 = NumArray1; Array.Resize(ref argNumArray1, 5); NumArray1 = argNumArray1; ``` This ensures valid C# code is generated. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com> --- CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index 5895bb83..bc9ded39 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -320,7 +320,7 @@ private async Task> ConvertRedimClauseAsync(VBSyntax var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); if (preserve && convertedBounds.Count == 1) { - if (node.Expression is VBSyntax.MemberAccessExpressionSyntax || node.Expression is VBSyntax.IdentifierNameSyntax identifier && _semanticModel.GetSymbolInfo(identifier).Symbol.IsKind(SymbolKind.Property)) { + if (_semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true) { var (tempVarDecl, tempVar) = CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression); var resizeArgs = new[] { (ExpressionSyntax)tempVar, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); var resizeCall = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), resizeArgs); From dfe5298a3dc4d80d743bd0bb24f17f9ad8ad5cf4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:34:04 +0000 Subject: [PATCH 03/16] Fix CS0206 when converting ReDim Preserve on a property When `ReDim Preserve` is used on a property in VB.NET, the C# converter was generating `Array.Resize(ref Property, ...)` which causes CS0206 because properties cannot be passed as `ref` or `out`. This change updates `MethodBodyExecutableStatementVisitor.cs` to detect when the target of `ReDim Preserve` is a property or member access. In such cases, it generates a temporary variable, assigns the property value to it, resizes the temporary variable using `ref`, and then assigns the temporary variable back to the property. Example: VB.NET: ```vb Public Property NumArray1 As Integer() ... ReDim Preserve NumArray1(4) ``` Converted C#: ```csharp var argNumArray1 = NumArray1; Array.Resize(ref argNumArray1, 5); NumArray1 = argNumArray1; ``` This ensures valid C# code is generated. Code generation was also refactored to remove duplicate syntax logic for cleaner compilation. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com> --- .../MethodBodyExecutableStatementVisitor.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index bc9ded39..8f2a4856 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -320,16 +320,18 @@ private async Task> ConvertRedimClauseAsync(VBSyntax var csTargetArrayExpression = await node.Expression.AcceptAsync(_expressionVisitor); var convertedBounds = (await CommonConversions.ConvertArrayBoundsAsync(node.ArrayBounds)).Sizes.ToList(); if (preserve && convertedBounds.Count == 1) { - if (_semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true) { - var (tempVarDecl, tempVar) = CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression); - var resizeArgs = new[] { (ExpressionSyntax)tempVar, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); - var resizeCall = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), resizeArgs); - var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, tempVar); - return SyntaxFactory.List(new StatementSyntax[] { tempVarDecl, SyntaxFactory.ExpressionStatement(resizeCall), SyntaxFactory.ExpressionStatement(assignment) }); - } + bool isProperty = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.IsKind(SymbolKind.Property) == true; + var arrayToResize = isProperty ? CreateLocalVariableWithUniqueName(node.Expression, "arg" + csTargetArrayExpression.ToString().Split('.').Last(), csTargetArrayExpression) : default; + var resizeArg = isProperty ? (ExpressionSyntax)arrayToResize.Reference : csTargetArrayExpression; - var argumentList = new[] { csTargetArrayExpression, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); + var argumentList = new[] { resizeArg, convertedBounds.Single() }.CreateCsArgList(SyntaxKind.RefKeyword); var arrayResize = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Array), nameof(Array.Resize)), argumentList); + + if (isProperty) { + var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, csTargetArrayExpression, arrayToResize.Reference); + return SyntaxFactory.List(new StatementSyntax[] { arrayToResize.Declaration, SyntaxFactory.ExpressionStatement(arrayResize), SyntaxFactory.ExpressionStatement(assignment) }); + } + return SingleStatement(arrayResize); } var newArrayAssignment = CreateNewArrayAssignment(node.Expression, csTargetArrayExpression, convertedBounds); From 0532ed5b6bc041a5fde9d81acabd8b8caeecd771 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:46:00 +0000 Subject: [PATCH 04/16] Fix VB With block conversion with null-conditional operator Modified IsSubPartOfConditionalAccess to recursively check if a member access is part of the WhenNotNull branch of a ConditionalAccessExpression, handling nested conditional accesses. This ensures that the expression part (LHS) of a conditional access is correctly identified as not being part of the binding chain, allowing it to be resolved to the With block variable. Added regression test WithBlockWithNullConditionalAccessAsync. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com> --- .../CSharp/NameExpressionNodeVisitor.cs | 39 +++++++++++++--- .../StatementTests/MethodStatementTests.cs | 44 ++++++++++++++++++- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs index f9be5753..bb68567c 100644 --- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -1,4 +1,4 @@ -using System.Data; +using System.Data; using System.Globalization; using ICSharpCode.CodeConverter.CSharp.Replacements; using ICSharpCode.CodeConverter.Util.FromRoslyn; @@ -688,14 +688,39 @@ private static QualifiedNameSyntax Qualify(string qualification, ExpressionSynta private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpressionSyntax node) { - var firstPossiblyConditionalAncestor = node.Parent; - while (firstPossiblyConditionalAncestor != null && - firstPossiblyConditionalAncestor.IsKind(VBasic.SyntaxKind.InvocationExpression, - VBasic.SyntaxKind.SimpleMemberAccessExpression)) { - firstPossiblyConditionalAncestor = firstPossiblyConditionalAncestor.Parent; + SyntaxNode child = node; + SyntaxNode parent = node.Parent; + + while (parent != null) + { + if (parent.IsKind(VBasic.SyntaxKind.InvocationExpression, + VBasic.SyntaxKind.SimpleMemberAccessExpression, + VBasic.SyntaxKind.ParenthesizedExpression)) + { + child = parent; + parent = parent.Parent; + continue; + } + + if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae) + { + if (cae.WhenNotNull == child) + { + return true; + } + + if (cae.Expression == child) + { + child = parent; + parent = parent.Parent; + continue; + } + } + + break; } - return firstPossiblyConditionalAncestor?.IsKind(VBasic.SyntaxKind.ConditionalAccessExpression) == true; + return false; } private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode expr, SyntaxToken idToken, string overrideIdentifier) diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index 39bfd1c7..42c13f4d 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using ICSharpCode.CodeConverter.Tests.TestRunners; using Xunit; @@ -1673,4 +1673,44 @@ public object Func() } }"); } -} \ No newline at end of file + + [Fact] + public async Task WithBlockWithNullConditionalAccessAsync() + { + await TestConversionVisualBasicToCSharpAsync(@" +Public Class Class1 + Public Property x As Class1 + Public Property Name As String +End Class + +Public Class TestClass + Private _Data As Class1 + Private x As String + + Public Sub TestMethod() + With _Data + x = .x?.Name + End With + End Sub +End Class", @" +public partial class Class1 +{ + public Class1 x { get; set; } + public string Name { get; set; } +} + +public partial class TestClass +{ + private Class1 _Data; + private string x; + + public void TestMethod() + { + { + ref var withBlock = ref _Data; + x = withBlock.x?.Name; + } + } +}"); + } +} From 83db1fd5f16bab93d18419f6c1abf106dc1cfb5c Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 14:24:17 +0000 Subject: [PATCH 05/16] Simplify --- .../CSharp/NameExpressionNodeVisitor.cs | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs index bb68567c..ec879e72 100644 --- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -693,31 +693,25 @@ private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpre while (parent != null) { + // On right hand side of a ?. conditional access + if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae && cae.WhenNotNull == child) + { + return true; + } + + // Recurse outwards through chained accesses if (parent.IsKind(VBasic.SyntaxKind.InvocationExpression, VBasic.SyntaxKind.SimpleMemberAccessExpression, - VBasic.SyntaxKind.ParenthesizedExpression)) + VBasic.SyntaxKind.ParenthesizedExpression, + VBasic.SyntaxKind.ConditionalAccessExpression)) { child = parent; parent = parent.Parent; - continue; } - - if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae) + else { - if (cae.WhenNotNull == child) - { - return true; - } - - if (cae.Expression == child) - { - child = parent; - parent = parent.Parent; - continue; - } + break; } - - break; } return false; From 63806cf42425655fd3f4d33c390399cdc5d02258 Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 14:30:51 +0000 Subject: [PATCH 06/16] Less cyclomatic complexity --- .../CSharp/NameExpressionNodeVisitor.cs | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs index ec879e72..1b39251f 100644 --- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -688,29 +688,15 @@ private static QualifiedNameSyntax Qualify(string qualification, ExpressionSynta private static bool IsSubPartOfConditionalAccess(VBasic.Syntax.MemberAccessExpressionSyntax node) { - SyntaxNode child = node; - SyntaxNode parent = node.Parent; - - while (parent != null) - { - // On right hand side of a ?. conditional access - if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae && cae.WhenNotNull == child) - { - return true; - } - - // Recurse outwards through chained accesses - if (parent.IsKind(VBasic.SyntaxKind.InvocationExpression, - VBasic.SyntaxKind.SimpleMemberAccessExpression, - VBasic.SyntaxKind.ParenthesizedExpression, - VBasic.SyntaxKind.ConditionalAccessExpression)) - { - child = parent; - parent = parent.Parent; - } - else - { - break; + static bool IsMemberAccessChain(SyntaxNode exp) => + exp?.IsKind(VBasic.SyntaxKind.InvocationExpression, + VBasic.SyntaxKind.SimpleMemberAccessExpression, + VBasic.SyntaxKind.ParenthesizedExpression, + VBasic.SyntaxKind.ConditionalAccessExpression) == true; + + for (SyntaxNode child = node, parent = node.Parent; IsMemberAccessChain(parent); child = parent, parent = parent.Parent) { + if (parent is VBSyntax.ConditionalAccessExpressionSyntax cae && cae.WhenNotNull == child) { + return true; // On right hand side of a ?. conditional access } } From ef65cf8b6e3d219fa875fa0871f1812274242dff Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:09:19 +0000 Subject: [PATCH 07/16] New slnx format uses forward slash --- .../Common/SolutionFileTextEditor.cs | 18 ++++++++++--- .../SolutionFileTextEditorTests.cs | 25 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CodeConverter/Common/SolutionFileTextEditor.cs b/CodeConverter/Common/SolutionFileTextEditor.cs index ba209af3..50981dce 100644 --- a/CodeConverter/Common/SolutionFileTextEditor.cs +++ b/CodeConverter/Common/SolutionFileTextEditor.cs @@ -15,9 +15,21 @@ public class SolutionFileTextEditor : ISolutionFileTextEditor var projectReferenceReplacements = new List<(string Find, string Replace, bool FirstOnly)>(); foreach (var relativeProjPath in relativeProjPaths) { - var escapedProjPath = Regex.Escape(relativeProjPath); - var newProjPath = PathConverter.TogglePathExtension(relativeProjPath); - projectReferenceReplacements.Add((escapedProjPath, newProjPath, false)); + // Add replacements for both backslash and forward-slash variants so .slnx files using either separator are handled + var nativeVariant = relativeProjPath; + var altVariant = relativeProjPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + // native (likely backslashes on Windows) + var escapedNative = Regex.Escape(nativeVariant); + var newNative = PathConverter.TogglePathExtension(nativeVariant); + projectReferenceReplacements.Add((escapedNative, newNative, false)); + + // alternate (forward slashes) + if (altVariant != nativeVariant) { + var escapedAlt = Regex.Escape(altVariant); + var newAlt = PathConverter.TogglePathExtension(altVariant); + projectReferenceReplacements.Add((escapedAlt, newAlt, false)); + } } return projectReferenceReplacements; diff --git a/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs b/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs index 6766e3f2..f502a06a 100644 --- a/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs +++ b/Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs @@ -58,6 +58,31 @@ public void ConvertSolutionFile_WhenInSolutionBaseDirThenUpdated() Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile)); } + [Fact] + public void ConvertSlnxSolutionFile_ProjectElementsWithForwardSlashesAreUpdated() + { + //Arrange + var slnxContents = "\r\n \r\n"; + var slnxSln = CreateTestSolution(@"C:\MySolution\MySolution.slnx"); + var projectId = ProjectId.CreateNewId(); + var projInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ConsoleApp1", "ConsoleApp1", + LanguageNames.VisualBasic, @"C:\MySolution\ConsoleApp1\ConsoleApp1.vbproj"); + slnxSln = slnxSln.AddProject(projInfo); + var testProject = slnxSln.GetProject(projectId); + + _fsMock.Setup(mock => mock.File.ReadAllText(It.IsAny())).Returns(""); + + var slnConverter = SolutionConverter.CreateFor(new List { testProject }, + fileSystem: _fsMock.Object, solutionContents: slnxContents); + + //Act + var convertedSlnFile = slnConverter.ConvertSolutionFile().ConvertedCode; + + //Assert + var expectedSlnFile = "\r\n \r\n"; + Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile)); + } + [Fact] public void ConvertSolutionFile_WhenInProjectFolderThenUpdated() { From d675467bac082042eb9c562dfc0fc08a1f50d97f Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:10:16 +0000 Subject: [PATCH 08/16] Try to force dotnet 8 for the vs2022 test run --- .github/workflows/dotnet-tests.yml | 9 ++++++++- Tests/Tests.csproj | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml index dee7375f..9b3a961e 100644 --- a/.github/workflows/dotnet-tests.yml +++ b/.github/workflows/dotnet-tests.yml @@ -20,6 +20,10 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Create global.json to enforce .NET 8 for MSBuild + if: inputs.dotnet-version == '8.0.x' + run: echo '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' > global.json + - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -33,8 +37,11 @@ jobs: - name: Log MSBuild version run: msbuild -version + - name: Log .NET version + run: dotnet --info + - name: Build run: dotnet build DotNetBuildable.slnf /p:Configuration=Release - name: Execute unit tests - run: dotnet test Tests/bin/Release/ICSharpCode.CodeConverter.Tests.dll + run: dotnet test Tests/Tests.csproj -c Release --framework net${{ inputs.dotnet-version == '8.0.x' && '8.0' || '10.0' }} --no-build diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index e1f42328..7ab69394 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,6 +1,6 @@ - net10.0 + net8.0;net10.0 Library ICSharpCode.CodeConverter.Tests ICSharpCode.CodeConverter.Tests From 0fb3e58467937a233d837a0d77714fc8729009ee Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:14:54 +0000 Subject: [PATCH 09/16] v10.0.1 + changelog --- .github/workflows/dotnet.yml | 2 +- CHANGELOG.md | 11 +++++++++++ Directory.Build.props | 6 +++--- Vsix/source.extension.vsixmanifest | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index c3bada0b..bef5811f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -7,7 +7,7 @@ on: branches: [ master, main ] env: - BuildVersion: '10.0.0' + BuildVersion: '10.0.1' jobs: build: diff --git a/CHANGELOG.md b/CHANGELOG.md index 21cfb598..1622181e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### C# -> VB +## [10.0.1] - 2026-02-28 + +* Reintroduce tentative legacy support for dotnet 8 and VS2022 +* Support slnx format [1195](https://github.com/icsharpcode/CodeConverter/issues/1195) + +### VB -> C# +* Fix for ReDim Preserve of array property - [#1156](https://github.com/icsharpcode/CodeConverter/issues/1156) +* Fix for with block conversion with null conditional [#1174](https://github.com/icsharpcode/CodeConverter/issues/1174) +Fixes #1195 + + ## [10.0.0] - 2026-02-06 * Support for net framework dropped. Please use an older version if you are converting projects that still use it. diff --git a/Directory.Build.props b/Directory.Build.props index 5b95257b..c048c3fe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,9 +8,9 @@ 4 true 14.0 - 10.0.0.0 - 10.0.0.0 - 10.0.0 + 10.0.1.0 + 10.0.1.0 + 10.0.1 ICSharpCode Copyright (c) 2017-2023 AlphaSierraPapa for the CodeConverter team ICSharpCode diff --git a/Vsix/source.extension.vsixmanifest b/Vsix/source.extension.vsixmanifest index aad6a73e..961647bf 100644 --- a/Vsix/source.extension.vsixmanifest +++ b/Vsix/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + Code Converter (VB - C#) Convert VB.NET to C# and vice versa with this roslyn based converter https://github.com/icsharpcode/CodeConverter From abcf0fdd5616a680e308507e9d46646de6022b91 Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:20:14 +0000 Subject: [PATCH 10/16] Append framework --- Tests/Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 7ab69394..d56ddf16 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,6 +1,7 @@ net8.0;net10.0 + true Library ICSharpCode.CodeConverter.Tests ICSharpCode.CodeConverter.Tests From 824022d154ff48057dc27ea40a74c34b22bc27ef Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:29:24 +0000 Subject: [PATCH 11/16] Build with net10 for slnx support, then force net8 after that --- .github/workflows/dotnet-tests.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml index 9b3a961e..5cd33a96 100644 --- a/.github/workflows/dotnet-tests.yml +++ b/.github/workflows/dotnet-tests.yml @@ -19,11 +19,6 @@ jobs: steps: - uses: actions/checkout@v4 - - - name: Create global.json to enforce .NET 8 for MSBuild - if: inputs.dotnet-version == '8.0.x' - run: echo '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' > global.json - - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -34,14 +29,18 @@ jobs: with: vs-version: ${{ inputs.vs-version }} + - name: Build + run: dotnet build DotNetBuildable.slnf /p:Configuration=Release + + - name: Create global.json to enforce .NET 8 for MSBuild + if: inputs.dotnet-version == '8.0.x' + run: echo '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' > global.json + - name: Log MSBuild version run: msbuild -version - name: Log .NET version run: dotnet --info - - name: Build - run: dotnet build DotNetBuildable.slnf /p:Configuration=Release - - name: Execute unit tests run: dotnet test Tests/Tests.csproj -c Release --framework net${{ inputs.dotnet-version == '8.0.x' && '8.0' || '10.0' }} --no-build From cf9510fbeff2cdf9215e1a2d8cca7b095b6acaa5 Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:31:56 +0000 Subject: [PATCH 12/16] Get rid of build error in visual studio --- web/web.esproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/web.esproj b/web/web.esproj index 7f0a5977..57639216 100644 --- a/web/web.esproj +++ b/web/web.esproj @@ -1,5 +1,7 @@ - + + false + false npm run dev src\ Vitest @@ -8,5 +10,4 @@ $(MSBuildProjectDirectory)\dist - \ No newline at end of file From 9197f23adc0354cc0745d16ca6d7b868943b5b3f Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 28 Feb 2026 15:33:26 +0000 Subject: [PATCH 13/16] Up one more directory --- Tests/TestConstants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TestConstants.cs b/Tests/TestConstants.cs index 4fa077c9..20bf60b8 100644 --- a/Tests/TestConstants.cs +++ b/Tests/TestConstants.cs @@ -19,7 +19,7 @@ public static class TestConstants public static string GetTestDataDirectory() { var assembly = Assembly.GetExecutingAssembly(); - var solutionDir = new FileInfo(new Uri(assembly.Location).LocalPath).Directory?.Parent?.Parent ?? + var solutionDir = new FileInfo(new Uri(assembly.Location).LocalPath).Directory?.Parent?.Parent?.Parent ?? throw new InvalidOperationException(assembly.Location); return Path.Combine(solutionDir.FullName, "TestData"); } From fa3136f704dec4a38e4f141f7f2dd0ba618401d7 Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 1 Mar 2026 12:29:43 +0000 Subject: [PATCH 14/16] The test projects won't restore under dotnet 10 --- .github/workflows/dotnet-tests.yml | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml index 5cd33a96..4dab523c 100644 --- a/.github/workflows/dotnet-tests.yml +++ b/.github/workflows/dotnet-tests.yml @@ -32,9 +32,37 @@ jobs: - name: Build run: dotnet build DotNetBuildable.slnf /p:Configuration=Release - - name: Create global.json to enforce .NET 8 for MSBuild + - name: Create global.json to enforce .NET 8 for MSBuild and update Tests/TestData projects if: inputs.dotnet-version == '8.0.x' - run: echo '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' > global.json + shell: pwsh + run: | + # ensure .NET 8 SDK is used for MSBuild operations + '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' | Out-File -FilePath global.json -Encoding utf8 + Write-Host "Updating net10.0... entries to net8.0 under Tests/TestData" + + # Replace net10.0 and net10.0-windows with net8.0 / net8.0-windows respectively + $projFiles = Get-ChildItem -Path Tests/TestData -Recurse -Include *.csproj,*.vbproj,*.fsproj -ErrorAction SilentlyContinue + foreach ($f in $projFiles) { + $path = $f.FullName + $content = Get-Content -Path $path -Raw -ErrorAction Stop + $updated = $content -replace 'net10.0(-windows)?', 'net8.0$1' + if ($updated -ne $content) { + Write-Host "Updating: $path" + $updated | Set-Content -Path $path -Encoding utf8 + } + } + + # If any files were changed, commit them so the working tree is clean for subsequent test steps. + $status = git status --porcelain + if ($status) { + git config user.name "github-actions[bot]" + git config user.email "${{ github.actor }}@users.noreply.github.com" + git add -A + git commit -m "CI: Update Tests/TestData TargetFramework -> net8.0 for .NET 8 run" || Write-Host "git commit returned non-zero (possibly no staged changes)" + Write-Host "Committed changes to local repo (not pushed)." + } else { + Write-Host "No project files needed updating." + } - name: Log MSBuild version run: msbuild -version From dc3f903fe742c7a1795eb5e2b848d2ea4f416c07 Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 1 Mar 2026 12:30:53 +0000 Subject: [PATCH 15/16] Consistent order --- Tests/Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index d56ddf16..f25cb7b9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,6 +1,6 @@ - + - net8.0;net10.0 + net10.0;net8.0 true Library ICSharpCode.CodeConverter.Tests From df7b89ed7dfb72139dce110b300fd6714a3bdc53 Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 1 Mar 2026 13:07:48 +0000 Subject: [PATCH 16/16] Split script out and try to take care over messing with ending newlines --- .../update-testdata-targetframework.ps1 | 57 +++++++++++++++++++ .github/workflows/dotnet-tests.yml | 29 +--------- 2 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 .github/scripts/update-testdata-targetframework.ps1 diff --git a/.github/scripts/update-testdata-targetframework.ps1 b/.github/scripts/update-testdata-targetframework.ps1 new file mode 100644 index 00000000..67f6ef00 --- /dev/null +++ b/.github/scripts/update-testdata-targetframework.ps1 @@ -0,0 +1,57 @@ +param() + +$ErrorActionPreference = 'Stop' + +Write-Host "Creating global.json to enforce .NET 8 for MSBuild" +$globalJson = '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' +[System.IO.File]::WriteAllText('global.json', $globalJson, [System.Text.Encoding]::UTF8) + +Write-Host "Searching for project files under Tests/TestData..." +$projFiles = Get-ChildItem -Path Tests/TestData -Recurse -Include *.csproj,*.vbproj,*.fsproj -File -ErrorAction SilentlyContinue + +if (-not $projFiles) { + Write-Host "No project files found under Tests/TestData" + exit 0 +} + +$changed = $false +foreach ($f in $projFiles) { + $path = $f.FullName + Write-Host "Processing: $path" + + # Use StreamReader to detect encoding and preserve it when writing back + $sr = [System.IO.StreamReader]::new($path, $true) + try { + $content = $sr.ReadToEnd() + $encoding = $sr.CurrentEncoding + } finally { + $sr.Close() + } + + # Replace net10.0 and net10.0-windows with net8.0 / net8.0-windows + $updated = [System.Text.RegularExpressions.Regex]::Replace($content, 'net10\.0(-windows)?', 'net8.0$1') + + if ($updated -ne $content) { + Write-Host "Updating TargetFramework in: $path" + # Write back preserving detected encoding and internal newlines + [System.IO.File]::WriteAllText($path, $updated, $encoding) + $changed = $true + } +} + +if ($changed) { + Write-Host "Changes detected — committing to local repo so working tree is clean for tests" + git config user.name "github-actions[bot]" + if ($env:GITHUB_ACTOR) { + git config user.email "$($env:GITHUB_ACTOR)@users.noreply.github.com" + } else { + git config user.email "actions@github.com" + } + git add -A + git commit -m "CI: Update Tests/TestData TargetFramework -> net8.0 for .NET 8 run" || Write-Host "No commit created (maybe no staged changes)" + Write-Host "Committed changes locally." +} else { + Write-Host "No TargetFramework updates required." +} + +Write-Host "Done." diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml index 4dab523c..0fe9bc6f 100644 --- a/.github/workflows/dotnet-tests.yml +++ b/.github/workflows/dotnet-tests.yml @@ -35,34 +35,7 @@ jobs: - name: Create global.json to enforce .NET 8 for MSBuild and update Tests/TestData projects if: inputs.dotnet-version == '8.0.x' shell: pwsh - run: | - # ensure .NET 8 SDK is used for MSBuild operations - '{"sdk":{"version":"8.0.0","rollForward":"latestFeature"}}' | Out-File -FilePath global.json -Encoding utf8 - Write-Host "Updating net10.0... entries to net8.0 under Tests/TestData" - - # Replace net10.0 and net10.0-windows with net8.0 / net8.0-windows respectively - $projFiles = Get-ChildItem -Path Tests/TestData -Recurse -Include *.csproj,*.vbproj,*.fsproj -ErrorAction SilentlyContinue - foreach ($f in $projFiles) { - $path = $f.FullName - $content = Get-Content -Path $path -Raw -ErrorAction Stop - $updated = $content -replace 'net10.0(-windows)?', 'net8.0$1' - if ($updated -ne $content) { - Write-Host "Updating: $path" - $updated | Set-Content -Path $path -Encoding utf8 - } - } - - # If any files were changed, commit them so the working tree is clean for subsequent test steps. - $status = git status --porcelain - if ($status) { - git config user.name "github-actions[bot]" - git config user.email "${{ github.actor }}@users.noreply.github.com" - git add -A - git commit -m "CI: Update Tests/TestData TargetFramework -> net8.0 for .NET 8 run" || Write-Host "git commit returned non-zero (possibly no staged changes)" - Write-Host "Committed changes to local repo (not pushed)." - } else { - Write-Host "No project files needed updating." - } + run: ./.github/scripts/update-testdata-targetframework.ps1 - name: Log MSBuild version run: msbuild -version