Skip to content

Commit 8ad56c8

Browse files
author
Zubin Ramlakhan
committed
Added UT's
Split project + sln reference replacements Replacements regexes now match on relativeprojpath
1 parent 2adc9fa commit 8ad56c8

File tree

7 files changed

+166
-70
lines changed

7 files changed

+166
-70
lines changed

CodeConverter/CodeConverter.csproj

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
<AssemblyName>ICSharpCode.CodeConverter</AssemblyName>
55
<RootNamespace>ICSharpCode.CodeConverter</RootNamespace>
66
<Company>ICSharpCode</Company>
7-
<Description>Convert VB.NET to/from C#
8-
* Accurate: Full project context (through Roslyn) is used to get the most accurate conversion.
9-
* Actively developed: User feedback helps us continuously strive for a more accurate conversion.
10-
* Completely free and open source
11-
This nuget package is useful if you're building a tool which converts code, or if you want finer-grained control over what's converted.
12-
See https://github.com/icsharpcode/CodeConverter for a pre-built command line, visual studio extension and web converter.
13-
</Description>
7+
<Description>
8+
Convert VB.NET to/from C#
9+
* Accurate: Full project context (through Roslyn) is used to get the most accurate conversion.
10+
* Actively developed: User feedback helps us continuously strive for a more accurate conversion.
11+
* Completely free and open source
12+
This nuget package is useful if you're building a tool which converts code, or if you want finer-grained control over what's converted.
13+
See https://github.com/icsharpcode/CodeConverter for a pre-built command line, visual studio extension and web converter.
14+
</Description>
1415
<Product>Code Converter for C# to/from VB.NET</Product>
1516
<Copyright>Copyright (c) 2017-2020 AlphaSierraPapa for the CodeConverter team</Copyright>
1617
<AssemblyVersion>8.2.3.0</AssemblyVersion>
@@ -46,6 +47,7 @@ See https://github.com/icsharpcode/CodeConverter for a pre-built command line, v
4647
<PackageReference Include="Microsoft.VisualStudio.Composition" Version="15.8.98" />
4748
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.8.122" />
4849
<PackageReference Include="System.Globalization.Extensions" Version="4.3.0" />
50+
<PackageReference Include="System.IO.Abstractions" Version="13.2.33" />
4951
<PackageReference Include="System.Linq.Async" Version="4.0.0" />
5052
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.6.0">
5153
<IncludeAssets>all</IncludeAssets>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace ICSharpCode.CodeConverter.Shared
2+
{
3+
using System.Collections.Generic;
4+
5+
public interface ISolutionFileTextEditor
6+
{
7+
List<(string Find, string Replace, bool FirstOnly)> GetProjectFileProjectReferenceReplacements(
8+
IEnumerable<(string Name, string RelativeProjPath, string ProjContents)> projTuples, string sourceSolutionContents);
9+
}
10+
}

CodeConverter/Shared/PathConverter.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23

34
namespace ICSharpCode.CodeConverter.Shared
45
{
@@ -33,12 +34,12 @@ private static string GetConvertedExtension(string originalExtension)
3334
}
3435
}
3536

36-
public static string GetFileDirPath(string fileName, string dirPath)
37+
public static string GetRelativePath(string relativeTo, string path)
3738
{
38-
var dirName = Path.GetFileName(dirPath);
39-
var fileDirPath = Path.Combine(dirName ?? string.Empty, fileName);
39+
var uri = new Uri(relativeTo);
40+
var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
4041

41-
return fileDirPath;
42+
return rel;
4243
}
4344
}
4445
}

