diff --git a/src/CodeFormatter.cs b/src/CodeFormatter.cs index 10894f4055..6129edefa5 100644 --- a/src/CodeFormatter.cs +++ b/src/CodeFormatter.cs @@ -26,22 +26,21 @@ internal static class CodeFormatter }.ToImmutableArray(); public static async Task FormatWorkspaceAsync( - string solutionOrProjectPath, - bool isSolution, - bool logWorkspaceWarnings, - bool saveFormattedFiles, - ImmutableHashSet 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) { @@ -51,7 +50,7 @@ public static async Task 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); @@ -65,7 +64,7 @@ public static async Task 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); @@ -165,6 +164,7 @@ void LogWorkspaceWarnings(object sender, WorkspaceDiagnosticEventArgs args) private static async Task RunCodeFormattersAsync( Solution solution, ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments, + FormatOptions options, ILogger logger, CancellationToken cancellationToken) { @@ -172,7 +172,7 @@ private static async Task RunCodeFormattersAsync( 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; diff --git a/src/FormatOptions.cs b/src/FormatOptions.cs new file mode 100644 index 0000000000..348ba40039 --- /dev/null +++ b/src/FormatOptions.cs @@ -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 FilesToFormat { get; } + + public FormatOptions( + string workspaceFilePath, + bool isSolution, + LogLevel logLevel, + bool saveFormattedFiles, + bool changesAreErrors, + ImmutableHashSet 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 filesToFormat) + { + workspaceFilePath = WorkspaceFilePath; + isSolution = IsSolution; + logLevel = LogLevel; + saveFormattedFiles = SaveFormattedFiles; + changesAreErrors = ChangesAreErrors; + filesToFormat = FilesToFormat; + } + } +} diff --git a/src/Formatters/DocumentFormatter.cs b/src/Formatters/DocumentFormatter.cs index f6dca24cf3..fa9f06f487 100644 --- a/src/Formatters/DocumentFormatter.cs +++ b/src/Formatters/DocumentFormatter.cs @@ -16,17 +16,20 @@ namespace Microsoft.CodeAnalysis.Tools.Formatters /// internal abstract class DocumentFormatter : ICodeFormatter { + protected abstract string FormatWarningDescription { get; } + /// /// Applies formatting and returns a formatted /// public async Task 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); } /// @@ -36,22 +39,24 @@ protected abstract Task FormatFileAsync( Document document, OptionSet options, ICodingConventionsSnapshot codingConventions, + FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken); /// /// Applies formatting and returns the changed for each . /// - private ImmutableArray<(Document, Task)> 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)>(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)); } @@ -62,29 +67,32 @@ protected abstract Task FormatFileAsync( /// /// Get formatted for a . /// - private async Task 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); } /// /// Applies the changed to each formatted . /// - private static async Task ApplyFileChangesAsync( + private async Task ApplyFileChangesAsync( Solution solution, - ImmutableArray<(Document, Task)> formattedDocuments, + ImmutableArray<(Document, Task<(SourceText originalText, SourceText formattedText)>)> formattedDocuments, + FormatOptions formatOptions, + ILogger logger, CancellationToken cancellationToken) { var formattedSolution = solution; @@ -96,16 +104,44 @@ private static async Task 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); + } + } + } } } diff --git a/src/Formatters/FinalNewlineFormatter.cs b/src/Formatters/FinalNewlineFormatter.cs index a1ca8292fe..0f1b987bc2 100644 --- a/src/Formatters/FinalNewlineFormatter.cs +++ b/src/Formatters/FinalNewlineFormatter.cs @@ -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 FormatFileAsync( Document document, OptionSet options, ICodingConventionsSnapshot codingConventions, + FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken) { diff --git a/src/Formatters/ICodeFormatter.cs b/src/Formatters/ICodeFormatter.cs index 70ab7385fb..c0d23f347a 100644 --- a/src/Formatters/ICodeFormatter.cs +++ b/src/Formatters/ICodeFormatter.cs @@ -17,6 +17,7 @@ internal interface ICodeFormatter Task FormatAsync( Solution solution, ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments, + FormatOptions options, ILogger logger, CancellationToken cancellationToken); } diff --git a/src/Formatters/WhitespaceFormatter.cs b/src/Formatters/WhitespaceFormatter.cs index b1f145d151..a609df3bc5 100644 --- a/src/Formatters/WhitespaceFormatter.cs +++ b/src/Formatters/WhitespaceFormatter.cs @@ -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; @@ -15,15 +17,45 @@ namespace Microsoft.CodeAnalysis.Tools.Formatters /// internal sealed class WhitespaceFormatter : DocumentFormatter { + protected override string FormatWarningDescription => Resources.Fix_whitespace_formatting; + protected override async Task 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); + } + } + + /// + /// Returns a formatted with a single that encompasses the entire document. + /// + private static async Task GetFormattedDocument(Document document, OptionSet options, CancellationToken cancellationToken) { var formattedDocument = await Formatter.FormatAsync(document, options, cancellationToken).ConfigureAwait(false); return await formattedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); } + + /// + /// Returns a formatted with multiple s for each formatting change. + /// + private static async Task 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); + } } } diff --git a/src/Program.cs b/src/Program.cs index 92da22a8b8..61dee16a04 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -88,12 +88,16 @@ public static async Task 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); diff --git a/src/Resources.resx b/src/Resources.resx index b14f722391..0b58b9ae71 100644 --- a/src/Resources.resx +++ b/src/Resources.resx @@ -192,4 +192,10 @@ Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + + Add final newline. + + + Fix whitespace formatting. + \ No newline at end of file diff --git a/src/xlf/Resources.cs.xlf b/src/xlf/Resources.cs.xlf index c7972f7aad..7cc896b5c2 100644 --- a/src/xlf/Resources.cs.xlf +++ b/src/xlf/Resources.cs.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. {0} obsahuje jak soubor projektu MSBuild, tak soubor řešení. Určete, který soubor chcete použít, pomocí parametru --workspace. @@ -37,6 +42,11 @@ Nepodařilo se uložit změny formátování. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.de.xlf b/src/xlf/Resources.de.xlf index 1df60fd1b5..01b976ec4d 100644 --- a/src/xlf/Resources.de.xlf +++ b/src/xlf/Resources.de.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. In "{0}" wurden eine MSBuild-Projektdatei und eine Projektmappe gefunden. Geben Sie mit der Option "--workspace" an, welche verwendet werden soll. @@ -37,6 +42,11 @@ Fehler beim Speichern von Formatierungsänderungen. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.es.xlf b/src/xlf/Resources.es.xlf index 2e0facbe40..ee0a6b02cc 100644 --- a/src/xlf/Resources.es.xlf +++ b/src/xlf/Resources.es.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. Se encontró un archivo de proyecto y un archivo de solución MSBuild en "{0}". Especifique cuál debe usarse con la opción --workspace. @@ -37,6 +42,11 @@ Error al guardar cambios de formato. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.fr.xlf b/src/xlf/Resources.fr.xlf index fbdf0470c2..f7af7fa185 100644 --- a/src/xlf/Resources.fr.xlf +++ b/src/xlf/Resources.fr.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. Un fichier projet et un fichier solution MSBuild ont été trouvés dans '{0}'. Spécifiez celui à utiliser avec l'option --workspace. @@ -37,6 +42,11 @@ L'enregistrement des changements de mise en forme a échoué. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.it.xlf b/src/xlf/Resources.it.xlf index 1c258331b3..f2a1a3a0b1 100644 --- a/src/xlf/Resources.it.xlf +++ b/src/xlf/Resources.it.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. In '{0}' sono stati trovati sia un file di progetto che un file di soluzione MSBuild. Per specificare quello desiderato, usare l'opzione --workspace. @@ -37,6 +42,11 @@ Non è stato possibile salvare le modifiche di formattazione. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.ja.xlf b/src/xlf/Resources.ja.xlf index 8005993f5c..21a3fd9c33 100644 --- a/src/xlf/Resources.ja.xlf +++ b/src/xlf/Resources.ja.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. MSBuild プロジェクト ファイルとソリューション ファイルが '{0}' で見つかりました。使用するファイルを --workspace オプションで指定してください。 @@ -37,6 +42,11 @@ 書式変更を保存できませんでした。 + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.ko.xlf b/src/xlf/Resources.ko.xlf index 8f02cd1b82..fb41a80578 100644 --- a/src/xlf/Resources.ko.xlf +++ b/src/xlf/Resources.ko.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. '{0}'에 MSBuild 프로젝트 파일 및 솔루션 파일이 모두 있습니다. --workspace 옵션에 사용할 파일을 지정하세요. @@ -37,6 +42,11 @@ 서식 변경 내용을 저장하지 못했습니다. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.pl.xlf b/src/xlf/Resources.pl.xlf index 39f8b6f52b..f1a4eaa976 100644 --- a/src/xlf/Resources.pl.xlf +++ b/src/xlf/Resources.pl.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. W elemencie „{0}” znaleziono zarówno plik rozwiązania, jak i plik projektu MSBuild. Określ plik do użycia za pomocą opcji --workspace. @@ -37,6 +42,11 @@ Nie można zapisać zmian formatowania. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.pt-BR.xlf b/src/xlf/Resources.pt-BR.xlf index 1030a57fab..c36b0a5f59 100644 --- a/src/xlf/Resources.pt-BR.xlf +++ b/src/xlf/Resources.pt-BR.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. Foram encontrados um arquivo de solução e um arquivo de projeto MSBuild em '{0}'. Especifique qual usar com a opção --espaço de trabalho. @@ -37,6 +42,11 @@ Falha ao salvar alterações de formatação. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.ru.xlf b/src/xlf/Resources.ru.xlf index 82e1cd0943..b3260d0075 100644 --- a/src/xlf/Resources.ru.xlf +++ b/src/xlf/Resources.ru.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. В "{0}" обнаружены как файл проекта, так и файл решения MSBuild. Укажите используемый файл с помощью параметра --workspace. @@ -37,6 +42,11 @@ Не удалось сохранить изменения форматирования. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.tr.xlf b/src/xlf/Resources.tr.xlf index 17e32fd256..72aea9a5bc 100644 --- a/src/xlf/Resources.tr.xlf +++ b/src/xlf/Resources.tr.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. Hem bir MSBuild proje dosyası ve çözüm dosyası '{0}' içinde bulundu. Hangi--çalışma alanı seçeneği ile kullanmak için belirtin. @@ -37,6 +42,11 @@ Biçimlendirme değişiklikleri kaydedilemedi. + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.zh-Hans.xlf b/src/xlf/Resources.zh-Hans.xlf index fd8135046e..cbaaa8981e 100644 --- a/src/xlf/Resources.zh-Hans.xlf +++ b/src/xlf/Resources.zh-Hans.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. 在“{0}”中同时找到 MSBuild 项目文件和解决方案文件。请指定要将哪一个文件用于 --workspace 选项。 @@ -37,6 +42,11 @@ 未能保存格式更改。 + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/src/xlf/Resources.zh-Hant.xlf b/src/xlf/Resources.zh-Hant.xlf index 219ae213a3..c5a18dd184 100644 --- a/src/xlf/Resources.zh-Hant.xlf +++ b/src/xlf/Resources.zh-Hant.xlf @@ -7,6 +7,11 @@ A comma separated list of relative file paths to format. All files are formatted if empty. + + Add final newline. + Add final newline. + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the --workspace option. 在 '{0}' 中同時找到 MSBuild 專案檔和解決方案檔。請指定要搭配 --workspace 選項使用的檔案。 @@ -37,6 +42,11 @@ 無法儲存格式化變更。 + + Fix whitespace formatting. + Fix whitespace formatting. + + Format complete in {0}ms. Format complete in {0}ms. diff --git a/tests/CodeFormatterTests.cs b/tests/CodeFormatterTests.cs index 71d56ad518..cb4b32962f 100644 --- a/tests/CodeFormatterTests.cs +++ b/tests/CodeFormatterTests.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.CodeAnalysis.Tools.Tests @@ -29,6 +31,8 @@ public class CodeFormatterTests : IClassFixture, IClassFixture EmptyFilesToFormat => Array.Empty(); + private Regex FindFormattingLogLine => new Regex(@"((.*)\(\d+,\d+\): (.*))\r|((.*)\(\d+,\d+\): (.*))"); + public CodeFormatterTests(MSBuildFixture msBuildFixture, SolutionPathFixture solutionPathFixture) { msBuildFixture.RegisterInstance(); @@ -141,6 +145,71 @@ public async Task OnlyLogFormattedFiles() Assert.Equal("Program.cs", match.Groups[1].Value); } + [Fact] + public async Task FormatLocationsLoggedInUnformattedProject() + { + var log = await TestFormatWorkspaceAsync( + UnformattedProjectFilePath, + EmptyFilesToFormat, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 5); + + var formatLocations = log.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Where(line => FindFormattingLogLine.Match(line).Success) + .ToArray(); + + var expectedFormatLocations = new[] + { + @"other_items\OtherClass.cs(5,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(6,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(7,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(8,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(9,7): Fix whitespace formatting.", + @"other_items\OtherClass.cs(10,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(11,3): Fix whitespace formatting.", + @"Program.cs(5,3): Fix whitespace formatting.", + @"Program.cs(6,3): Fix whitespace formatting.", + @"Program.cs(7,5): Fix whitespace formatting.", + @"Program.cs(8,5): Fix whitespace formatting.", + @"Program.cs(9,7): Fix whitespace formatting.", + @"Program.cs(10,5): Fix whitespace formatting.", + @"Program.cs(11,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(12,2): Add final newline.", + @"Program.cs(12,2): Add final newline.", + }.Select(path => path.Replace('\\', Path.DirectorySeparatorChar)).ToArray(); + + // We can't assert the location of the format message because different platform + // line endings change the position in the file. + Assert.Equal(expectedFormatLocations.Length, formatLocations.Length); + for (var index = 0; index < expectedFormatLocations.Length; index++) + { + var expectedParts = FindFormattingLogLine.Match(expectedFormatLocations[index]); + var formatParts = FindFormattingLogLine.Match(formatLocations[index]); + + // Match filename + Assert.Equal(expectedParts.Groups[2].Value, formatParts.Groups[2].Value); + // Match formatter message + Assert.Equal(expectedParts.Groups[3].Value, formatParts.Groups[3].Value); + } + } + + [Fact] + public async Task FormatLocationsNotLoggedInFormattedProject() + { + var log = await TestFormatWorkspaceAsync( + FormattedProjectFilePath, + EmptyFilesToFormat, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3); + + var formatLocations = log.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Where(line => FindFormattingLogLine.Match(line).Success); + + Assert.Empty(formatLocations); + } + public async Task TestFormatWorkspaceAsync(string solutionOrProjectPath, IEnumerable files, int expectedExitCode, int expectedFilesFormatted, int expectedFileCount) { var workspacePath = Path.GetFullPath(solutionOrProjectPath); @@ -148,7 +217,14 @@ public async Task TestFormatWorkspaceAsync(string solutionOrProjectPath, var filesToFormat = files.Select(Path.GetFullPath).ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); var logger = new TestLogger(); - var formatResult = await CodeFormatter.FormatWorkspaceAsync(workspacePath, isSolution, logWorkspaceWarnings: false, saveFormattedFiles: false, filesToFormat, logger, CancellationToken.None); + var formatOptions = new FormatOptions( + workspacePath, + isSolution, + LogLevel.Trace, + saveFormattedFiles: false, + changesAreErrors: false, + filesToFormat); + var formatResult = await CodeFormatter.FormatWorkspaceAsync(formatOptions, logger, CancellationToken.None); Assert.Equal(expectedExitCode, formatResult.ExitCode); Assert.Equal(expectedFilesFormatted, formatResult.FilesFormatted); diff --git a/tests/Formatters/AbstractFormatterTests.cs b/tests/Formatters/AbstractFormatterTests.cs index a98aff2e77..3c8de67a64 100644 --- a/tests/Formatters/AbstractFormatterTests.cs +++ b/tests/Formatters/AbstractFormatterTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -44,6 +45,8 @@ static AbstractFormatterTest() protected virtual string DefaultTestProjectName { get; } = "TestProject"; + protected virtual string DefaultTestProjectPath => "." + Path.DirectorySeparatorChar + DefaultTestProjectName + "." + DefaultFileExt + "proj"; + protected virtual string DefaultFilePath => DefaultFilePathPrefix + 0 + "." + DefaultFileExt; protected abstract string DefaultFileExt { get; } @@ -84,15 +87,23 @@ private protected async Task TestAsync(string testCode, string expectedCode, ICo TestCode = testCode; var solution = GetSolution(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray()); - var document = solution.Projects.Single().Documents.Single(); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); var options = (OptionSet)await document.GetOptionsAsync(); + var formatOptions = new FormatOptions( + workspaceFilePath: project.FilePath, + isSolution: false, + logLevel: LogLevel.Trace, + saveFormattedFiles: false, + changesAreErrors: false, + filesToFormat: ImmutableHashSet.Create(document.FilePath)); ICodingConventionsSnapshot codingConventions = new TestCodingConventionsSnapshot(editorConfig); options = OptionsApplier.ApplyConventions(options, codingConventions, Language); var filesToFormat = new[] { (document, options, codingConventions) }.ToImmutableArray(); - var formattedSolution = await formatter.FormatAsync(solution, filesToFormat, Logger, default); + var formattedSolution = await formatter.FormatAsync(solution, filesToFormat, formatOptions, Logger, default); var formattedDocument = formattedSolution.Projects.Single().Documents.Single(); var formattedText = await formattedDocument.GetTextAsync(); @@ -176,7 +187,7 @@ protected virtual Project CreateProjectImpl((string filename, SourceText content { (var newFileName, var source) = sources[i]; var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, source); + solution = solution.AddDocument(documentId, newFileName, source, filePath: newFileName); } for (var i = 0; i < additionalFiles.Length; i++) @@ -209,7 +220,7 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language) var solution = CreateWorkspace() .CurrentSolution - .AddProject(projectId, DefaultTestProjectName, DefaultTestProjectName, language) + .AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), DefaultTestProjectName, DefaultTestProjectName, language, filePath: DefaultTestProjectPath)) .WithProjectCompilationOptions(projectId, compilationOptions) .AddMetadataReference(projectId, MetadataReferences.CorlibReference) .AddMetadataReference(projectId, MetadataReferences.SystemReference)