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

Improve folder workspace performance #763

Merged
merged 3 commits into from Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}
}
}