CodeConverter/Shared/ProjectConversion.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
using ICSharpCode.CodeConverter.CSharp;
1111
using ICSharpCode.CodeConverter.Util;
1212
using Microsoft.CodeAnalysis;
13-
using Microsoft.CodeAnalysis.Text;
14-
1513
namespace ICSharpCode.CodeConverter.Shared
1614
{
15+
using System.IO.Abstractions;
16+
using Microsoft.CodeAnalysis.Text;
17+
1718
public class ProjectConversion
1819
{
1920
private readonly IProjectContentsConverter _projectContentsConverter;
@@ -22,7 +23,7 @@ public class ProjectConversion
2223
private readonly ILanguageConversion _languageConversion;
2324
private readonly bool _showCompilationErrors;
2425
private readonly bool _returnSelectedNode;
25-
private static readonly string[] BannedPaths = new[] { ".AssemblyAttributes.", "\\bin\\", "\\obj\\" };
26+
private static readonly string[] BannedPaths = { ".AssemblyAttributes.", "\\bin\\", "\\obj\\" };
2627
private readonly CancellationToken _cancellationToken;
2728

2829
private ProjectConversion(IProjectContentsConverter projectContentsConverter, IEnumerable<Document> documentsToConvert, IEnumerable<TextDocument> additionalDocumentsToConvert,

CodeConverter/Shared/SolutionConverter.cs

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5-
using System.Text.RegularExpressions;
65
using System.Threading;
7-
using ICSharpCode.CodeConverter.CSharp;
86
using ICSharpCode.CodeConverter.Util;
97
using Microsoft.CodeAnalysis;
8+
using System.IO.Abstractions;
109

1110
namespace ICSharpCode.CodeConverter.Shared
1211
{
@@ -18,32 +17,58 @@ public class SolutionConverter
1817
private readonly List<(string Find, string Replace, bool FirstOnly)> _projectReferenceReplacements;
1918
private readonly IProgress<ConversionProgress> _progress;
2019
private readonly ILanguageConversion _languageConversion;
20+
private readonly SolutionFileTextEditor _solutionFileTextEditor;
2121
private readonly CancellationToken _cancellationToken;
2222

23+
public static IFileSystem FileSystem { get; set; } = new FileSystem();
24+
25+
public static SolutionConverter CreateFor<TLanguageConversion>(IReadOnlyCollection<Project> projectsToConvert, string sourceSolutionContents)
26+
where TLanguageConversion : ILanguageConversion, new()
27+
{
28+
return CreateFor<TLanguageConversion>(projectsToConvert, solutionContents: sourceSolutionContents);
29+
}
30+
2331
public static SolutionConverter CreateFor<TLanguageConversion>(IReadOnlyCollection<Project> projectsToConvert,
2432
ConversionOptions conversionOptions = default,
2533
IProgress<ConversionProgress> progress = null,
26-
CancellationToken cancellationToken = default) where TLanguageConversion : ILanguageConversion, new()
34+
CancellationToken cancellationToken = default,
35+
string solutionContents = "") where TLanguageConversion : ILanguageConversion, new()
2736
{
2837
var conversion = new TLanguageConversion { ConversionOptions = conversionOptions };
29-
return CreateFor(conversion, projectsToConvert, progress, cancellationToken);
38+
return CreateFor(conversion, projectsToConvert, progress, cancellationToken, solutionContents);
3039
}
3140

3241
public static SolutionConverter CreateFor(ILanguageConversion languageConversion, IReadOnlyCollection<Project> projectsToConvert,
3342
IProgress<ConversionProgress> progress,
34-
CancellationToken cancellationToken)
43+
CancellationToken cancellationToken, string solutionContents = "")
3544
{
3645
languageConversion.ConversionOptions ??= new ConversionOptions();
3746
var solutionFilePath = projectsToConvert.First().Solution.FilePath;
38-
var sourceSolutionContents = File.Exists(solutionFilePath) ? File.ReadAllText(solutionFilePath) : "";
39-
var projectReferenceReplacements = GetProjectReferenceReplacements(projectsToConvert, sourceSolutionContents);
40-
return new SolutionConverter(solutionFilePath, sourceSolutionContents, projectsToConvert, projectReferenceReplacements, progress ?? new Progress<ConversionProgress>(), cancellationToken, languageConversion);
47+
var sourceSolutionContents = File.Exists(solutionFilePath)
48+
? FileSystem.File.ReadAllText(solutionFilePath)
49+
: solutionContents;
50+
51+
var projTuples = projectsToConvert.Select(proj =>
52+
{
53+
var relativeProjPath = PathConverter.GetRelativePath(solutionFilePath, proj.FilePath);
54+
var projContents = FileSystem.File.ReadAllText(proj.FilePath);
55+
56+
return (proj.Name, RelativeProjPath: relativeProjPath, ProjContents: projContents);
57+
});
58+
59+
var solutionFileTextEditor = new SolutionFileTextEditor();
60+
var projectReferenceReplacements = solutionFileTextEditor.GetProjectFileProjectReferenceReplacements(projTuples, sourceSolutionContents);
61+
62+
return new SolutionConverter(solutionFilePath, sourceSolutionContents, projectsToConvert, projectReferenceReplacements,
63+
progress ?? new Progress<ConversionProgress>(), cancellationToken, languageConversion, solutionFileTextEditor);
4164
}
4265

4366
private SolutionConverter(string solutionFilePath,
4467
string sourceSolutionContents, IReadOnlyCollection<Project> projectsToConvert,
45-
List<(string Find, string Replace, bool FirstOnly)> projectReferenceReplacements, IProgress<ConversionProgress> showProgressMessage,
46-
CancellationToken cancellationToken, ILanguageConversion languageConversion)
68+
List<(string Find, string Replace, bool FirstOnly)> projectReferenceReplacements,
69+
IProgress<ConversionProgress> showProgressMessage,
70+
CancellationToken cancellationToken, ILanguageConversion languageConversion,
71+
SolutionFileTextEditor solutionFileTextEditor)
4772
{
4873
_solutionFilePath = solutionFilePath;
4974
_sourceSolutionContents = sourceSolutionContents;
@@ -52,6 +77,7 @@ private SolutionConverter(string solutionFilePath,
5277
_progress = showProgressMessage;
5378
_languageConversion = languageConversion;
5479
_cancellationToken = cancellationToken;
80+
_solutionFileTextEditor = solutionFileTextEditor;
5581
}
5682

5783
public IAsyncEnumerable<ConversionResult> Convert()
@@ -76,34 +102,32 @@ private IAsyncEnumerable<ConversionResult> ConvertProject(Project project)
76102

77103
private IEnumerable<ConversionResult> UpdateProjectReferences(IEnumerable<Project> projectsToUpdateReferencesOnly)
78104
{
79-
return projectsToUpdateReferencesOnly
80-
.Where(p => p.FilePath != null) //Some project types like Websites don't have a project file
81-
.Select(project => {
105+
var conversionResults = projectsToUpdateReferencesOnly
106+
.Where(p => p.FilePath != null) //Some project types like Websites don't have a project file
107+
.Select(project => {
82108
var withReferencesReplaced =
83109
new FileInfo(project.FilePath).ConversionResultFromReplacements(_projectReferenceReplacements);
84110
withReferencesReplaced.TargetPathOrNull = withReferencesReplaced.SourcePathOrNull;
85111
return withReferencesReplaced;
86-
}).Where(c => !c.IsIdentity);
87-
}
112+
});
88113

89-
private static List<(string Find, string Replace, bool FirstOnly)> GetProjectReferenceReplacements(IReadOnlyCollection<Project> projectsToConvert, string sourceSolutionContents) =>
90-
SolutionFileTextEditor.GetProjectReferenceReplacements(projectsToConvert.Select(p => (FilePath: p.FilePath, DirectoryPath: p.GetDirectoryPath())), sourceSolutionContents).ToList();
114+
return conversionResults.Where(c => !c.IsIdentity);
115+
}
91116

92-
private ConversionResult ConvertSolutionFile()
117+
public ConversionResult ConvertSolutionFile()
93118
{
94119
var projectTypeGuidMappings = _languageConversion.GetProjectTypeGuidMappings();
95-
var projectTypeReplacements = _projectsToConvert.SelectMany(project => GetProjectTypeReplacement(project, projectTypeGuidMappings)).ToList();
120+
var relativeProjPaths = _projectsToConvert.Select(proj =>
121+
(proj.Name, RelativeProjPath: PathConverter.GetRelativePath(_solutionFilePath, proj.FilePath)));
122+
123+
var slnProjectReferenceReplacements = _solutionFileTextEditor.GetSolutionFileProjectReferenceReplacements(relativeProjPaths,
124+
_sourceSolutionContents, projectTypeGuidMappings);
96125

97-
var convertedSolutionContents = _sourceSolutionContents.Replace(_projectReferenceReplacements.Concat(projectTypeReplacements));
126+
var convertedSolutionContents = _sourceSolutionContents.Replace(slnProjectReferenceReplacements);
98127
return new ConversionResult(convertedSolutionContents) {
99128
SourcePathOrNull = _solutionFilePath,
100129
TargetPathOrNull = _solutionFilePath
101130
};
102131
}
103-
104-
private IEnumerable<(string, string, bool)> GetProjectTypeReplacement(Project project, IReadOnlyCollection<(string, string)> typeGuidMappings)
105-
{
106-
return typeGuidMappings.Select(guidReplacement => ($@"Project\s*\(\s*""{guidReplacement.Item1}""\s*\)\s*=\s*""{project.Name}""", $@"Project(""{guidReplacement.Item2}"") = ""{project.Name}""", false));
107-
}
108132
}
109133
}
Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,91 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Text.RegularExpressions;
6-
7-
namespace ICSharpCode.CodeConverter.Shared
1+
namespace ICSharpCode.CodeConverter.Shared
82
{
9-
public class SolutionFileTextEditor
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text.RegularExpressions;
7+
8+
public class SolutionFileTextEditor : ISolutionFileTextEditor
109
{
11-
public static IEnumerable<(string Find, string Replace, bool FirstOnly)> GetProjectReferenceReplacements(IEnumerable<(string FilePath, string DirectoryPath)> projectsToConvert, string sourceSolutionContents)
10+
public List<(string Find, string Replace, bool FirstOnly)> GetSolutionFileProjectReferenceReplacements(
11+
IEnumerable<(string Name, string RelativeProjPath)> projTuples, string sourceSolutionContents,
12+
IReadOnlyCollection<(string, string)> projTypeGuidMappings)
1213
{
13-
foreach (var (projFilePath, projDirPath) in projectsToConvert) {
14-
var projFilename = Path.GetFileName(projFilePath);
14+
if (string.IsNullOrWhiteSpace(sourceSolutionContents)) return new List<(string Find, string Replace, bool FirstOnly)>();
15+
16+
var projectReferenceReplacements = new List<(string Find, string Replace, bool FirstOnly)>();
17+
foreach ((string projName, string relativeProjPath) in projTuples)
18+
{
19+
var newProjPath = @"""" + PathConverter.TogglePathExtension(relativeProjPath) + @""", ";
20+
var projPathEscaped = @"""" + Regex.Escape(relativeProjPath);
1521

16-
var newProjFilename = PathConverter.TogglePathExtension(projFilename);
22+
(string oldType, string newType) = GetProjectTypeReplacement(projTypeGuidMappings, projName, projPathEscaped,
23+
sourceSolutionContents);
24+
(string oldGuid, string newGuid, bool firstOnly) = GetProjectGuidReplacement(projPathEscaped, sourceSolutionContents);
1725

18-
var projPath = PathConverter.GetFileDirPath(projFilename, projDirPath);
19-
var newProjPath = PathConverter.GetFileDirPath(newProjFilename, projDirPath);
26+
var oldProjRefReplacement = oldType + projPathEscaped + @""", """ + oldGuid + @"""";
27+
var newProjRefReplacement = newType + newProjPath + @"""" + newGuid + @"""";
2028

21-
var projPathEscaped = Regex.Escape(projPath);
29+
projectReferenceReplacements.Add((oldProjRefReplacement, newProjRefReplacement, false));
2230

23-
yield return (projPathEscaped, newProjPath, false);
24-
if (!string.IsNullOrWhiteSpace(sourceSolutionContents) && GetProjectGuidReplacement(projPathEscaped, sourceSolutionContents) is { } replacement) yield return replacement;
31+
// this is needed for the guid replacement in the SolutionConfigurationPlatforms GlobalSection
32+
projectReferenceReplacements.Add((oldGuid, newGuid, firstOnly));
2533
}
34+
35+
return projectReferenceReplacements;
2636
}
2737

28-
private static (string Find, string Replace, bool FirstOnly)? GetProjectGuidReplacement(string projPath, string contents)
38+
public List<(string Find, string Replace, bool FirstOnly)> GetProjectFileProjectReferenceReplacements(
39+
IEnumerable<(string Name, string RelativeProjPath, string ProjContents)> projTuples, string sourceSolutionContents)
40+
{
41+
var projectReferenceReplacements = new List<(string Find, string Replace, bool FirstOnly)>();
42+
foreach ((string _, string relativeProjPath, string projContents) in projTuples)
43+
{
44+
var escapedProjPath = Regex.Escape(relativeProjPath);
45+
var projRefRegex = new Regex(@"(\\|"")" + escapedProjPath);
46+
47+
var projRefMatch = projRefRegex.Match(projContents);
48+
var characterBeforePath = projRefMatch.Groups[1].Value;
49+
50+
var extendedEscProjPath = Regex.Escape(characterBeforePath) + escapedProjPath;
51+
var newProjPath = characterBeforePath + PathConverter.TogglePathExtension(relativeProjPath);
52+
53+
projectReferenceReplacements.Add((extendedEscProjPath, newProjPath, false));
54+
if (string.IsNullOrWhiteSpace(sourceSolutionContents)) {
55+
continue;
56+
}
57+
58+
projectReferenceReplacements.Add(GetProjectGuidReplacement(escapedProjPath, sourceSolutionContents));
59+
}
60+
61+
return projectReferenceReplacements;
62+
}
63+
64+
private static (string oldProjTypeReference, string newProjTypeReference) GetProjectTypeReplacement(
65+
IEnumerable<(string oldTypeGuid, string newTypeGuid)> projTypeGuidMappings, string projName, string projPath,
66+
string contents)
67+
{
68+
foreach ((string oldTypeGuid, string newTypeGuid) in projTypeGuidMappings)
69+
{
70+
var oldProjTypeReference = $@"Project\s*\(\s*""{oldTypeGuid}""\s*\)\s*=\s*""{projName}"", ";
71+
var projGuidRegex = new Regex($@"({oldProjTypeReference})({projPath})");
72+
var projTypeReference = projGuidRegex.Match(contents);
73+
if(projTypeReference.Groups.Count == 0) continue;
74+
75+
var oldReference = Regex.Escape(projTypeReference.Groups[1].Value);
76+
var newProjTypeReference = $@"Project(""{newTypeGuid}"") = ""{projName}"", ";
77+
78+
return (oldReference, newProjTypeReference);
79+
}
80+
81+
return default;
82+
}
83+
84+
private static (string Find, string Replace, bool FirstOnly) GetProjectGuidReplacement(string projPath,
85+
string contents)
2986
{
3087
var projGuidRegex = new Regex(projPath + @""", ""({[0-9A-Fa-f\-]{32,36}})("")");
3188
var projGuidMatch = projGuidRegex.Match(contents);
32-
if (!projGuidMatch.Success) return null;
33-
3489
var oldGuid = projGuidMatch.Groups[1].Value;
3590
var newGuid = GetDeterministicGuidFrom(new Guid(oldGuid));
3691
return (oldGuid, newGuid.ToString("B").ToUpperInvariant(), false);
@@ -44,4 +99,4 @@ private static Guid GetDeterministicGuidFrom(Guid guidToConvert)
4499
return new Guid(deterministicNewBytes.ToArray());
45100
}
46101
}
47-
}
102+
}

0 commit comments

Comments
 (0)