Skip to content

Commit 4235dd7

Browse files
Merge pull request #1220 from icsharpcode/issue/1212
First effort to support slnx
2 parents 3681f83 + 7be90d6 commit 4235dd7

File tree

4 files changed

+92
-11
lines changed

4 files changed

+92
-11
lines changed

CodeConv/CodeConvProgram.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ private async Task ConvertAsync(IProgress<ConversionProgress> progress, Cancella
105105

106106
IProgress<string> strProgress = new Progress<string>(p => progress.Report(new ConversionProgress(p)));
107107

108-
if (!string.Equals(Path.GetExtension(finalSolutionPath), ".sln", StringComparison.OrdinalIgnoreCase)) {
109-
throw new ValidationException("Solution path must end in `.sln`");
108+
var ext = Path.GetExtension(finalSolutionPath);
109+
if (!string.Equals(ext, ".sln", StringComparison.OrdinalIgnoreCase) &&
110+
!string.Equals(ext, ".slnx", StringComparison.OrdinalIgnoreCase)) {
111+
throw new ValidationException("Solution path must end in `.sln` or `.slnx`");
110112
}
111113

112114
string? directoryName = string.IsNullOrWhiteSpace(OutputDirectory) ? Path.GetDirectoryName(finalSolutionPath) : OutputDirectory;

CodeConverter/Common/SolutionConverter.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ public static SolutionConverter CreateFor(ILanguageConversion languageConversion
4343
return (proj.Name, RelativeProjPath: relativeProjPath, ProjContents: projContents);
4444
});
4545

46+
// .slnx files have no project GUIDs - skip the GUID lookup by passing empty solution contents
47+
var isSlnx = string.Equals(Path.GetExtension(solutionFilePath), ".slnx", StringComparison.OrdinalIgnoreCase);
4648
var solutionFileTextEditor = new SolutionFileTextEditor();
47-
var projectReferenceReplacements = solutionFileTextEditor.GetProjectFileProjectReferenceReplacements(projTuples, sourceSolutionContents);
49+
var projectReferenceReplacements = solutionFileTextEditor.GetProjectFileProjectReferenceReplacements(projTuples, isSlnx ? "" : sourceSolutionContents);
4850

4951
return new SolutionConverter(solutionFilePath, sourceSolutionContents, projectsToConvert, projectReferenceReplacements, languageConversion, fileSystem, progress ?? new Progress<ConversionProgress>(), cancellationToken);
5052
}
@@ -108,12 +110,20 @@ private IEnumerable<ConversionResult> UpdateProjectReferences(IEnumerable<Projec
108110

109111
public ConversionResult ConvertSolutionFile()
110112
{
111-
var projectTypeGuidMappings = _languageConversion.GetProjectTypeGuidMappings();
112-
var relativeProjPaths = _projectsToConvert.Select(proj =>
113-
(proj.Name, RelativeProjPath: PathConverter.GetRelativePath(_solutionFilePath, proj.FilePath)));
114-
115-
var slnProjectReferenceReplacements = SolutionFileTextEditor.GetSolutionFileProjectReferenceReplacements(relativeProjPaths,
116-
_sourceSolutionContents, projectTypeGuidMappings);
113+
IEnumerable<(string Find, string Replace, bool FirstOnly)> slnProjectReferenceReplacements;
114+
115+
if (string.Equals(Path.GetExtension(_solutionFilePath), ".slnx", StringComparison.OrdinalIgnoreCase)) {
116+
var relativeProjPaths = _projectsToConvert.Select(proj =>
117+
PathConverter.GetRelativePath(_solutionFilePath, proj.FilePath));
118+
slnProjectReferenceReplacements = SolutionFileTextEditor.GetSlnxSolutionFileProjectReferenceReplacements(
119+
relativeProjPaths, _sourceSolutionContents);
120+
} else {
121+
var projectTypeGuidMappings = _languageConversion.GetProjectTypeGuidMappings();
122+
var relativeProjPaths = _projectsToConvert.Select(proj =>
123+
(proj.Name, RelativeProjPath: PathConverter.GetRelativePath(_solutionFilePath, proj.FilePath)));
124+
slnProjectReferenceReplacements = SolutionFileTextEditor.GetSolutionFileProjectReferenceReplacements(
125+
relativeProjPaths, _sourceSolutionContents, projectTypeGuidMappings);
126+
}
117127

118128
var convertedSolutionContents = TextReplacementConverter.Replace(_sourceSolutionContents, slnProjectReferenceReplacements);
119129
return new ConversionResult(convertedSolutionContents) {

CodeConverter/Common/SolutionFileTextEditor.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ namespace ICSharpCode.CodeConverter.Common;
44

55
public class SolutionFileTextEditor : ISolutionFileTextEditor
66
{
7+
/// <summary>
8+
/// For .slnx files: simply replaces project path extensions (no GUIDs or project type GUIDs exist in .slnx).
9+
/// </summary>
10+
public static List<(string Find, string Replace, bool FirstOnly)> GetSlnxSolutionFileProjectReferenceReplacements(
11+
IEnumerable<string> relativeProjPaths, string sourceSolutionContents)
12+
{
13+
if (string.IsNullOrWhiteSpace(sourceSolutionContents)) return new List<(string Find, string Replace, bool FirstOnly)>();
14+
15+
var projectReferenceReplacements = new List<(string Find, string Replace, bool FirstOnly)>();
16+
foreach (var relativeProjPath in relativeProjPaths)
17+
{
18+
var escapedProjPath = Regex.Escape(relativeProjPath);
19+
var newProjPath = PathConverter.TogglePathExtension(relativeProjPath);
20+
projectReferenceReplacements.Add((escapedProjPath, newProjPath, false));
21+
}
22+
23+
return projectReferenceReplacements;
24+
}
25+
726
public static List<(string Find, string Replace, bool FirstOnly)> GetSolutionFileProjectReferenceReplacements(
827
IEnumerable<(string Name, string RelativeProjPath)> projTuples, string sourceSolutionContents,
928
IReadOnlyCollection<(string, string)> projTypeGuidMappings)

Tests/LanguageAgnostic/SolutionFileTextEditorTests.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,15 +552,15 @@ private static string GetProjectProjectReference(IReadOnlyCollection<string> rel
552552
return Utils.HomogenizeEol(referenceString);
553553
}
554554

555-
private static Solution CreateTestSolution()
555+
private static Solution CreateTestSolution(string filePath = SlnFilePath)
556556
{
557557
var ws = Task.Run(() => ThreadSafeWorkspaceHelper.CreateAdhocWorkspace.GetValueAsync())
558558
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - the tests don't deadlock
559559
.GetAwaiter().GetResult();
560560
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
561561
var solutionId = SolutionId.CreateNewId(SlnName);
562562
var versionStamp = VersionStamp.Create();
563-
var solutionInfo = SolutionInfo.Create(solutionId, versionStamp, SlnFilePath);
563+
var solutionInfo = SolutionInfo.Create(solutionId, versionStamp, filePath);
564564

565565
return ws.AddSolution(solutionInfo);
566566
}
@@ -589,4 +589,54 @@ private static async Task<string> GetConvertedCodeAsync(SolutionConverter slnCon
589589

590590
return conversionResult.ConvertedCode;
591591
}
592+
593+
[Fact]
594+
public void ConvertSlnxSolutionFile_WhenInSolutionBaseDirThenUpdated()
595+
{
596+
//Arrange
597+
var slnxContents = "<Solution>\r\n <Project Path=\"VbLibrary.vbproj\" />\r\n</Solution>";
598+
var slnxSln = CreateTestSolution(@"C:\MySolution\MySolution.slnx");
599+
var projectId = ProjectId.CreateNewId();
600+
var projInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "VbLibrary", "VbLibrary",
601+
LanguageNames.VisualBasic, @"C:\MySolution\VbLibrary.vbproj");
602+
slnxSln = slnxSln.AddProject(projInfo);
603+
var testProject = slnxSln.GetProject(projectId);
604+
605+
_fsMock.Setup(mock => mock.File.ReadAllText(It.IsAny<string>())).Returns("");
606+
607+
var slnConverter = SolutionConverter.CreateFor<VBToCSConversion>(new List<Project> { testProject },
608+
fileSystem: _fsMock.Object, solutionContents: slnxContents);
609+
610+
//Act
611+
var convertedSlnFile = slnConverter.ConvertSolutionFile().ConvertedCode;
612+
613+
//Assert
614+
var expectedSlnFile = "<Solution>\r\n <Project Path=\"VbLibrary.csproj\" />\r\n</Solution>";
615+
Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile));
616+
}
617+
618+
[Fact]
619+
public void ConvertSlnxSolutionFile_WhenInProjectFolderThenUpdated()
620+
{
621+
//Arrange
622+
var slnxContents = "<Solution>\r\n <Project Path=\"VbLibrary\\VbLibrary.vbproj\" />\r\n</Solution>";
623+
var slnxSln = CreateTestSolution(@"C:\MySolution\MySolution.slnx");
624+
var projectId = ProjectId.CreateNewId();
625+
var projInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "VbLibrary", "VbLibrary",
626+
LanguageNames.VisualBasic, @"C:\MySolution\VbLibrary\VbLibrary.vbproj");
627+
slnxSln = slnxSln.AddProject(projInfo);
628+
var testProject = slnxSln.GetProject(projectId);
629+
630+
_fsMock.Setup(mock => mock.File.ReadAllText(It.IsAny<string>())).Returns("");
631+
632+
var slnConverter = SolutionConverter.CreateFor<VBToCSConversion>(new List<Project> { testProject },
633+
fileSystem: _fsMock.Object, solutionContents: slnxContents);
634+
635+
//Act
636+
var convertedSlnFile = slnConverter.ConvertSolutionFile().ConvertedCode;
637+
638+
//Assert
639+
var expectedSlnFile = "<Solution>\r\n <Project Path=\"VbLibrary\\VbLibrary.csproj\" />\r\n</Solution>";
640+
Assert.Equal(expectedSlnFile, Utils.HomogenizeEol(convertedSlnFile));
641+
}
592642
}

0 commit comments

Comments
 (0)