• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dendrodocs / dotnet-tool / 21591161825

02 Feb 2026 01:02PM UTC coverage: 64.525%. First build
21591161825

Pull #51

github

web-flow
Merge f98ea5b0c into 2999bc7dd
Pull Request #51: Add support for folder and glob pattern project selection

186 of 335 branches covered (55.52%)

Branch coverage included in aggregate %.

33 of 66 new or added lines in 3 files covered. (50.0%)

487 of 708 relevant lines covered (68.79%)

0.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.81
/src/DendroDocs.Tool/AnalyzerSetup.cs
1
using Buildalyzer;
2
using Buildalyzer.Workspaces;
3
using Microsoft.Extensions.FileSystemGlobbing;
4
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
5

6
namespace DendroDocs.Tool;
7

8
public sealed class AnalyzerSetup : IDisposable
9
{
10
    public IEnumerable<Project> Projects;
11
    public readonly Workspace Workspace;
12

13
    private AnalyzerSetup(AnalyzerManager Manager)
1✔
14
    {
1✔
15
        this.Workspace = Manager.GetWorkspace();
1✔
16
        this.Projects = this.Workspace.CurrentSolution.Projects;
1✔
17
    }
1✔
18

19
    public void Dispose()
20
    {
1✔
21
        this.Workspace.Dispose();
1✔
22
    }
1✔
23

24
    public static AnalyzerSetup BuildSolutionAnalyzer(string solutionFile, IEnumerable<string> excludedProjects = default!)
25
    {
1✔
26
        var excludedSet = excludedProjects is not null ? new HashSet<string>(excludedProjects, StringComparer.OrdinalIgnoreCase) : [];
1✔
27

28
        var manager = new AnalyzerManager(solutionFile);
1✔
29
        var analysis = new AnalyzerSetup(manager);
1✔
30

31
        // Every project in the solution, except unit test projects
32
        analysis.Projects = analysis.Projects
1✔
33
            .Where(p => !ProjectContainsTestPackageReference(manager, p))
1✔
34
            .Where(p => string.IsNullOrEmpty(p.FilePath) || !excludedSet.Contains(p.FilePath));
1!
35

36
        return analysis;
1✔
37
    }
1✔
38

39
    public static AnalyzerSetup BuildProjectAnalyzer(string projectFile)
40
    {
1✔
41
        var manager = new AnalyzerManager();
1✔
42
        manager.GetProject(projectFile);
1✔
43

44
        return new AnalyzerSetup(manager);
1✔
45
    }
1✔
46

47
    public static AnalyzerSetup BuildFolderAnalyzer(string folderPathOrPattern, IEnumerable<string> excludedProjects = default!)
48
    {
1✔
49
        var excludedSet = excludedProjects is not null ? new HashSet<string>(excludedProjects, StringComparer.OrdinalIgnoreCase) : [];
1✔
50
        var projectFiles = DiscoverProjectFiles(folderPathOrPattern);
1✔
51

52
        if (!projectFiles.Any())
1!
NEW
53
        {
×
NEW
54
            throw new InvalidOperationException($"No project files found in folder or pattern: {folderPathOrPattern}");
×
55
        }
56

57
        var manager = new AnalyzerManager();
1✔
58
        foreach (var projectFile in projectFiles)
1✔
59
        {
1✔
60
            if (!excludedSet.Contains(projectFile))
1✔
61
            {
1✔
62
                manager.GetProject(projectFile);
1✔
63
            }
1✔
64
        }
1✔
65

66
        var analysis = new AnalyzerSetup(manager);
1✔
67
        
68
        // Filter out test projects and excluded projects
69
        analysis.Projects = analysis.Projects
1✔
70
            .Where(p => !ProjectContainsTestPackageReference(manager, p))
1✔
71
            .Where(p => string.IsNullOrEmpty(p.FilePath) || !excludedSet.Contains(p.FilePath));
1!
72

73
        return analysis;
1✔
74
    }
1✔
75

76
    private static bool ProjectContainsTestPackageReference(AnalyzerManager manager, Project p)
77
    {
1✔
78
        return manager.Projects.First(mp => p.Id.Id == mp.Value.ProjectGuid).Value.ProjectFile.PackageReferences.Any(pr => pr.Name.Contains("Test", StringComparison.Ordinal));
1✔
79
    }
1✔
80

81
    private static IEnumerable<string> DiscoverProjectFiles(string folderPathOrPattern)
82
    {
1✔
83
        // Check if it's a direct path to a folder
84
        if (Directory.Exists(folderPathOrPattern))
1✔
85
        {
1✔
86
            return Directory.GetFiles(folderPathOrPattern, "*.csproj", SearchOption.AllDirectories);
1✔
87
        }
88

89
        // Handle glob patterns
90
        var matcher = new Matcher();
1✔
91
        
92
        // If the pattern doesn't contain wildcards, assume it's a folder that doesn't exist
93
        if (!folderPathOrPattern.Contains('*') && !folderPathOrPattern.Contains('?'))
1!
NEW
94
        {
×
NEW
95
            throw new DirectoryNotFoundException($"Folder not found: {folderPathOrPattern}");
×
96
        }
97

98
        // Handle as a glob pattern
99
        string baseDirectory;
100
        string pattern;
101
        
102
        // Check if pattern is absolute or relative
103
        if (Path.IsPathRooted(folderPathOrPattern))
1!
NEW
104
        {
×
105
            // Absolute path - extract base directory and relative pattern
NEW
106
            var parts = SplitAbsolutePattern(folderPathOrPattern);
×
NEW
107
            baseDirectory = parts.BaseDirectory;
×
NEW
108
            pattern = parts.Pattern;
×
NEW
109
        }
×
110
        else
111
        {
1✔
112
            // Relative path
113
            baseDirectory = Directory.GetCurrentDirectory();
1✔
114
            pattern = folderPathOrPattern;
1✔
115
        }
1✔
116

117
        matcher.AddInclude(pattern);
1✔
118
        var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory)));
