Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/CodeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,21 @@ internal static class CodeFormatter
}.ToImmutableArray();

public static async Task<WorkspaceFormatResult> FormatWorkspaceAsync(
string solutionOrProjectPath,
bool isSolution,
bool logWorkspaceWarnings,
bool saveFormattedFiles,
ImmutableHashSet<string> filesToFormat,
FormatOptions options,
ILogger logger,
CancellationToken cancellationToken)
{
logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, solutionOrProjectPath));
var (workspaceFilePath, isSolution, logLevel, saveFormattedFiles, _, filesToFormat) = options;
var logWorkspaceWarnings = logLevel == LogLevel.Trace;

logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, workspaceFilePath));

logger.LogTrace(Resources.Loading_workspace);

var workspaceStopwatch = Stopwatch.StartNew();

using (var workspace = await OpenWorkspaceAsync(
solutionOrProjectPath, isSolution, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false))
workspaceFilePath, isSolution, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false))
{
if (workspace is null)
{
Expand All @@ -51,7 +50,7 @@ public static async Task<WorkspaceFormatResult> FormatWorkspaceAsync(
var loadWorkspaceMS = workspaceStopwatch.ElapsedMilliseconds;
logger.LogTrace(Resources.Complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds);

var projectPath = isSolution ? string.Empty : solutionOrProjectPath;
var projectPath = isSolution ? string.Empty : workspaceFilePath;
var solution = workspace.CurrentSolution;

logger.LogTrace(Resources.Determining_formattable_files);
Expand All @@ -65,7 +64,7 @@ public static async Task<WorkspaceFormatResult> FormatWorkspaceAsync(
logger.LogTrace(Resources.Running_formatters);

var formattedSolution = await RunCodeFormattersAsync(
solution, formatableFiles, logger, cancellationToken).ConfigureAwait(false);
solution, formatableFiles, options, logger, cancellationToken).ConfigureAwait(false);

var formatterRanMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS - determineFilesMS;
logger.LogTrace(Resources.Complete_in_0_ms, formatterRanMS);
Expand Down Expand Up @@ -165,14 +164,15 @@ void LogWorkspaceWarnings(object sender, WorkspaceDiagnosticEventArgs args)
private static async Task<Solution> RunCodeFormattersAsync(
Solution solution,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
FormatOptions options,
ILogger logger,
CancellationToken cancellationToken)
{
var formattedSolution = solution;

foreach (var codeFormatter in s_codeFormatters)
{
formattedSolution = await codeFormatter.FormatAsync(formattedSolution, formattableDocuments, logger, cancellationToken).ConfigureAwait(false);
formattedSolution = await codeFormatter.FormatAsync(formattedSolution, formattableDocuments, options, logger, cancellationToken).ConfigureAwait(false);
}

return formattedSolution;
Expand Down
47 changes: 47 additions & 0 deletions src/FormatOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools
{
internal class FormatOptions
{
public string WorkspaceFilePath { get; }
public bool IsSolution { get; }
public LogLevel LogLevel { get; }
public bool SaveFormattedFiles { get; }
public bool ChangesAreErrors { get; }
public ImmutableHashSet<string> FilesToFormat { get; }

public FormatOptions(
string workspaceFilePath,
bool isSolution,
LogLevel logLevel,
bool saveFormattedFiles,
bool changesAreErrors,
ImmutableHashSet<string> filesToFormat)
{
WorkspaceFilePath = workspaceFilePath;
IsSolution = isSolution;
LogLevel = logLevel;
SaveFormattedFiles = saveFormattedFiles;
ChangesAreErrors = changesAreErrors;
FilesToFormat = filesToFormat;
}

public void Deconstruct(
out string workspaceFilePath,
out bool isSolution,
out LogLevel logLevel,
out bool saveFormattedFiles,
out bool changesAreErrors,
out ImmutableHashSet<string> filesToFormat)
{
workspaceFilePath = WorkspaceFilePath;
isSolution = IsSolution;
logLevel = LogLevel;
saveFormattedFiles = SaveFormattedFiles;
changesAreErrors = ChangesAreErrors;
filesToFormat = FilesToFormat;
}
}
}
64 changes: 50 additions & 14 deletions src/Formatters/DocumentFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ namespace Microsoft.CodeAnalysis.Tools.Formatters
/// </summary>
internal abstract class DocumentFormatter : ICodeFormatter
{
protected abstract string FormatWarningDescription { get; }

/// <summary>
/// Applies formatting and returns a formatted <see cref="Solution"/>
/// </summary>
public async Task<Solution> FormatAsync(
Solution solution,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
var formattedDocuments = FormatFiles(formattableDocuments, logger, cancellationToken);
return await ApplyFileChangesAsync(solution, formattedDocuments, cancellationToken).ConfigureAwait(false);
var formattedDocuments = FormatFiles(formattableDocuments, formatOptions, logger, cancellationToken);
return await ApplyFileChangesAsync(solution, formattedDocuments, formatOptions, logger, cancellationToken).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -36,22 +39,24 @@ protected abstract Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken);

/// <summary>
/// Applies formatting and returns the changed <see cref="SourceText"/> for each <see cref="Document"/>.
/// </summary>
private ImmutableArray<(Document, Task<SourceText>)> FormatFiles(
private ImmutableArray<(Document, Task<(SourceText originalText, SourceText formattedText)>)> FormatFiles(
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
var formattedDocuments = ImmutableArray.CreateBuilder<(Document, Task<SourceText>)>(formattableDocuments.Length);
var formattedDocuments = ImmutableArray.CreateBuilder<(Document, Task<(SourceText originalText, SourceText formattedText)>)>(formattableDocuments.Length);

foreach (var (document, options, codingConventions) in formattableDocuments)
{
var formatTask = Task.Run(async () => await GetFormattedSourceTextAsync(document, options, codingConventions, logger, cancellationToken).ConfigureAwait(false), cancellationToken);
var formatTask = Task.Run(async () => await GetFormattedSourceTextAsync(document, options, codingConventions, formatOptions, logger, cancellationToken).ConfigureAwait(false), cancellationToken);

formattedDocuments.Add((document, formatTask));
}
Expand All @@ -62,29 +67,32 @@ protected abstract Task<SourceText> FormatFileAsync(
/// <summary>
/// Get formatted <see cref="SourceText"/> for a <see cref="Document"/>.
/// </summary>
private async Task<SourceText> GetFormattedSourceTextAsync(
private async Task<(SourceText originalText, SourceText formattedText)> GetFormattedSourceTextAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
logger.LogTrace(Resources.Formatting_code_file_0, Path.GetFileName(document.FilePath));

var originalSourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var formattedSourceText = await FormatFileAsync(document, options, codingConventions, logger, cancellationToken).ConfigureAwait(false);
var formattedSourceText = await FormatFileAsync(document, options, codingConventions, formatOptions, logger, cancellationToken).ConfigureAwait(false);

return !formattedSourceText.ContentEquals(originalSourceText)
? formattedSourceText
: null;
? (originalSourceText, formattedSourceText)
: (originalSourceText, null);
}

/// <summary>
/// Applies the changed <see cref="SourceText"/> to each formatted <see cref="Document"/>.
/// </summary>
private static async Task<Solution> ApplyFileChangesAsync(
private async Task<Solution> ApplyFileChangesAsync(
Solution solution,
ImmutableArray<(Document, Task<SourceText>)> formattedDocuments,
ImmutableArray<(Document, Task<(SourceText originalText, SourceText formattedText)>)> formattedDocuments,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
var formattedSolution = solution;
Expand All @@ -96,16 +104,44 @@ private static async Task<Solution> ApplyFileChangesAsync(
return formattedSolution;
}

var text = await formatTask.ConfigureAwait(false);
if (text is null)
var (originalText, formattedText) = await formatTask.ConfigureAwait(false);
if (formattedText is null)
{
continue;
}

formattedSolution = formattedSolution.WithDocumentText(document.Id, text);
if (!formatOptions.SaveFormattedFiles)
{
// Log formatting changes as errors when we are doing a dry-run.
LogFormattingChanges(formatOptions.WorkspaceFilePath, document.FilePath, originalText, formattedText, formatOptions.ChangesAreErrors, logger);
}

formattedSolution = formattedSolution.WithDocumentText(document.Id, formattedText);
}

return formattedSolution;
}

private void LogFormattingChanges(string workspacePath, string filePath, SourceText originalText, SourceText formattedText, bool changesAreErrors, ILogger logger)
{
var workspaceFolder = Path.GetDirectoryName(workspacePath);
var changes = formattedText.GetChangeRanges(originalText);

foreach (var change in changes)
{
// LinePosition is zero based so we need to increment to report numbers people expect.
var changePosition = originalText.Lines.GetLinePosition(change.Span.Start);
var formatMessage = $"{Path.GetRelativePath(workspaceFolder, filePath)}({changePosition.Line + 1},{changePosition.Character + 1}): {FormatWarningDescription}";

if (changesAreErrors)
{
logger.LogError(formatMessage);
}
else
{
logger.LogWarning(formatMessage);
}
}
}
}
}
3 changes: 3 additions & 0 deletions src/Formatters/FinalNewlineFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ namespace Microsoft.CodeAnalysis.Tools.Formatters
{
internal sealed class FinalNewlineFormatter : DocumentFormatter
{
protected override string FormatWarningDescription => Resources.Add_final_newline;

protected override async Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
Expand Down
1 change: 1 addition & 0 deletions src/Formatters/ICodeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal interface ICodeFormatter
Task<Solution> FormatAsync(
Solution solution,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
FormatOptions options,
ILogger logger,
CancellationToken cancellationToken);
}
Expand Down
32 changes: 32 additions & 0 deletions src/Formatters/WhitespaceFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Buffers;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Formatting;
Expand All @@ -15,15 +17,45 @@ namespace Microsoft.CodeAnalysis.Tools.Formatters
/// </summary>
internal sealed class WhitespaceFormatter : DocumentFormatter
{
protected override string FormatWarningDescription => Resources.Fix_whitespace_formatting;

protected override async Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
FormatOptions formatOptions,
ILogger logger,
CancellationToken cancellationToken)
{
if (formatOptions.SaveFormattedFiles)
{
return await GetFormattedDocument(document, options, cancellationToken);
}
else
{
return await GetFormattedDocumentWithDetailedChanges(document, options, cancellationToken);
}
}

/// <summary>
/// Returns a formatted <see cref="SourceText"/> with a single <see cref="TextChange"/> that encompasses the entire document.
/// </summary>
private static async Task<SourceText> GetFormattedDocument(Document document, OptionSet options, CancellationToken cancellationToken)
{
var formattedDocument = await Formatter.FormatAsync(document, options, cancellationToken).ConfigureAwait(false);
return await formattedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Returns a formatted <see cref="SoureText"/> with multiple <see cref="TextChange"/>s for each formatting change.
/// </summary>
private static async Task<SourceText> GetFormattedDocumentWithDetailedChanges(Document document, OptionSet options, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
var originalText = await document.GetTextAsync(cancellationToken);

var formattingTextChanges = Formatter.GetFormattedTextChanges(root, document.Project.Solution.Workspace, options, cancellationToken);
return originalText.WithChanges(formattingTextChanges);
}
}
}
10 changes: 7 additions & 3 deletions src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,16 @@ public static async Task<int> Run(string workspace, string verbosity, bool dryRu

Build.Locator.MSBuildLocator.RegisterInstance(msBuildInstance);

var formatResult = await CodeFormatter.FormatWorkspaceAsync(
var formatOptions = new FormatOptions(
workspacePath,
isSolution,
logWorkspaceWarnings: logLevel == LogLevel.Trace,
logLevel,
saveFormattedFiles: !dryRun,
filesToFormat,
changesAreErrors: check,
filesToFormat);

var formatResult = await CodeFormatter.FormatWorkspaceAsync(
formatOptions,
logger,
cancellationTokenSource.Token).ConfigureAwait(false);

Expand Down
6 changes: 6 additions & 0 deletions src/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,10 @@
<data name="Warnings_were_encountered_while_loading_the_workspace_Set_the_verbosity_option_to_the_diagnostic_level_to_log_warnings" xml:space="preserve">
<value>Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings.</value>
</data>
<data name="Add_final_newline" xml:space="preserve">
<value>Add final newline.</value>
</data>
<data name="Fix_whitespace_formatting" xml:space="preserve">
<value>Fix whitespace formatting.</value>
</data>
</root>
10 changes: 10 additions & 0 deletions src/xlf/Resources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
<target state="new">A comma separated list of relative file paths to format. All files are formatted if empty.</target>
<note />
</trans-unit>
<trans-unit id="Add_final_newline">
<source>Add final newline.</source>
<target state="new">Add final newline.</target>
<note />
</trans-unit>
<trans-unit id="Both_a_MSBuild_project_file_and_solution_file_found_in_0_Specify_which_to_use_with_the_workspace_option">
<source>Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option.</source>
<target state="translated">{0} obsahuje jak soubor projektu MSBuild, tak soubor řešení. Určete, který soubor chcete použít, pomocí parametru --workspace.</target>
Expand Down Expand Up @@ -37,6 +42,11 @@
<target state="translated">Nepodařilo se uložit změny formátování.</target>
<note />
</trans-unit>
<trans-unit id="Fix_whitespace_formatting">
<source>Fix whitespace formatting.</source>
<target state="new">Fix whitespace formatting.</target>
<note />
</trans-unit>
<trans-unit id="Format_complete_in_0_ms">
<source>Format complete in {0}ms.</source>
<target state="new">Format complete in {0}ms.</target>
Expand Down
Loading