Skip to content

Commit

Permalink
Merge pull request #763 from dotnet/improve-folder-performance
Browse files Browse the repository at this point in the history
Improve folder workspace performance
  • Loading branch information
JoeRobich committed Aug 20, 2020
2 parents 96bf832 + 87118e5 commit 0570b5b
Show file tree
Hide file tree
Showing 14 changed files with 210 additions and 131 deletions.
3 changes: 1 addition & 2 deletions perf/FormattedFiles.cs
Expand Up @@ -4,7 +4,6 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Perf
Expand All @@ -17,7 +16,7 @@ public class FormattedFiles
private const string UnformattedSolutionFilePath = "tests/projects/for_code_formatter/unformatted_solution/unformatted_solution.sln";

private static EmptyLogger EmptyLogger => new EmptyLogger();
private static Matcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());
private static SourceFileMatcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());

[IterationSetup]
public void NoFilesFormattedSetup()
Expand Down
3 changes: 1 addition & 2 deletions perf/NoFilesFormatted.cs
Expand Up @@ -4,7 +4,6 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Perf
Expand All @@ -17,7 +16,7 @@ public class NoFilesFormatted
private const string FormattedSolutionFilePath = "tests/projects/for_code_formatter/formatted_solution/formatted_solution.sln";

private static EmptyLogger EmptyLogger => new EmptyLogger();
private static Matcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());
private static SourceFileMatcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());

[IterationSetup]
public void NoFilesFormattedSetup()
Expand Down
3 changes: 1 addition & 2 deletions perf/RealWorldSolution.cs
Expand Up @@ -7,7 +7,6 @@
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Perf
Expand All @@ -19,7 +18,7 @@ public class RealWorldSolution
private const string UnformattedSolutionFilePath = UnformattedFolderFilePath + "ProjectSystem.sln";

private static EmptyLogger EmptyLogger => new EmptyLogger();
private static Matcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());
private static SourceFileMatcher AllFileMatcher => SourceFileMatcher.CreateMatcher(Array.Empty<string>(), Array.Empty<string>());

[IterationSetup]
public void RealWorldSolutionIterationSetup()
Expand Down
80 changes: 20 additions & 60 deletions src/CodeFormatter.cs
Expand Up @@ -4,33 +4,28 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Tools.Analyzers;
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.CodeAnalysis.Tools.Workspaces;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools
{
internal static class CodeFormatter
{
private static readonly ImmutableArray<ICodeFormatter> s_codeFormatters = new ICodeFormatter[]
{
private static readonly ImmutableArray<ICodeFormatter> s_codeFormatters = ImmutableArray.Create<ICodeFormatter>(
new WhitespaceFormatter(),
new FinalNewlineFormatter(),
new EndOfLineFormatter(),
new CharsetFormatter(),
new OrganizeImportsFormatter(),
new UnnecessaryImportsFormatter(),
new AnalyzerFormatter(Resources.Code_Style, new CodeStyleInformationProvider(), new AnalyzerRunner(), new SolutionCodeFixApplier()),
new AnalyzerFormatter(Resources.Analyzer_Reference, new AnalyzerReferenceInformationProvider(), new AnalyzerRunner(), new SolutionCodeFixApplier()),
}.ToImmutableArray();
new AnalyzerFormatter(Resources.Analyzer_Reference, new AnalyzerReferenceInformationProvider(), new AnalyzerRunner(), new SolutionCodeFixApplier()));

public static async Task<WorkspaceFormatResult> FormatWorkspaceAsync(
FormatOptions formatOptions,
Expand All @@ -47,7 +42,7 @@ internal static class CodeFormatter
var workspaceStopwatch = Stopwatch.StartNew();

using var workspace = formatOptions.WorkspaceType == WorkspaceType.Folder
? await OpenFolderWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.FileMatcher, cancellationToken).ConfigureAwait(false)
? OpenFolderWorkspace(formatOptions.WorkspaceFilePath, formatOptions.FileMatcher)
: await OpenMSBuildWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.WorkspaceType, createBinaryLog, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false);

if (workspace is null)
Expand All @@ -56,22 +51,22 @@ internal static class CodeFormatter
}