1✔
119
        return result.Files.Select(f => Path.Combine(baseDirectory, f.Path));
1✔
120
    }
1✔
121

122
    private static (string BaseDirectory, string Pattern) SplitAbsolutePattern(string absolutePattern)
NEW
123
    {
×
124
        // Find the first wildcard
NEW
125
        var wildcardIndex = Math.Min(
×
NEW
126
            absolutePattern.IndexOf('*') >= 0 ? absolutePattern.IndexOf('*') : int.MaxValue,
×
NEW
127
            absolutePattern.IndexOf('?') >= 0 ? absolutePattern.IndexOf('?') : int.MaxValue
×
NEW
128
        );
×
129
        
NEW
130
        if (wildcardIndex == int.MaxValue)
×
NEW
131
        {
×
132
            // No wildcards found, treat as directory
NEW
133
            return (absolutePattern, "**/*.csproj");
×
134
        }
135
        
136
        // Find the last directory separator before the wildcard
NEW
137
        var basePart = absolutePattern.Substring(0, wildcardIndex);
×
NEW
138
        var lastSeparator = basePart.LastIndexOf(Path.DirectorySeparatorChar);
×
139
        
NEW
140
        if (lastSeparator >= 0)
×
NEW
141
        {
×
NEW
142
            var baseDir = basePart.Substring(0, lastSeparator);
×
NEW
143
            var relativePattern = absolutePattern.Substring(lastSeparator + 1);
×
144
            
145
            // Ensure base directory exists
NEW
146
            if (Directory.Exists(baseDir))
×
NEW
147
            {
×
NEW
148
                return (baseDir, relativePattern);
×
149
            }
NEW
150
        }
×
151
        
152
        // Fallback to current directory
NEW
153
        return (Directory.GetCurrentDirectory(), absolutePattern);
×
NEW
154
    }
×
155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc