Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix globbing to include/exclude projects #1183

Merged
merged 16 commits into from
Aug 25, 2023
2 changes: 1 addition & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Remove empty namespace declaration ([RCS1072](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1072))
- Remove empty region directive ([RCS1091](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1091))
- Remove empty destructor ([RCS1106](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1106))
- [CLI] Add glob pattern matching (`--include` or/and `--exclude`) ([#1178](https://github.com/josefpihrt/roslynator/pull/1178)).
- [CLI] Add glob pattern matching (`--include` or/and `--exclude`) ([#1178](https://github.com/josefpihrt/roslynator/pull/1178), [#1183](https://github.com/josefpihrt/roslynator/pull/1183)).

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public DocumentationWriter(MarkdownWriter writer) : base(writer)

public override void WriteOptionDescription(CommandOption option)
{
string description = option.FullDescription;
string description = option.Description;

if (!string.IsNullOrEmpty(description))
{
Expand All @@ -49,5 +49,8 @@ public override void WriteOptionDescription(CommandOption option)
_writer.WriteString(description);
}
}

if (!string.IsNullOrEmpty(option.AdditionalDescription))
_writer.WriteRaw(option.AdditionalDescription);
}
}
17 changes: 17 additions & 0 deletions src/CommandLine/CommandLineHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ namespace Roslynator.CommandLine;

internal static class CommandLineHelpers
{
public static bool IsGlobPatternForFileOrFolder(string pattern)
{
return !IsGlobPatternForProject(pattern)
&& !IsGlobPatternForSolution(pattern);
}

public static bool IsGlobPatternForProject(string pattern)
{
return pattern.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)
|| pattern.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase);
}

public static bool IsGlobPatternForSolution(string pattern)
{
return pattern.EndsWith(".sln", StringComparison.OrdinalIgnoreCase);
}

public static void WaitForKeyPress(string message = null)
{
if (Console.IsInputRedirected)
Expand Down
2 changes: 0 additions & 2 deletions src/CommandLine/Commands/AnalyzeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ public override async Task<AnalyzeCommandResult> ExecuteAsync(ProjectOrSolution
{
Solution solution = projectOrSolution.AsSolution();

var projectFilter = new ProjectFilter(Options.Projects, Options.IgnoredProjects, Language);

results = await codeAnalyzer.AnalyzeSolutionAsync(solution, f => IsMatch(f), cancellationToken);
}

Expand Down
2 changes: 0 additions & 2 deletions src/CommandLine/Commands/FixCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ public override async Task<FixCommandResult> ExecuteAsync(ProjectOrSolution proj

CultureInfo culture = (Options.Culture is not null) ? CultureInfo.GetCultureInfo(Options.Culture) : null;

var projectFilter = new ProjectFilter(Options.Projects, Options.IgnoredProjects, Language);

return await FixAsync(projectOrSolution, analyzerAssemblies, codeFixerOptions, culture, cancellationToken);
}

Expand Down
37 changes: 18 additions & 19 deletions src/CommandLine/Commands/MSBuildWorkspaceCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.Extensions.FileSystemGlobbing;
using static Roslynator.Logger;

namespace Roslynator.CommandLine;
Expand All @@ -34,7 +35,7 @@ public string Language

public abstract Task<TCommandResult> ExecuteAsync(ProjectOrSolution projectOrSolution, CancellationToken cancellationToken = default);

public async Task<CommandStatus> ExecuteAsync(IEnumerable<string> paths, string msbuildPath = null, IEnumerable<string> properties = null)
public async Task<CommandStatus> ExecuteAsync(IEnumerable<PathInfo> paths, string msbuildPath = null, IEnumerable<string> properties = null)
{
if (paths is null)
throw new ArgumentNullException(nameof(paths));
Expand Down Expand Up @@ -67,12 +68,25 @@ public async Task<CommandStatus> ExecuteAsync(IEnumerable<string> paths, string
var status = CommandStatus.Success;
var results = new List<TCommandResult>();

foreach (string path in paths)
foreach (PathInfo path in paths)
{
if (path.Origin == PathOrigin.PipedInput)
{
Matcher matcher = (string.Equals(Path.GetExtension(path.Path), ".sln", StringComparison.OrdinalIgnoreCase))
? ProjectFilter.SolutionMatcher
: ProjectFilter.Matcher;

if (matcher?.Match(path.Path).HasMatches == false)
{
WriteLine($"Skip '{path.Path}'", ConsoleColors.DarkGray, Verbosity.Normal);
continue;
}
}

TCommandResult result;
try
{
result = await ExecuteAsync(path, workspace, cancellationToken);
result = await ExecuteAsync(path.Path, workspace, cancellationToken);

if (result is null)
{
Expand Down Expand Up @@ -134,11 +148,6 @@ private async Task<TCommandResult> ExecuteAsync(string path, MSBuildWorkspace wo
if (!File.Exists(path))
throw new FileNotFoundException($"Project or solution file not found: {path}");

TCommandResult result = await ExecuteAsync(path, workspace, ConsoleProgressReporter.Default, cancellationToken);

if (result is not null)
return result;

ProjectOrSolution projectOrSolution = await OpenProjectOrSolutionAsync(path, workspace, ConsoleProgressReporter.Default, cancellationToken);

Solution solution = projectOrSolution.AsSolution();
Expand Down Expand Up @@ -207,15 +216,6 @@ protected virtual void WorkspaceFailed(object sender, WorkspaceDiagnosticEventAr
WriteLine($" {e.Diagnostic.Message}", e.Diagnostic.Kind.GetColors(), Verbosity.Detailed);
}

protected virtual Task<TCommandResult> ExecuteAsync(
string path,
MSBuildWorkspace workspace,
IProgress<ProjectLoadProgress> progress = null,
CancellationToken cancellationToken = default)
{
return Task.FromResult(default(TCommandResult));
}

private static async Task<ProjectOrSolution> OpenProjectOrSolutionAsync(
string path,
MSBuildWorkspace workspace,
Expand Down Expand Up @@ -353,8 +353,7 @@ private static bool TryGetVisualStudioInstance(out VisualStudioInstance result)

private protected bool IsMatch(Project project)
{
return ProjectFilter.IsMatch(project)
&& FileSystemFilter?.IsMatch(project.FilePath) != false;
return ProjectFilter.IsMatch(project);
}

private protected async Task<ImmutableArray<Compilation>> GetCompilationsAsync(
Expand Down
8 changes: 4 additions & 4 deletions src/CommandLine/Commands/MigrateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ internal class MigrateCommand
",
RegexOptions.IgnorePatternWhitespace);

public MigrateCommand(ImmutableArray<string> paths, string identifier, Version version, bool dryRun)
public MigrateCommand(ImmutableArray<PathInfo> paths, string identifier, Version version, bool dryRun)
{
Paths = paths;
Identifier = identifier;
Version = version;
DryRun = dryRun;
}

public ImmutableArray<string> Paths { get; }
public ImmutableArray<PathInfo> Paths { get; }

public string Identifier { get; }

Expand Down Expand Up @@ -90,9 +90,9 @@ private CommandStatus Execute(CancellationToken cancellationToken)
{
var status = CommandStatus.Success;

foreach (string path in Paths)
foreach (PathInfo path in Paths)
{
CommandStatus status2 = ExecutePath(path, cancellationToken);
CommandStatus status2 = ExecutePath(path.Path, cancellationToken);

if (status != CommandStatus.Success)
status = status2;
Expand Down
2 changes: 0 additions & 2 deletions src/CommandLine/Commands/RenameSymbolCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ public override async Task<RenameSymbolCommandResult> ExecuteAsync(ProjectOrSolu
{
AssemblyResolver.Register();

var projectFilter = new ProjectFilter(Options.Projects, Options.IgnoredProjects, Language);

SymbolRenameState renamer = null;

if (projectOrSolution.IsProject)
Expand Down
2 changes: 0 additions & 2 deletions src/CommandLine/Commands/SpellcheckCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ public override async Task<SpellcheckCommandResult> ExecuteAsync(ProjectOrSoluti

CultureInfo culture = (Options.Culture is not null) ? CultureInfo.GetCultureInfo(Options.Culture) : null;

var projectFilter = new ProjectFilter(Options.Projects, Options.IgnoredProjects, Language);

return await FixAsync(projectOrSolution, options, culture, cancellationToken);
}

Expand Down
48 changes: 44 additions & 4 deletions src/CommandLine/Options/MSBuildCommandLineOptions.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using CommandLine;
using Microsoft.Extensions.FileSystemGlobbing;

namespace Roslynator.CommandLine;

// Files, IgnoredFiles
public abstract class MSBuildCommandLineOptions : BaseCommandLineOptions
{
[AdditionalDescription(" For further information about the syntax see [reference documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.filesystemglobbing.matcher?view=dotnet-plat-ext-7.0#remarks).")]
[Option(
longName: "include",
HelpText = "Space separated list of glob patterns to include files/folders.",
HelpText = "Space separated list of glob patterns to include files, folders, solutions or projects.",
MetaValue = "<GLOB>")]
public IEnumerable<string> Include { get; set; }

[AdditionalDescription(" For further information about the syntax see [reference documentation](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.filesystemglobbing.matcher?view=dotnet-plat-ext-7.0#remarks).")]
[Option(
longName: "exclude",
HelpText = "Space separated list of glob patterns to exclude files/folders.",
HelpText = "Space separated list of glob patterns to exclude files, folders, solutions or projects.",
MetaValue = "<GLOB>")]
public IEnumerable<string> Exclude { get; set; }

Expand Down Expand Up @@ -81,7 +84,44 @@ internal bool TryGetProjectFilter(out ProjectFilter projectFilter)
return false;
}

projectFilter = new ProjectFilter(Projects, IgnoredProjects, language);
Matcher projectMatcher = CreateMatcher(p => CommandLineHelpers.IsGlobPatternForProject(p));
Matcher solutionMatcher = CreateMatcher(p => CommandLineHelpers.IsGlobPatternForSolution(p));

projectFilter = new ProjectFilter(projectMatcher, solutionMatcher, Projects, IgnoredProjects, language);
return true;
}

private Matcher CreateMatcher(Func<string, bool> patternPredicate)
{
if (!Include.Any()
&& !Exclude.Any())
{
return null;
}

string[] include = Include.Where(patternPredicate).ToArray();
string[] exclude = Exclude.Where(patternPredicate).ToArray();

Matcher matcher = null;

if (include.Any()
|| exclude.Any())
{
matcher = new Matcher((FileSystemHelpers.IsCaseSensitive) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);

if (include.Any())
{
matcher.AddIncludePatterns(include);
}
else
{
matcher.AddInclude("**");
}

if (exclude.Any())
matcher.AddExcludePatterns(exclude);
}

return matcher;
}
}
5 changes: 5 additions & 0 deletions src/CommandLine/PathInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Roslynator.CommandLine;

internal readonly record struct PathInfo(string Path, PathOrigin Origin);
10 changes: 10 additions & 0 deletions src/CommandLine/PathOrigin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Roslynator.CommandLine;

internal enum PathOrigin
{
Argument,
PipedInput,
CurrentDirectory,
}