var loadWorkspaceMS = workspaceStopwatch.ElapsedMilliseconds;
logger.LogTrace(Resources.Complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds);
logger.LogTrace(Resources.Complete_in_0_ms, loadWorkspaceMS);

var projectPath = formatOptions.WorkspaceType == WorkspaceType.Project ? formatOptions.WorkspaceFilePath : string.Empty;
var solution = workspace.CurrentSolution;

logger.LogTrace(Resources.Determining_formattable_files);

var (fileCount, formatableFiles) = await DetermineFormattableFilesAsync(
solution, projectPath, formatOptions.FileMatcher, formatOptions.IncludeGeneratedFiles, logger, cancellationToken).ConfigureAwait(false);
solution, projectPath, formatOptions, logger, cancellationToken).ConfigureAwait(false);

var determineFilesMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS;
logger.LogTrace(Resources.Complete_in_0_ms, determineFilesMS);

logger.LogTrace(Resources.Running_formatters);

var formattedFiles = new List<FormattedFile>();
var formattedFiles = new List<FormattedFile>(fileCount);
var formattedSolution = await RunCodeFormattersAsync(
solution, formatableFiles, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -106,22 +101,7 @@ internal static class CodeFormatter

if (exitCode == 0 && !string.IsNullOrWhiteSpace(formatOptions.ReportPath))
{
var reportFilePath = GetReportFilePath(formatOptions.ReportPath!); // IsNullOrEmpty is not annotated on .NET Core 2.1
var reportFolderPath = Path.GetDirectoryName(reportFilePath);

if (!Directory.Exists(reportFolderPath))
{
Directory.CreateDirectory(reportFolderPath);
}

logger.LogInformation(Resources.Writing_formatting_report_to_0, reportFilePath);
var seralizerOptions = new JsonSerializerOptions
{
WriteIndented = true
};
var formattedFilesJson = JsonSerializer.Serialize(formattedFiles, seralizerOptions);

File.WriteAllText(reportFilePath, formattedFilesJson);
ReportWriter.Write(formatOptions.ReportPath!, formattedFiles, logger);
}

logger.LogDebug(Resources.Formatted_0_of_1_files, filesFormatted, fileCount);
Expand All @@ -131,31 +111,11 @@ internal static class CodeFormatter
return new WorkspaceFormatResult(filesFormatted, fileCount, exitCode);
}

private static string GetReportFilePath(string reportPath)
{
var defaultReportName = "format-report.json";
if (reportPath.EndsWith(".json"))
{
return reportPath;
}
else if (reportPath == ".")
{
return Path.Combine(Environment.CurrentDirectory, defaultReportName);
}
else
{
return Path.Combine(reportPath, defaultReportName);
}
}

private static Task<Workspace> OpenFolderWorkspaceAsync(string workspacePath, Matcher fileMatcher, CancellationToken cancellationToken)
private static Workspace OpenFolderWorkspace(string workspacePath, SourceFileMatcher fileMatcher)
{
return Task.Run<Workspace>(() =>
{
var folderWorkspace = FolderWorkspace.Create();
folderWorkspace.OpenFolder(workspacePath, fileMatcher);
return folderWorkspace;
}, cancellationToken);
var folderWorkspace = FolderWorkspace.Create();
folderWorkspace.OpenFolder(workspacePath, fileMatcher);
return folderWorkspace;
}

