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

[CLI] Include/exclude files using glob patterns #1178

Merged
merged 14 commits into from
Aug 23, 2023
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +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)).

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
{
SyntaxTree tree = context.Tree;

if (_options.FileSystemFilter?.IsMatch(tree.FilePath) == false)
return;

SyntaxNode root = tree.GetRoot(context.CancellationToken);

var analysisContext = new SpellingAnalysisContext(
Expand Down
74 changes: 73 additions & 1 deletion src/CommandLine/Commands/AbstractLinesOfCodeCommand`1.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,82 @@
// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Roslynator.CodeMetrics;
using Roslynator.Host.Mef;
using static Roslynator.Logger;

namespace Roslynator.CommandLine;

internal abstract class AbstractLinesOfCodeCommand<TResult> : MSBuildWorkspaceCommand<LinesOfCodeCommandResult>
{
protected AbstractLinesOfCodeCommand(in ProjectFilter projectFilter) : base(projectFilter)
protected AbstractLinesOfCodeCommand(in ProjectFilter projectFilter, FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
}

public async Task<ImmutableDictionary<ProjectId, CodeMetricsInfo>> CountLinesAsync(
IEnumerable<Project> projects,
LinesOfCodeKind kind,
CodeMetricsOptions options = null,
CancellationToken cancellationToken = default)
{
var codeMetrics = new ConcurrentBag<(ProjectId projectId, CodeMetricsInfo codeMetrics)>();

#if NETFRAMEWORK
await Task.CompletedTask;

Parallel.ForEach(
projects,
project =>
{
ICodeMetricsService service = MefWorkspaceServices.Default.GetService<ICodeMetricsService>(project.Language);

CodeMetricsInfo projectMetrics = (service is not null)
? service.CountLinesAsync(project, kind, FileSystemFilter, options, cancellationToken).Result
: CodeMetricsInfo.NotAvailable;

codeMetrics.Add((project.Id, codeMetrics: projectMetrics));
});
#else
await Parallel.ForEachAsync(
projects,
cancellationToken,
async (project, cancellationToken) =>
{
ICodeMetricsService service = MefWorkspaceServices.Default.GetService<ICodeMetricsService>(project.Language);

CodeMetricsInfo projectMetrics = (service is not null)
? await service.CountLinesAsync(project, kind, FileSystemFilter, options, cancellationToken)
: CodeMetricsInfo.NotAvailable;

codeMetrics.Add((project.Id, codeMetrics: projectMetrics));
});
#endif
return codeMetrics.ToImmutableDictionary(f => f.projectId, f => f.codeMetrics);
}

protected static void WriteLinesOfCode(Solution solution, ImmutableDictionary<ProjectId, CodeMetricsInfo> projectsMetrics)
{
int maxDigits = projectsMetrics.Max(f => f.Value.CodeLineCount).ToString("n0").Length;
int maxNameLength = projectsMetrics.Max(f => solution.GetProject(f.Key).Name.Length);

foreach (KeyValuePair<ProjectId, CodeMetricsInfo> kvp in projectsMetrics
.OrderByDescending(f => f.Value.CodeLineCount)
.ThenBy(f => solution.GetProject(f.Key).Name))
{
Project project = solution.GetProject(kvp.Key);
CodeMetricsInfo codeMetrics = kvp.Value;

string count = (codeMetrics.CodeLineCount >= 0)
? codeMetrics.CodeLineCount.ToString("n0").PadLeft(maxDigits)
: "-";

WriteLine($"{count} {project.Name.PadRight(maxNameLength)} {project.Language}", Verbosity.Normal);
}
}
}
5 changes: 3 additions & 2 deletions src/CommandLine/Commands/AnalyzeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Roslynator.CommandLine;

internal class AnalyzeCommand : MSBuildWorkspaceCommand<AnalyzeCommandResult>
{
public AnalyzeCommand(AnalyzeCommandLineOptions options, DiagnosticSeverity severityLevel, in ProjectFilter projectFilter) : base(projectFilter)
public AnalyzeCommand(AnalyzeCommandLineOptions options, DiagnosticSeverity severityLevel, in ProjectFilter projectFilter, FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
SeverityLevel = severityLevel;
Expand All @@ -31,6 +31,7 @@ public override async Task<AnalyzeCommandResult> ExecuteAsync(ProjectOrSolution
AssemblyResolver.Register();

var codeAnalyzerOptions = new CodeAnalyzerOptions(
fileSystemFilter: FileSystemFilter,
ignoreAnalyzerReferences: Options.IgnoreAnalyzerReferences,
ignoreCompilerDiagnostics: Options.IgnoreCompilerDiagnostics,
reportNotConfigurable: Options.ReportNotConfigurable,
Expand Down Expand Up @@ -80,7 +81,7 @@ public override async Task<AnalyzeCommandResult> ExecuteAsync(ProjectOrSolution

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

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

return new AnalyzeCommandResult(
Expand Down
3 changes: 2 additions & 1 deletion src/CommandLine/Commands/FindSymbolsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal class FindSymbolsCommand : MSBuildWorkspaceCommand<CommandResult>
public FindSymbolsCommand(
FindSymbolsCommandLineOptions options,
SymbolFinderOptions symbolFinderOptions,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
SymbolFinderOptions = symbolFinderOptions;
Expand Down
11 changes: 6 additions & 5 deletions src/CommandLine/Commands/FixCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ internal class FixCommand : MSBuildWorkspaceCommand<FixCommandResult>
IEnumerable<KeyValuePair<string, string>> diagnosticFixMap,
IEnumerable<KeyValuePair<string, string>> diagnosticFixerMap,
FixAllScope fixAllScope,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
SeverityLevel = severityLevel;
Expand All @@ -47,6 +48,7 @@ public override async Task<FixCommandResult> ExecuteAsync(ProjectOrSolution proj
AssemblyResolver.Register();

var codeFixerOptions = new CodeFixerOptions(
fileSystemFilter: FileSystemFilter,
severityLevel: SeverityLevel,
ignoreCompilerErrors: Options.IgnoreCompilerErrors,
ignoreAnalyzerReferences: Options.IgnoreAnalyzerReferences,
Expand All @@ -69,14 +71,13 @@ public override async Task<FixCommandResult> ExecuteAsync(ProjectOrSolution proj

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

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

private static async Task<FixCommandResult> FixAsync(
private async Task<FixCommandResult> FixAsync(
ProjectOrSolution projectOrSolution,
IEnumerable<AnalyzerAssembly> analyzerAssemblies,
CodeFixerOptions codeFixerOptions,
ProjectFilter projectFilter,
IFormatProvider formatProvider = null,
CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -114,7 +115,7 @@ public override async Task<FixCommandResult> ExecuteAsync(ProjectOrSolution proj

CodeFixer codeFixer = GetCodeFixer(solution);

results = await codeFixer.FixSolutionAsync(f => projectFilter.IsMatch(f), cancellationToken);
results = await codeFixer.FixSolutionAsync(f => IsMatch(f), cancellationToken);
}

WriteProjectFixResults(results, codeFixerOptions, formatProvider);
Expand Down
34 changes: 29 additions & 5 deletions src/CommandLine/Commands/FormatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Roslynator.CommandLine;

internal class FormatCommand : MSBuildWorkspaceCommand<FormatCommandResult>
{
public FormatCommand(FormatCommandLineOptions options, in ProjectFilter projectFilter) : base(projectFilter)
public FormatCommand(FormatCommandLineOptions options, in ProjectFilter projectFilter, FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
}
Expand All @@ -30,20 +30,18 @@ public override async Task<FormatCommandResult> ExecuteAsync(ProjectOrSolution p
{
ImmutableArray<DocumentId> formattedDocuments;

var options = new CodeFormatterOptions(fileSystemFilter: FileSystemFilter, includeGeneratedCode: Options.IncludeGeneratedCode);

if (projectOrSolution.IsProject)
{
Project project = projectOrSolution.AsProject();

var options = new CodeFormatterOptions(includeGeneratedCode: Options.IncludeGeneratedCode);

formattedDocuments = await FormatProjectAsync(project, options, cancellationToken);
}
else
{
Solution solution = projectOrSolution.AsSolution();

var options = new CodeFormatterOptions(includeGeneratedCode: Options.IncludeGeneratedCode);

formattedDocuments = await FormatSolutionAsync(solution, options, cancellationToken);
}

Expand All @@ -60,6 +58,9 @@ private async Task<ImmutableArray<DocumentId>> FormatSolutionAsync(Solution solu

var changedDocuments = new ConcurrentBag<ImmutableArray<DocumentId>>();

#if NETFRAMEWORK
await Task.CompletedTask;

Parallel.ForEach(
FilterProjects(solution),
project =>
Expand All @@ -80,6 +81,29 @@ private async Task<ImmutableArray<DocumentId>> FormatSolutionAsync(Solution solu

WriteLine($" Done analyzing '{project.Name}'", Verbosity.Normal);
});
#else
await Parallel.ForEachAsync(
FilterProjects(solution),
cancellationToken,
async (project, cancellationToken) =>
{
WriteLine($" Analyze '{project.Name}'", Verbosity.Minimal);

ISyntaxFactsService syntaxFacts = MefWorkspaceServices.Default.GetService<ISyntaxFactsService>(project.Language);

Project newProject = CodeFormatter.FormatProjectAsync(project, syntaxFacts, options, cancellationToken).Result;

ImmutableArray<DocumentId> formattedDocuments = await CodeFormatter.GetFormattedDocumentsAsync(project, newProject, syntaxFacts);

if (formattedDocuments.Any())
{
changedDocuments.Add(formattedDocuments);
LogHelpers.WriteFormattedDocuments(formattedDocuments, project, solutionDirectory);
}

WriteLine($" Done analyzing '{project.Name}'", Verbosity.Normal);
});
#endif

if (!changedDocuments.IsEmpty)
{
Expand Down
6 changes: 4 additions & 2 deletions src/CommandLine/Commands/GenerateDocCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ internal class GenerateDocCommand : MSBuildWorkspaceCommand<CommandResult>
FilesLayout filesLayout,
bool groupByCommonNamespace,
InheritanceStyle inheritanceStyle,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
Depth = depth;
Expand Down Expand Up @@ -118,7 +119,8 @@ public override async Task<CommandResult> ExecuteAsync(ProjectOrSolution project
IgnoredTitleParts = IgnoredTitleParts,
IncludeContainingNamespaceFilter = IncludeContainingNamespaceFilter,
FilesLayout = FilesLayout,
ScrollToContent = (DocumentationHost == DocumentationHost.GitHub) && Options.ScrollToContent
ScrollToContent = (DocumentationHost == DocumentationHost.GitHub) && Options.ScrollToContent,
FileSystemFilter = FileSystemFilter,
};

if (Options.IgnoredNames is not null)
Expand Down
4 changes: 3 additions & 1 deletion src/CommandLine/Commands/GenerateDocRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ internal class GenerateDocRootCommand : MSBuildWorkspaceCommand<CommandResult>
DocumentationHost documentationHost,
FilesLayout filesLayout,
bool groupByCommonNamespace,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
Depth = depth;
Expand Down Expand Up @@ -73,6 +74,7 @@ public override async Task<CommandResult> ExecuteAsync(ProjectOrSolution project
IncludeContainingNamespaceFilter = IncludeContainingNamespaceFilter,
ScrollToContent = (DocumentationHost == DocumentationHost.GitHub) && Options.ScrollToContent,
FilesLayout = FilesLayout,
FileSystemFilter = FileSystemFilter,
};

if (Options.IgnoredNames is not null)
Expand Down
5 changes: 3 additions & 2 deletions src/CommandLine/Commands/GenerateSourceReferencesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ internal class GenerateSourceReferencesCommand : MSBuildWorkspaceCommand<Command
GenerateSourceReferencesCommandLineOptions options,
DocumentationDepth depth,
Visibility visibility,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
Depth = depth;
Expand All @@ -39,7 +40,7 @@ public override async Task<CommandResult> ExecuteAsync(ProjectOrSolution project
{
AssemblyResolver.Register();

var filter = new SymbolFilterOptions(Visibility.ToVisibilityFilter());
var filter = new SymbolFilterOptions(FileSystemFilter, Visibility.ToVisibilityFilter());

WriteLine($"Save source references to '{Options.Output}'.", Verbosity.Minimal);

Expand Down
3 changes: 2 additions & 1 deletion src/CommandLine/Commands/ListReferencesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ internal class ListReferencesCommand : MSBuildWorkspaceCommand<CommandResult>
ListReferencesCommandLineOptions options,
MetadataReferenceDisplay display,
MetadataReferenceFilter filter,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
Display = display;
Expand Down
3 changes: 2 additions & 1 deletion src/CommandLine/Commands/ListSymbolsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ internal class ListSymbolsCommand : MSBuildWorkspaceCommand<CommandResult>
WrapListOptions wrapListOptions,
SymbolDefinitionListLayout layout,
SymbolDefinitionPartFilter ignoredParts,
in ProjectFilter projectFilter) : base(projectFilter)
in ProjectFilter projectFilter,
FileSystemFilter fileSystemFilter) : base(projectFilter, fileSystemFilter)
{
Options = options;
SymbolFilterOptions = symbolFilterOptions;
Expand Down