diff --git a/src/Snitch.Tests.Fixtures/Snitch.Tests.Fixtures.slnx b/src/Snitch.Tests.Fixtures/Snitch.Tests.Fixtures.slnx
new file mode 100644
index 0000000..f463c82
--- /dev/null
+++ b/src/Snitch.Tests.Fixtures/Snitch.Tests.Fixtures.slnx
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Snitch.Tests/Expectations/Slnx.Default.verified.txt b/src/Snitch.Tests/Expectations/Slnx.Default.verified.txt
new file mode 100644
index 0000000..1fe2be4
--- /dev/null
+++ b/src/Snitch.Tests/Expectations/Slnx.Default.verified.txt
@@ -0,0 +1,50 @@
+Analyzing...
+Analyzing Snitch.Tests.Fixtures.slnx
+Analyzing Foo...
+Analyzing Bar...
+Analyzing Baz...
+Analyzing Qux...
+Analyzing Zap...
+Analyzing Quux...
+Analyzing Quuux...
+Analyzing Thud...
+Analyzing Thuuud...
+Analyzing FSharp...
+
+╭─────────────────────────────────────────────────────────────────╮
+│ Packages that can be removed from Bar: │
+│ ┌──────────────────────┬──────────────────────────────────────┐ │
+│ │ Package │ Referenced by │ │
+│ ├──────────────────────┼──────────────────────────────────────┤ │
+│ │ Autofac │ Foo │ │
+│ └──────────────────────┴──────────────────────────────────────┘ │
+│ │
+│ Packages that can be removed from Baz: │
+│ ┌──────────────────────┬──────────────────────────────────────┐ │
+│ │ Package │ Referenced by │ │
+│ ├──────────────────────┼──────────────────────────────────────┤ │
+│ │ Autofac │ Foo │ │
+│ └──────────────────────┴──────────────────────────────────────┘ │
+│ │
+│ Packages that might be removed from Qux: │
+│ ┌───────────┬───────────┬─────────────────────────────────────┐ │
+│ │ Package │ Version │ Reason │ │
+│ ├───────────┼───────────┼─────────────────────────────────────┤ │
+│ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
+│ └───────────┴───────────┴─────────────────────────────────────┘ │
+│ │
+│ Packages that might be removed from Zap: │
+│ ┌──────────────────┬──────────┬───────────────────────────────┐ │
+│ │ Package │ Version │ Reason │ │
+│ ├──────────────────┼──────────┼───────────────────────────────┤ │
+│ │ Newtonsoft.Json │ 12.0.3 │ Updated from 12.0.1 in Foo │ │
+│ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
+│ └──────────────────┴──────────┴───────────────────────────────┘ │
+│ │
+│ Packages that might be removed from Thuuud: │
+│ ┌─────────────────┬──────────────┬────────────────────────────┐ │
+│ │ Package │ Version │ Reason │ │
+│ ├─────────────────┼──────────────┼────────────────────────────┤ │
+│ │ Newtonsoft.Json │ 13.0.2-beta2 │ Updated from 12.0.1 in Foo │ │
+│ └─────────────────┴──────────────┴────────────────────────────┘ │
+╰─────────────────────────────────────────────────────────────────╯
diff --git a/src/Snitch.Tests/ProgramTests.cs b/src/Snitch.Tests/ProgramTests.cs
index f264bcd..6855117 100644
--- a/src/Snitch.Tests/ProgramTests.cs
+++ b/src/Snitch.Tests/ProgramTests.cs
@@ -222,5 +222,21 @@ public async Task Should_Return_Expected_Result_For_FSharp_Not_Specifying_Framew
exitCode.ShouldBe(0);
await Verifier.Verify(output);
}
+
+ [Fact]
+ [Expectation("Slnx", "Default")]
+ public async Task Should_Return_Expected_Result_For_Slnx_Solution()
+ {
+ // Given
+ var fixture = new Fixture();
+ var solution = Fixture.GetPath("Snitch.Tests.Fixtures.slnx");
+
+ // When
+ var (exitCode, output) = await Fixture.Run(solution);
+
+ // Then
+ exitCode.ShouldBe(0);
+ await Verifier.Verify(output);
+ }
}
}
diff --git a/src/Snitch/Analysis/Utilities/PathUtility.cs b/src/Snitch/Analysis/Utilities/PathUtility.cs
index 1f7d35e..b4acfe3 100644
--- a/src/Snitch/Analysis/Utilities/PathUtility.cs
+++ b/src/Snitch/Analysis/Utilities/PathUtility.cs
@@ -53,6 +53,11 @@ private static List GetProjectsFromFile(string path)
return GetProjectsFromSolution(path);
}
+ if (path.EndsWith(".slnx", StringComparison.InvariantCulture))
+ {
+ return SlnxParser.GetProjectsFromSlnx(path);
+ }
+
throw new InvalidOperationException("Project or solution file do not exist.");
}
@@ -61,8 +66,11 @@ private static List FindProjects(string? root, out string entry)
root ??= Environment.CurrentDirectory;
var slns = Directory.GetFiles(root, "*.sln");
+ var slnxs = Directory.GetFiles(root, "*.slnx");
+ var allSolutions = new List(slns);
+ allSolutions.AddRange(slnxs);
- if (slns.Length == 0)
+ if (allSolutions.Count == 0)
{
var subProjects = Directory.GetFiles(root, "*.csproj");
if (subProjects.Length == 0)
@@ -77,14 +85,14 @@ private static List FindProjects(string? root, out string entry)
entry = subProjects[0];
return new List(new[] { subProjects[0] });
}
- else if (slns.Length > 1)
+ else if (allSolutions.Count > 1)
{
throw new InvalidOperationException("More than one solution file found.");
}
else
{
- entry = slns[0];
- return GetProjectsFromSolution(slns[0]);
+ entry = allSolutions[0];
+ return GetProjectsFromFile(allSolutions[0]);
}
}
diff --git a/src/Snitch/Analysis/Utilities/SlnxParser.cs b/src/Snitch/Analysis/Utilities/SlnxParser.cs
new file mode 100644
index 0000000..3c38b42
--- /dev/null
+++ b/src/Snitch/Analysis/Utilities/SlnxParser.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml.Linq;
+
+namespace Snitch.Analysis.Utilities
+{
+ internal static class SlnxParser
+ {
+ public static List GetProjectsFromSlnx(string slnxPath)
+ {
+ if (!File.Exists(slnxPath))
+ {
+ throw new FileNotFoundException($"Solution file not found: {slnxPath}");
+ }
+
+ var slnxDirectory = Path.GetDirectoryName(slnxPath)
+ ?? throw new InvalidOperationException("Could not determine solution directory.");
+
+ var doc = XDocument.Load(slnxPath);
+ var solution = doc.Root;
+
+ if (solution == null || solution.Name.LocalName != "Solution")
+ {
+ throw new InvalidOperationException("Invalid slnx file: missing Solution root element.");
+ }
+
+ var projects = new List();
+ CollectProjects(solution, slnxDirectory, projects);
+
+ return projects;
+ }
+
+ private static void CollectProjects(XElement element, string slnxDirectory, List projects)
+ {
+ foreach (var child in element.Elements())
+ {
+ if (child.Name.LocalName == "Project")
+ {
+ var pathAttr = child.Attribute("Path");
+ if (pathAttr != null && !string.IsNullOrWhiteSpace(pathAttr.Value))
+ {
+ var projectPath = pathAttr.Value;
+
+ // Only include MSBuild project files (.csproj, .fsproj, .vbproj)
+ if (IsMSBuildProject(projectPath))
+ {
+ var absolutePath = Path.GetFullPath(Path.Combine(slnxDirectory, projectPath));
+ if (!projects.Contains(absolutePath))
+ {
+ projects.Add(absolutePath);
+ }
+ }
+ }
+ }
+ else if (child.Name.LocalName == "Folder")
+ {
+ // Recursively collect projects from folders
+ CollectProjects(child, slnxDirectory, projects);
+ }
+ }
+ }
+
+ private static bool IsMSBuildProject(string path)
+ {
+ return path.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)
+ || path.EndsWith(".fsproj", StringComparison.OrdinalIgnoreCase)
+ || path.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}