diff --git a/src/Cli/dotnet/SlnFileFactory.cs b/src/Cli/dotnet/SlnFileFactory.cs index 788752df3cff..fe8a73f6d2d0 100644 --- a/src/Cli/dotnet/SlnFileFactory.cs +++ b/src/Cli/dotnet/SlnFileFactory.cs @@ -89,7 +89,12 @@ public static SolutionModel CreateFromFilteredSolutionFile(string filteredSoluti IEnumerable filteredSolutionProjectPaths; try { - JsonElement root = JsonDocument.Parse(File.ReadAllText(filteredSolutionPath)).RootElement; + var options = new JsonDocumentOptions + { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip + }; + JsonElement root = JsonDocument.Parse(File.ReadAllText(filteredSolutionPath), options).RootElement; originalSolutionPath = Uri.UnescapeDataString(root.GetProperty("solution").GetProperty("path").GetString()); filteredSolutionProjectPaths = [.. root.GetProperty("solution").GetProperty("projects").EnumerateArray().Select(p => p.GetString())]; originalSolutionPathAbsolute = Path.GetFullPath(originalSolutionPath, Path.GetDirectoryName(filteredSolutionPath)); diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App.sln b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App.sln new file mode 100644 index 000000000000..8b12e33e563a --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{8E5D6DE4-3E3B-4C22-8E3B-4E3B4C228E3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib", "Lib\Lib.csproj", "{9F6E7EF5-4F4C-5D33-9F4C-5F4C5D339F4C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8E5D6DE4-3E3B-4C22-8E3B-4E3B4C228E3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E5D6DE4-3E3B-4C22-8E3B-4E3B4C228E3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E5D6DE4-3E3B-4C22-8E3B-4E3B4C228E3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E5D6DE4-3E3B-4C22-8E3B-4E3B4C228E3B}.Release|Any CPU.Build.0 = Release|Any CPU + {9F6E7EF5-4F4C-5D33-9F4C-5F4C5D339F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F6E7EF5-4F4C-5D33-9F4C-5F4C5D339F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F6E7EF5-4F4C-5D33-9F4C-5F4C5D339F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F6E7EF5-4F4C-5D33-9F4C-5F4C5D339F4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/App.csproj b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/App.csproj new file mode 100644 index 000000000000..a269962b552f --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/App.csproj @@ -0,0 +1,8 @@ + + + + Exe + net8.0 + + + diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/Program.cs b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/Program.cs new file mode 100644 index 000000000000..411eb2978996 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/App/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace App +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello from App!"); + } + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithComments.slnf b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithComments.slnf new file mode 100644 index 000000000000..de1561a20aa8 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithComments.slnf @@ -0,0 +1,10 @@ +{ + /* Multi-line comment + Testing comment support */ + "solution": { + "path": "App.sln", + "projects": [ + "App\\App.csproj" // Only include App project + ] + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithTrailingComma.slnf b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithTrailingComma.slnf new file mode 100644 index 000000000000..479d52260ad3 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/AppWithTrailingComma.slnf @@ -0,0 +1,10 @@ +{ + // This is a solution filter with trailing commas and comments + "solution": { + "path": "App.sln", // Path to the main solution + "projects": [ + "App\\App.csproj", + "Lib\\Lib.csproj", + ] + } +} diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/Lib.csproj b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/Lib.csproj new file mode 100644 index 000000000000..58990cd569d2 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/Lib.csproj @@ -0,0 +1,7 @@ + + + + net8.0 + + + diff --git a/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/LibClass.cs b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/LibClass.cs new file mode 100644 index 000000000000..6d0d8977a498 --- /dev/null +++ b/test/TestAssets/TestProjects/TestAppWithTrailingCommaSlnf/Lib/LibClass.cs @@ -0,0 +1,10 @@ +namespace Lib +{ + public class LibClass + { + public string GetMessage() + { + return "Hello from Lib!"; + } + } +} diff --git a/test/dotnet.Tests/CommandTests/Solution/List/GivenDotnetSlnList.cs b/test/dotnet.Tests/CommandTests/Solution/List/GivenDotnetSlnList.cs index fbd8fcab1ed9..e1ea5196316b 100644 --- a/test/dotnet.Tests/CommandTests/Solution/List/GivenDotnetSlnList.cs +++ b/test/dotnet.Tests/CommandTests/Solution/List/GivenDotnetSlnList.cs @@ -298,5 +298,46 @@ public void WhenSolutionFilterOriginalPathContainsSpecialCharactersTheyAreUnesca cmd.Should().Pass(); } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFilterWithTrailingCommaIsPassedItListsProjects(string solutionCommand) + { + string[] expectedOutput = { $"{CliCommandStrings.ProjectsHeader}", + $"{new string('-', CliCommandStrings.ProjectsHeader.Length)}", + $"{Path.Combine("App", "App.csproj")}", + $"{Path.Combine("Lib", "Lib.csproj")}" }; + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithTrailingCommaSlnf", identifier: "GivenDotnetSlnList-TrailingComma") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "AppWithTrailingComma.slnf", "list"); + cmd.Should().Pass(); + cmd.StdOut.Should().ContainAll(expectedOutput); + } + + [Theory] + [InlineData("sln")] + [InlineData("solution")] + public void WhenSolutionFilterWithCommentsIsPassedItListsProjects(string solutionCommand) + { + string[] expectedOutput = { $"{CliCommandStrings.ProjectsHeader}", + $"{new string('-', CliCommandStrings.ProjectsHeader.Length)}", + $"{Path.Combine("App", "App.csproj")}" }; + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithTrailingCommaSlnf", identifier: "GivenDotnetSlnList-Comments") + .WithSource() + .Path; + + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, "AppWithComments.slnf", "list"); + cmd.Should().Pass(); + cmd.StdOut.Should().ContainAll(expectedOutput); + } } }