From 4f8e117dc40d949749ed2c473579d4d789fcbf35 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 17:07:44 +0000 Subject: [PATCH] fix(gendoc): accept .slnx solutions in the documentation generator GetErrorDocumentationFrom validated the extension against ".sln" only and threw ArgumentException otherwise, contradicting ReadSolutionProjects (which enumerates projects via "dotnet sln list" and handles .sln and .slnx uniformly). Valid .slnx solutions were therefore rejected before reaching the enumeration step. Accept both .sln and .slnx. Solution filters (.slnf) stay excluded: the "dotnet sln" subcommand does not process them. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MshxEQnwdvTu8tkZPtt6PM --- ...olutionErrorDocumentationGeneratorTests.cs | 23 +++++++++++++++++++ .../SolutionErrorDocumentationGenerator.cs | 9 +++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs b/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs index c3130c4..83a1c45 100644 --- a/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs +++ b/FirstClassErrors.GenDoc.UnitTests/SolutionErrorDocumentationGeneratorTests.cs @@ -46,6 +46,29 @@ public void GetErrorDocumentationFromRejectsANonSolutionFile() { } } + [Theory(DisplayName = "GetErrorDocumentationFrom accepts the .sln and .slnx solution formats.")] + [InlineData(".sln")] + [InlineData(".slnx")] + public void GetErrorDocumentationFromAcceptsSolutionFormats(string extension) { + // Setup: a real file carrying a solution extension. It is not a valid solution, so the enumeration further + // down the pipeline will ultimately fail — but only *after* the extension validation, which is what this test + // guards. The rejected-extension path is the one that throws ArgumentException (see the sibling test); proving + // the format is accepted therefore means the call throws anything *but* an ArgumentException. + string path = Path.ChangeExtension(Path.GetTempFileName(), extension); + File.WriteAllText(path, string.Empty); + + try { + // Exercise + Exception? caught = Record.Exception( + () => SolutionErrorDocumentationGenerator.GetErrorDocumentationFrom(path, new SolutionGenerationOptions { BuildSolution = false }).ToList()); + + // Verify: the extension was accepted, so no ArgumentException was raised for it. + Check.That(caught).IsNotInstanceOf(); + } finally { + File.Delete(path); + } + } + [Fact(DisplayName = "GetErrorDocumentationFromAssemblies rejects a null path list.")] public void GetErrorDocumentationFromAssembliesRejectsANullPathList() { // Exercise & verify diff --git a/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs b/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs index 99f9b82..eb5f8b0 100644 --- a/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs +++ b/FirstClassErrors.GenDoc/SolutionErrorDocumentationGenerator.cs @@ -31,7 +31,14 @@ public static IEnumerable GetErrorDocumentationFrom(string s string fullSolutionPath = Path.GetFullPath(solutionPath); if (File.Exists(fullSolutionPath) is false) { throw new FileNotFoundException($"Solution file not found: '{fullSolutionPath}'", fullSolutionPath); } - if (string.Equals(Path.GetExtension(fullSolutionPath), ".sln", StringComparison.OrdinalIgnoreCase) is false) { throw new ArgumentException($"Expected a .sln file path, got: '{fullSolutionPath}'", nameof(solutionPath)); } + + // Accept both the classic (.sln) and the XML (.slnx) solution formats: "dotnet sln list", used below to + // enumerate the projects, handles the two uniformly. Solution filters (.slnf) are intentionally excluded — + // the "dotnet sln" subcommand does not process them. + string extension = Path.GetExtension(fullSolutionPath); + bool isSolution = string.Equals(extension, ".sln", StringComparison.OrdinalIgnoreCase) + || string.Equals(extension, ".slnx", StringComparison.OrdinalIgnoreCase); + if (isSolution is false) { throw new ArgumentException($"Expected a .sln or .slnx file path, got: '{fullSolutionPath}'", nameof(solutionPath)); } if (options.BuildSolution) { DotNetBuild(fullSolutionPath, options);