private static Task<Workspace?> OpenMSBuildWorkspaceAsync(
Expand All @@ -179,9 +139,9 @@ private static Task<Workspace> OpenFolderWorkspaceAsync(string workspacePath, Ma
{
var formattedSolution = solution;

foreach (var codeFormatter in s_codeFormatters)
for (var index = 0; index < s_codeFormatters.Length; index++)
{
formattedSolution = await codeFormatter.FormatAsync(formattedSolution, formattableDocuments, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false);
formattedSolution = await s_codeFormatters[index].FormatAsync(formattedSolution, formattableDocuments, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false);
}

return formattedSolution;
Expand All @@ -190,8 +150,7 @@ private static Task<Workspace> OpenFolderWorkspaceAsync(string workspacePath, Ma
internal static async Task<(int, ImmutableArray<DocumentId>)> DetermineFormattableFilesAsync(
Solution solution,
string projectPath,
Matcher fileMatcher,
bool includeGeneratedFiles,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -237,8 +196,9 @@ private static Task<Workspace> OpenFolderWorkspaceAsync(string workspacePath, Ma

addedFilePaths.Add(document.FilePath);

if (!fileMatcher.Match(document.FilePath).HasMatches ||
!document.SupportsSyntaxTree)
var isFileIncluded = formatOptions.WorkspaceType == WorkspaceType.Folder ||
formatOptions.FileMatcher.Match(document.FilePath).HasMatches;
if (!isFileIncluded || !document.SupportsSyntaxTree)
{
continue;
}
Expand All @@ -249,7 +209,7 @@ private static Task<Workspace> OpenFolderWorkspaceAsync(string workspacePath, Ma
throw new Exception($"Unable to get a syntax tree for '{document.Name}'");
}

if (!includeGeneratedFiles &&
if (!formatOptions.IncludeGeneratedFiles &&
await GeneratedCodeUtilities.IsGeneratedCodeAsync(syntaxTree, cancellationToken).ConfigureAwait(false))
{
continue;
Expand Down Expand Up @@ -277,8 +237,8 @@ await GeneratedCodeUtilities.IsGeneratedCodeAsync(syntaxTree, cancellationToken)
// If no files are covered by an editorconfig, then return them all. Otherwise only return
// files that are covered by an editorconfig.
return documentsCoveredByEditorConfig.Count == 0
? (projectFileCount, documentsNotCoveredByEditorConfig.ToImmutableArray())
: (projectFileCount, documentsCoveredByEditorConfig.ToImmutableArray());
? (projectFileCount, documentsNotCoveredByEditorConfig.ToImmutable())
: (projectFileCount, documentsCoveredByEditorConfig.ToImmutable());
}
}
}
8 changes: 4 additions & 4 deletions src/FormatOptions.cs
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools
Expand All @@ -16,7 +16,7 @@ internal class FormatOptions
public DiagnosticSeverity AnalyzerSeverity { get; }
public bool SaveFormattedFiles { get; }
public bool ChangesAreErrors { get; }
public Matcher FileMatcher { get; }
public SourceFileMatcher FileMatcher { get; }
public string? ReportPath { get; }
public bool IncludeGeneratedFiles { get; }

Expand All @@ -30,7 +30,7 @@ internal class FormatOptions
DiagnosticSeverity analyzerSeverity,
bool saveFormattedFiles,
bool changesAreErrors,
Matcher fileMatcher,
SourceFileMatcher fileMatcher,
string? reportPath,
bool includeGeneratedFiles)
{
Expand Down Expand Up @@ -58,7 +58,7 @@ internal class FormatOptions
out DiagnosticSeverity analyzerSeverity,
out bool saveFormattedFiles,
out bool changesAreErrors,
out Matcher fileMatcher,
out SourceFileMatcher fileMatcher,
out string? reportPath,
out bool includeGeneratedFiles)
{
Expand Down
22 changes: 13 additions & 9 deletions src/Formatters/DocumentFormatter.cs
Expand Up @@ -59,9 +59,9 @@ internal abstract class DocumentFormatter : ICodeFormatter
{
var formattedDocuments = ImmutableArray.CreateBuilder<(Document, Task<(SourceText originalText, SourceText? formattedText)>)>(formattableDocuments.Length);

foreach (var documentId in formattableDocuments)
for (var index = 0; index < formattableDocuments.Length; index++)
{
var document = solution.GetDocument(documentId);
var document = solution.GetDocument(formattableDocuments[index]);
if (document is null)
continue;

Expand All @@ -82,7 +82,7 @@ internal abstract class DocumentFormatter : ICodeFormatter
formattedDocuments.Add((document, formatTask));
}

return formattedDocuments.ToImmutableArray();
return formattedDocuments.ToImmutable();
}

/// <summary>
Expand Down Expand Up @@ -117,8 +117,9 @@ internal abstract class DocumentFormatter : ICodeFormatter
{
var formattedSolution = solution;

foreach (var (document, formatTask) in formattedDocuments)
for (var index = 0; index < formattedDocuments.Length; index++)
{
var (document, formatTask) = formattedDocuments[index];
if (cancellationToken.IsCancellationRequested)
{
return formattedSolution;
Expand All @@ -144,19 +145,22 @@ internal abstract class DocumentFormatter : ICodeFormatter
return formattedSolution;
}

private IEnumerable<FileChange> GetFileChanges(FormatOptions formatOptions, string workspacePath, string filePath, SourceText originalText, SourceText formattedText, bool changesAreErrors, ILogger logger)
private ImmutableArray<FileChange> GetFileChanges(FormatOptions formatOptions, string workspacePath, string filePath, SourceText originalText, SourceText formattedText, bool changesAreErrors, ILogger logger)
{
var fileChanges = new List<FileChange>();
var workspaceFolder = Path.GetDirectoryName(workspacePath);
var changes = formattedText.GetChangeRanges(originalText);
if (workspaceFolder is null)
{
throw new Exception($"Unable to find directory name for '{workspacePath}'");
}

foreach (var change in changes)
var fileChanges = ImmutableArray.CreateBuilder<FileChange>();
var changes = formattedText.GetChangeRanges(originalText);

for (var index = 0; index < changes.Count; index++)
{
var change = changes[index];
var changePosition = originalText.Lines.GetLinePosition(change.Span.Start);

var fileChange = new FileChange(changePosition, FormatWarningDescription);
fileChanges.Add(fileChange);

Expand All @@ -166,7 +170,7 @@ private IEnumerable<FileChange> GetFileChanges(FormatOptions formatOptions, stri
}
}

return fileChanges;
return fileChanges.ToImmutable();
}

private static void LogFormattingChanges(string filePath, bool changesAreErrors, ILogger logger, string workspaceFolder, FileChange fileChange)
Expand Down
1 change: 0 additions & 1 deletion src/Program.cs
Expand Up @@ -9,7 +9,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.CodeAnalysis.Tools.Logging;
using Microsoft.CodeAnalysis.Tools.MSBuild;
using Microsoft.CodeAnalysis.Tools.Utilities;
Expand Down
51 changes: 51 additions & 0 deletions src/ReportWriter.cs
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools
{
internal static class ReportWriter
{
public static void Write(string reportPath, IEnumerable<FormattedFile> formattedFiles, ILogger logger)
{
var reportFilePath = GetReportFilePath(reportPath);
var reportFolderPath = Path.GetDirectoryName(reportFilePath);

if (!Directory.Exists(reportFolderPath))
{
Directory.CreateDirectory(reportFolderPath);
}

logger.LogInformation(Resources.Writing_formatting_report_to_0, reportFilePath);

var seralizerOptions = new JsonSerializerOptions
{
WriteIndented = true
};
var formattedFilesJson = JsonSerializer.Serialize(formattedFiles, seralizerOptions);

File.WriteAllText(reportFilePath, formattedFilesJson);
}

private static string GetReportFilePath(string reportPath)
{
var defaultReportName = "format-report.json";
if (reportPath.EndsWith(".json"))
{
return reportPath;
}
else if (reportPath == ".")
{
return Path.Combine(Environment.CurrentDirectory, defaultReportName);
}
else
{
return Path.Combine(reportPath, defaultReportName);
}
}
}
}

0 comments on commit 0570b5b

Please sign in to comment.