From aac7c07bbddd15845b2291020292944895fcc7e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:23:58 +0000 Subject: [PATCH 1/4] Initial plan From b5b0844e543f9585d481c92a21bffd32539807d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:39:36 +0000 Subject: [PATCH 2/4] Implement folder support for project selection Co-authored-by: eNeRGy164 <10671831+eNeRGy164@users.noreply.github.com> --- Directory.Packages.props | 4 +- src/DendroDocs.Tool/AnalyzerSetup.cs | 95 +++++++++++++++++++ src/DendroDocs.Tool/DendroDocs.Tool.csproj | 2 + src/DendroDocs.Tool/Options.cs | 5 +- src/DendroDocs.Tool/Program.cs | 4 +- src/DendroDocs.Tool/packages.lock.json | 6 ++ .../AnalyzerSetup/AnalyzerSetupTests.cs | 59 ++++++++++++ .../DendroDocs.Tool.Tests/packages.lock.json | 7 ++ 8 files changed, 178 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3c3cc91..781f948 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true true - + @@ -23,4 +23,4 @@ - + \ No newline at end of file diff --git a/src/DendroDocs.Tool/AnalyzerSetup.cs b/src/DendroDocs.Tool/AnalyzerSetup.cs index 110df73..2d1d927 100644 --- a/src/DendroDocs.Tool/AnalyzerSetup.cs +++ b/src/DendroDocs.Tool/AnalyzerSetup.cs @@ -1,5 +1,7 @@ using Buildalyzer; using Buildalyzer.Workspaces; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; namespace DendroDocs.Tool; @@ -42,8 +44,101 @@ public static AnalyzerSetup BuildProjectAnalyzer(string projectFile) return new AnalyzerSetup(manager); } + public static AnalyzerSetup BuildFolderAnalyzer(string folderPathOrPattern, IEnumerable excludedProjects = default!) + { + var excludedSet = excludedProjects is not null ? new HashSet(excludedProjects, StringComparer.OrdinalIgnoreCase) : []; + var projectFiles = DiscoverProjectFiles(folderPathOrPattern); + + if (!projectFiles.Any()) + { + throw new InvalidOperationException($"No project files found in folder or pattern: {folderPathOrPattern}"); + } + + var manager = new AnalyzerManager(); + foreach (var projectFile in projectFiles) + { + if (!excludedSet.Contains(projectFile)) + { + manager.GetProject(projectFile); + } + } + + var analysis = new AnalyzerSetup(manager); + + // Filter out test projects and excluded projects + analysis.Projects = analysis.Projects + .Where(p => !ProjectContainsTestPackageReference(manager, p)) + .Where(p => string.IsNullOrEmpty(p.FilePath) || !excludedSet.Contains(p.FilePath)); + + return analysis; + } + private static bool ProjectContainsTestPackageReference(AnalyzerManager manager, Project p) { return manager.Projects.First(mp => p.Id.Id == mp.Value.ProjectGuid).Value.ProjectFile.PackageReferences.Any(pr => pr.Name.Contains("Test", StringComparison.Ordinal)); } + + private static IEnumerable DiscoverProjectFiles(string folderPathOrPattern) + { + // Check if it's a direct path to a folder + if (Directory.Exists(folderPathOrPattern)) + { + return Directory.GetFiles(folderPathOrPattern, "*.csproj", SearchOption.AllDirectories); + } + + // Treat as a glob pattern + var matcher = new Matcher(); + + // If the pattern doesn't contain wildcards, assume it's a folder and add /**/*.csproj + if (!folderPathOrPattern.Contains('*') && !folderPathOrPattern.Contains('?')) + { + var basePath = folderPathOrPattern; + if (!basePath.EndsWith(Path.DirectorySeparatorChar)) + { + basePath += Path.DirectorySeparatorChar; + } + matcher.AddInclude($"**/*.csproj"); + + // Use current directory as base if path doesn't exist as a directory + var baseDirectory = Directory.Exists(folderPathOrPattern) ? folderPathOrPattern : Directory.GetCurrentDirectory(); + var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory))); + return result.Files.Select(f => Path.Combine(baseDirectory, f.Path)); + } + else + { + // Handle as a true glob pattern + matcher.AddInclude(folderPathOrPattern); + + // Determine base directory from the pattern + var baseDirectory = GetBasDirectoryFromPattern(folderPathOrPattern); + var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory))); + return result.Files.Select(f => Path.Combine(baseDirectory, f.Path)); + } + } + + private static string GetBasDirectoryFromPattern(string pattern) + { + // Find the first occurrence of wildcards and take the directory part before it + var wildcardIndex = Math.Min( + pattern.IndexOf('*') >= 0 ? pattern.IndexOf('*') : int.MaxValue, + pattern.IndexOf('?') >= 0 ? pattern.IndexOf('?') : int.MaxValue + ); + + if (wildcardIndex == int.MaxValue) + { + // No wildcards, use the pattern as is if it's a directory + return Directory.Exists(pattern) ? pattern : Directory.GetCurrentDirectory(); + } + + var basePart = pattern.Substring(0, wildcardIndex); + var lastSeparator = basePart.LastIndexOf(Path.DirectorySeparatorChar); + + if (lastSeparator >= 0) + { + var baseDir = basePart.Substring(0, lastSeparator); + return Directory.Exists(baseDir) ? baseDir : Directory.GetCurrentDirectory(); + } + + return Directory.GetCurrentDirectory(); + } } diff --git a/src/DendroDocs.Tool/DendroDocs.Tool.csproj b/src/DendroDocs.Tool/DendroDocs.Tool.csproj index 5032590..27babeb 100644 --- a/src/DendroDocs.Tool/DendroDocs.Tool.csproj +++ b/src/DendroDocs.Tool/DendroDocs.Tool.csproj @@ -36,12 +36,14 @@ + + diff --git a/src/DendroDocs.Tool/Options.cs b/src/DendroDocs.Tool/Options.cs index 3ba4f00..3068e40 100644 --- a/src/DendroDocs.Tool/Options.cs +++ b/src/DendroDocs.Tool/Options.cs @@ -10,7 +10,10 @@ public class Options [Option("project", Required = true, SetName = "project", HelpText = "The project to analyze.")] public string? ProjectPath { get; set; } - [Option("exclude", Required = false, SetName = "solution", Separator = ',', HelpText = "Any projects to exclude from analysis.")] + [Option("folder", Required = true, SetName = "folder", HelpText = "The folder to search for projects, or a glob pattern to match project files.")] + public string? FolderPath { get; set; } + + [Option("exclude", Required = false, Separator = ',', HelpText = "Any projects to exclude from analysis.")] public IEnumerable ExcludedProjectPaths { get; set; } = []; [Option("output", Required = true, HelpText = "The location of the output.")] diff --git a/src/DendroDocs.Tool/Program.cs b/src/DendroDocs.Tool/Program.cs index 93ed339..89cbc7d 100644 --- a/src/DendroDocs.Tool/Program.cs +++ b/src/DendroDocs.Tool/Program.cs @@ -31,7 +31,9 @@ private static async Task RunApplicationAsync(Options options) using (var analyzer = options.SolutionPath is not null ? AnalyzerSetup.BuildSolutionAnalyzer(options.SolutionPath, options.ExcludedProjectPaths) - : AnalyzerSetup.BuildProjectAnalyzer(options.ProjectPath!)) + : options.ProjectPath is not null + ? AnalyzerSetup.BuildProjectAnalyzer(options.ProjectPath!) + : AnalyzerSetup.BuildFolderAnalyzer(options.FolderPath!, options.ExcludedProjectPaths)) { await AnalyzeWorkspace(types, analyzer).ConfigureAwait(false); } diff --git a/src/DendroDocs.Tool/packages.lock.json b/src/DendroDocs.Tool/packages.lock.json index 761a3a0..56260b1 100644 --- a/src/DendroDocs.Tool/packages.lock.json +++ b/src/DendroDocs.Tool/packages.lock.json @@ -64,6 +64,12 @@ "System.Threading.Channels": "7.0.0" } }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", diff --git a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs index 1b09d38..0e4e03f 100644 --- a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs +++ b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs @@ -95,6 +95,58 @@ public void SolutionShouldLoadProject() analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); } + [TestMethod] + public void FolderShouldLoadAllProjects() + { + // Arrange + var folderPath = SolutionPath; + + // Act + using var analyzerSetup = AnalyzerSetup.BuildFolderAnalyzer(folderPath); + + // Assert + analyzerSetup.Projects.Count().ShouldBe(3); + + var projectPaths = analyzerSetup.Projects.Select(p => p.FilePath).ToList(); + projectPaths.ShouldContain(path => path != null && path.EndsWith("Project.csproj")); + projectPaths.ShouldContain(path => path != null && path.EndsWith("OtherProject.csproj")); + projectPaths.ShouldContain(path => path != null && path.EndsWith("AnotherProject.csproj")); + } + + [TestMethod] + public void FolderShouldFilterTestProjects() + { + // Arrange + var basePath = GetBasePath(); + var folderPath = Path.Combine(basePath, "AnalyzerSetupVerification"); + + // Act + using var analyzerSetup = AnalyzerSetup.BuildFolderAnalyzer(folderPath); + + // Assert + // Should have 3 projects (excluding TestProject which has test packages) + analyzerSetup.Projects.Count().ShouldBe(3); + + var projects = analyzerSetup.Projects.ToList(); + projects.ShouldAllBe(p => p.FilePath != null && !p.FilePath.Contains("TestProject")); + } + + [TestMethod] + public void FolderShouldFilterExcludedProjects() + { + // Arrange + var folderPath = SolutionPath; + var excludeProjectFile1 = Path.Combine(SolutionPath, "OtherProject", "OtherProject.csproj"); + var excludeProjectFile2 = Path.Combine(SolutionPath, "AnotherProject", "AnotherProject.csproj"); + + // Act + using var analyzerSetup = AnalyzerSetup.BuildFolderAnalyzer(folderPath, [excludeProjectFile1, excludeProjectFile2]); + + // Assert + analyzerSetup.Projects.ShouldHaveSingleItem(); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); + } + private static string GetSolutionPath() { var currentDirectory = Directory.GetCurrentDirectory().AsSpan(); @@ -103,4 +155,11 @@ private static string GetSolutionPath() return Path.Combine(path.ToString(), "AnalyzerSetupVerification"); } + + private static string GetBasePath() + { + var currentDirectory = Directory.GetCurrentDirectory().AsSpan(); + + return currentDirectory[..(currentDirectory.IndexOf("tests") + 6)].ToString(); + } } diff --git a/tests/DendroDocs.Tool.Tests/packages.lock.json b/tests/DendroDocs.Tool.Tests/packages.lock.json index baf776c..d28da46 100644 --- a/tests/DendroDocs.Tool.Tests/packages.lock.json +++ b/tests/DendroDocs.Tool.Tests/packages.lock.json @@ -530,6 +530,7 @@ "DendroDocs.Shared": "[0.4.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.14.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.14.0, )", + "Microsoft.Extensions.FileSystemGlobbing": "[8.0.0, )", "Newtonsoft.Json.Schema": "[4.0.1, )" } }, @@ -633,6 +634,12 @@ "System.Threading.Channels": "7.0.0" } }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "CentralTransitive", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + }, "Newtonsoft.Json.Schema": { "type": "CentralTransitive", "requested": "[4.0.1, )", From 50acbe2adf7d846c632c7699c840d74ed7c57ba5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:45:13 +0000 Subject: [PATCH 3/4] Complete folder and glob pattern support with documentation Co-authored-by: eNeRGy164 <10671831+eNeRGy164@users.noreply.github.com> --- README.md | 13 ++++ src/DendroDocs.Tool/AnalyzerSetup.cs | 69 +++++++++++-------- src/DendroDocs.Tool/Options.cs | 2 +- src/DendroDocs.Tool/README.md | 13 ++++ .../AnalyzerSetup/AnalyzerSetupTests.cs | 31 +++++++++ 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b1a67be..1c68563 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,20 @@ dotnet tool install --global DendroDocs.Tool Example usage: ```shell +# Analyze a solution file dendrodocs-analyze --solution G:\DendroDocs\dotnet-shared-lib\DendroDocs.Shared.sln --output shared.json --pretty --verbose --exclude G:\DendroDocs\dotnet-shared-lib\build\_build.csproj + +# Analyze a single project file +dendrodocs-analyze --project MyProject.csproj --output project.json --pretty + +# Analyze all projects in a folder +dendrodocs-analyze --folder /path/to/projects --output folder.json --pretty + +# Use glob patterns to select specific projects +dendrodocs-analyze --folder "src/**/*.csproj" --output matched.json --pretty + +# Exclude specific projects when analyzing a folder +dendrodocs-analyze --folder /path/to/projects --exclude /path/to/unwanted.csproj,/path/to/test.csproj --output filtered.json ``` ## Output diff --git a/src/DendroDocs.Tool/AnalyzerSetup.cs b/src/DendroDocs.Tool/AnalyzerSetup.cs index 2d1d927..f6edb2a 100644 --- a/src/DendroDocs.Tool/AnalyzerSetup.cs +++ b/src/DendroDocs.Tool/AnalyzerSetup.cs @@ -86,59 +86,70 @@ private static IEnumerable DiscoverProjectFiles(string folderPathOrPatte return Directory.GetFiles(folderPathOrPattern, "*.csproj", SearchOption.AllDirectories); } - // Treat as a glob pattern + // Handle glob patterns var matcher = new Matcher(); - // If the pattern doesn't contain wildcards, assume it's a folder and add /**/*.csproj + // If the pattern doesn't contain wildcards, assume it's a folder that doesn't exist if (!folderPathOrPattern.Contains('*') && !folderPathOrPattern.Contains('?')) { - var basePath = folderPathOrPattern; - if (!basePath.EndsWith(Path.DirectorySeparatorChar)) - { - basePath += Path.DirectorySeparatorChar; - } - matcher.AddInclude($"**/*.csproj"); - - // Use current directory as base if path doesn't exist as a directory - var baseDirectory = Directory.Exists(folderPathOrPattern) ? folderPathOrPattern : Directory.GetCurrentDirectory(); - var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory))); - return result.Files.Select(f => Path.Combine(baseDirectory, f.Path)); + throw new DirectoryNotFoundException($"Folder not found: {folderPathOrPattern}"); + } + + // Handle as a glob pattern + string baseDirectory; + string pattern; + + // Check if pattern is absolute or relative + if (Path.IsPathRooted(folderPathOrPattern)) + { + // Absolute path - extract base directory and relative pattern + var parts = SplitAbsolutePattern(folderPathOrPattern); + baseDirectory = parts.BaseDirectory; + pattern = parts.Pattern; } else { - // Handle as a true glob pattern - matcher.AddInclude(folderPathOrPattern); - - // Determine base directory from the pattern - var baseDirectory = GetBasDirectoryFromPattern(folderPathOrPattern); - var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory))); - return result.Files.Select(f => Path.Combine(baseDirectory, f.Path)); + // Relative path + baseDirectory = Directory.GetCurrentDirectory(); + pattern = folderPathOrPattern; } + + matcher.AddInclude(pattern); + var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory))); + return result.Files.Select(f => Path.Combine(baseDirectory, f.Path)); } - private static string GetBasDirectoryFromPattern(string pattern) + private static (string BaseDirectory, string Pattern) SplitAbsolutePattern(string absolutePattern) { - // Find the first occurrence of wildcards and take the directory part before it + // Find the first wildcard var wildcardIndex = Math.Min( - pattern.IndexOf('*') >= 0 ? pattern.IndexOf('*') : int.MaxValue, - pattern.IndexOf('?') >= 0 ? pattern.IndexOf('?') : int.MaxValue + absolutePattern.IndexOf('*') >= 0 ? absolutePattern.IndexOf('*') : int.MaxValue, + absolutePattern.IndexOf('?') >= 0 ? absolutePattern.IndexOf('?') : int.MaxValue ); if (wildcardIndex == int.MaxValue) { - // No wildcards, use the pattern as is if it's a directory - return Directory.Exists(pattern) ? pattern : Directory.GetCurrentDirectory(); + // No wildcards found, treat as directory + return (absolutePattern, "**/*.csproj"); } - var basePart = pattern.Substring(0, wildcardIndex); + // Find the last directory separator before the wildcard + var basePart = absolutePattern.Substring(0, wildcardIndex); var lastSeparator = basePart.LastIndexOf(Path.DirectorySeparatorChar); if (lastSeparator >= 0) { var baseDir = basePart.Substring(0, lastSeparator); - return Directory.Exists(baseDir) ? baseDir : Directory.GetCurrentDirectory(); + var relativePattern = absolutePattern.Substring(lastSeparator + 1); + + // Ensure base directory exists + if (Directory.Exists(baseDir)) + { + return (baseDir, relativePattern); + } } - return Directory.GetCurrentDirectory(); + // Fallback to current directory + return (Directory.GetCurrentDirectory(), absolutePattern); } } diff --git a/src/DendroDocs.Tool/Options.cs b/src/DendroDocs.Tool/Options.cs index 3068e40..9763bdc 100644 --- a/src/DendroDocs.Tool/Options.cs +++ b/src/DendroDocs.Tool/Options.cs @@ -10,7 +10,7 @@ public class Options [Option("project", Required = true, SetName = "project", HelpText = "The project to analyze.")] public string? ProjectPath { get; set; } - [Option("folder", Required = true, SetName = "folder", HelpText = "The folder to search for projects, or a glob pattern to match project files.")] + [Option("folder", Required = true, SetName = "folder", HelpText = "The folder to search for projects recursively, or a glob pattern to match specific project files (e.g., 'src/**/*.csproj').")] public string? FolderPath { get; set; } [Option("exclude", Required = false, Separator = ',', HelpText = "Any projects to exclude from analysis.")] diff --git a/src/DendroDocs.Tool/README.md b/src/DendroDocs.Tool/README.md index 71903d9..1313590 100644 --- a/src/DendroDocs.Tool/README.md +++ b/src/DendroDocs.Tool/README.md @@ -22,7 +22,20 @@ dotnet tool install --global DendroDocs.Tool ## Example usage ```shell +# Analyze a solution file dendrodocs-analyze --solution G:\DendroDocs\dotnet-shared-lib\DendroDocs.Shared.sln --output shared.json --pretty --verbose --exclude G:\DendroDocs\dotnet-shared-lib\build\_build.csproj + +# Analyze a single project file +dendrodocs-analyze --project MyProject.csproj --output project.json --pretty + +# Analyze all projects in a folder +dendrodocs-analyze --folder /path/to/projects --output folder.json --pretty + +# Use glob patterns to select specific projects +dendrodocs-analyze --folder "src/**/*.csproj" --output matched.json --pretty + +# Exclude specific projects when analyzing a folder +dendrodocs-analyze --folder /path/to/projects --exclude /path/to/unwanted.csproj,/path/to/test.csproj --output filtered.json ``` ## Output diff --git a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs index 0e4e03f..f0460a5 100644 --- a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs +++ b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs @@ -147,6 +147,37 @@ public void FolderShouldFilterExcludedProjects() analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); } + [TestMethod] + public void GlobPatternShouldFindMatchingProjects() + { + // Arrange + var basePath = GetBasePath(); + var originalDir = Directory.GetCurrentDirectory(); + + try + { + // Change to the test base directory to make relative patterns work + Directory.SetCurrentDirectory(basePath); + var globPattern = "AnalyzerSetupVerification/**/*.csproj"; + + // Act + using var analyzerSetup = AnalyzerSetup.BuildFolderAnalyzer(globPattern); + + // Assert + // Should find the 3 non-test projects that match the pattern (excludes TestProject due to test references) + analyzerSetup.Projects.Count().ShouldBe(3); + + var projectPaths = analyzerSetup.Projects.Select(p => p.FilePath).ToList(); + projectPaths.ShouldContain(path => path != null && path.EndsWith("Project.csproj")); + projectPaths.ShouldContain(path => path != null && path.EndsWith("OtherProject.csproj")); + projectPaths.ShouldContain(path => path != null && path.EndsWith("AnotherProject.csproj")); + } + finally + { + Directory.SetCurrentDirectory(originalDir); + } + } + private static string GetSolutionPath() { var currentDirectory = Directory.GetCurrentDirectory().AsSpan(); From 63e9da05813fb7da2a82686fb2ad5e666cff994a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Jul 2025 08:10:55 +0000 Subject: [PATCH 4/4] Separate null checks from functional assertions Co-authored-by: eNeRGy164 <10671831+eNeRGy164@users.noreply.github.com> --- .../AnalyzerSetup/AnalyzerSetupTests.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs index f0460a5..1e547a8 100644 --- a/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs +++ b/tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs @@ -78,7 +78,8 @@ public void SolutionShouldFilterExcludedProjects() // Assert analyzerSetup.Projects.ShouldHaveSingleItem(); - analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath!.EndsWith("Project.csproj")); } [TestMethod] @@ -92,7 +93,8 @@ public void SolutionShouldLoadProject() // Assert analyzerSetup.Projects.ShouldHaveSingleItem(); - analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath!.EndsWith("Project.csproj")); } [TestMethod] @@ -108,9 +110,10 @@ public void FolderShouldLoadAllProjects() analyzerSetup.Projects.Count().ShouldBe(3); var projectPaths = analyzerSetup.Projects.Select(p => p.FilePath).ToList(); - projectPaths.ShouldContain(path => path != null && path.EndsWith("Project.csproj")); - projectPaths.ShouldContain(path => path != null && path.EndsWith("OtherProject.csproj")); - projectPaths.ShouldContain(path => path != null && path.EndsWith("AnotherProject.csproj")); + projectPaths.ShouldAllBe(path => path != null); + projectPaths.ShouldContain(path => path!.EndsWith("Project.csproj")); + projectPaths.ShouldContain(path => path!.EndsWith("OtherProject.csproj")); + projectPaths.ShouldContain(path => path!.EndsWith("AnotherProject.csproj")); } [TestMethod] @@ -128,7 +131,8 @@ public void FolderShouldFilterTestProjects() analyzerSetup.Projects.Count().ShouldBe(3); var projects = analyzerSetup.Projects.ToList(); - projects.ShouldAllBe(p => p.FilePath != null && !p.FilePath.Contains("TestProject")); + projects.ShouldAllBe(p => p.FilePath != null); + projects.ShouldAllBe(p => !p.FilePath!.Contains("TestProject")); } [TestMethod] @@ -144,7 +148,8 @@ public void FolderShouldFilterExcludedProjects() // Assert analyzerSetup.Projects.ShouldHaveSingleItem(); - analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj")); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null); + analyzerSetup.Projects.ShouldAllBe(p => p.FilePath!.EndsWith("Project.csproj")); } [TestMethod] @@ -168,9 +173,10 @@ public void GlobPatternShouldFindMatchingProjects() analyzerSetup.Projects.Count().ShouldBe(3); var projectPaths = analyzerSetup.Projects.Select(p => p.FilePath).ToList(); - projectPaths.ShouldContain(path => path != null && path.EndsWith("Project.csproj")); - projectPaths.ShouldContain(path => path != null && path.EndsWith("OtherProject.csproj")); - projectPaths.ShouldContain(path => path != null && path.EndsWith("AnotherProject.csproj")); + projectPaths.ShouldAllBe(path => path != null); + projectPaths.ShouldContain(path => path!.EndsWith("Project.csproj")); + projectPaths.ShouldContain(path => path!.EndsWith("OtherProject.csproj")); + projectPaths.ShouldContain(path => path!.EndsWith("AnotherProject.csproj")); } finally {