Skip to content

Commit

Permalink
Merge pull request dotnet#1 from jpshrader/format-500
Browse files Browse the repository at this point in the history
Format 500
  • Loading branch information
Patrick committed Feb 11, 2020
2 parents 49fbb7c + f84b006 commit ddc6c12
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 32 deletions.
53 changes: 37 additions & 16 deletions src/CodeFormatter.cs
Expand Up @@ -34,7 +34,7 @@ internal static class CodeFormatter
ILogger logger,
CancellationToken cancellationToken)
{
var (workspaceFilePath, workspaceType, logLevel, saveFormattedFiles, _, filesToFormat, reportPath) = options;
var (workspaceFilePath, workspaceType, logLevel, saveFormattedFiles, _, filesToFormat, filesToIgnore, reportPath) = options;
var logWorkspaceWarnings = logLevel == LogLevel.Trace;

logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, workspaceFilePath));
Expand All @@ -60,7 +60,7 @@ internal static class CodeFormatter
logger.LogTrace(Resources.Determining_formattable_files);

var (fileCount, formatableFiles) = await DetermineFormattableFiles(
solution, projectPath, filesToFormat, logger, cancellationToken).ConfigureAwait(false);
solution, projectPath, filesToFormat, filesToIgnore, logger, cancellationToken).ConfigureAwait(false);

var determineFilesMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS;
logger.LogTrace(Resources.Complete_in_0_ms, determineFilesMS);
Expand Down Expand Up @@ -242,6 +242,7 @@ private static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWar
Solution solution,
string projectPath,
ImmutableHashSet<string> filesToFormat,
ImmutableHashSet<string> filesToIgnore,
ILogger logger,
CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -271,7 +272,7 @@ private static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWar

// Get project documents and options with .editorconfig settings applied.
var getProjectDocuments = project.DocumentIds.Select(documentId => GetDocumentAndOptions(
project, documentId, filesToFormat, codingConventionsManager, optionsApplier, cancellationToken));
project, documentId, filesToFormat, filesToIgnore, codingConventionsManager, optionsApplier, cancellationToken));
getDocumentsAndOptions.AddRange(getProjectDocuments);
}

Expand Down Expand Up @@ -310,25 +311,14 @@ private static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWar
Project project,
DocumentId documentId,
ImmutableHashSet<string> filesToFormat,
ImmutableHashSet<string> filesToIgnore,
ICodingConventionsManager codingConventionsManager,
EditorConfigOptionsApplier optionsApplier,
CancellationToken cancellationToken)
{
var document = project.Solution.GetDocument(documentId);

// If a files list was passed in, then ignore files not present in the list.
if (!filesToFormat.IsEmpty && !filesToFormat.Contains(document.FilePath))
{
return (null, null, null, false);
}

if (!document.SupportsSyntaxTree)
{
return (null, null, null, false);
}

// Ignore generated code files.
if (await GeneratedCodeUtilities.IsGeneratedCodeAsync(document, cancellationToken).ConfigureAwait(false))
if (await ShouldIgnoreDocument(document, filesToFormat, filesToIgnore, cancellationToken))
{
return (null, null, null, false);
}
Expand All @@ -347,5 +337,36 @@ private static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWar
options = optionsApplier.ApplyConventions(options, context.CurrentConventions, project.Language);
return (document, options, context.CurrentConventions, true);
}

private static async Task<bool> ShouldIgnoreDocument(
Document document,
ImmutableHashSet<string> filesToFormat,
ImmutableHashSet<string> filesToIgnore,
CancellationToken cancellationToken)
{
if (!filesToFormat.IsEmpty && !filesToFormat.Contains(document.FilePath))
{
// If a files list was passed in, then ignore files not present in the list.
return true;
}
else if (!document.SupportsSyntaxTree)
{
return true;
}
else if (await GeneratedCodeUtilities.IsGeneratedCodeAsync(document, cancellationToken).ConfigureAwait(false))
{
// Ignore generated code files.
return true;
}
else if (!filesToIgnore.IsEmpty && filesToIgnore.Any(f => document.FilePath.Contains(f, StringComparison.OrdinalIgnoreCase)))
{
// Ignore file in, or under a folder in the list to exclude
return true;
}
else
{
return false;
}
}
}
}
5 changes: 5 additions & 0 deletions src/FormatOptions.cs
Expand Up @@ -13,6 +13,7 @@ internal class FormatOptions
public bool SaveFormattedFiles { get; }
public bool ChangesAreErrors { get; }
public ImmutableHashSet<string> FilesToFormat { get; }
public ImmutableHashSet<string> FilesToIgnore { get; }
public string ReportPath { get; }

public FormatOptions(
Expand All @@ -22,6 +23,7 @@ internal class FormatOptions
bool saveFormattedFiles,
bool changesAreErrors,
ImmutableHashSet<string> filesToFormat,
ImmutableHashSet<string> filesToIgnore,
string reportPath)
{
WorkspaceFilePath = workspaceFilePath;
Expand All @@ -30,6 +32,7 @@ internal class FormatOptions
SaveFormattedFiles = saveFormattedFiles;
ChangesAreErrors = changesAreErrors;
FilesToFormat = filesToFormat;
FilesToIgnore = filesToIgnore;
ReportPath = reportPath;
}

Expand All @@ -40,6 +43,7 @@ internal class FormatOptions
out bool saveFormattedFiles,
out bool changesAreErrors,
out ImmutableHashSet<string> filesToFormat,
out ImmutableHashSet<string> filesToIgnore,
out string reportPath)
{
workspaceFilePath = WorkspaceFilePath;
Expand All @@ -48,6 +52,7 @@ internal class FormatOptions
saveFormattedFiles = SaveFormattedFiles;
changesAreErrors = ChangesAreErrors;
filesToFormat = FilesToFormat;
filesToIgnore = FilesToIgnore;
reportPath = ReportPath;
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/Program.cs
Expand Up @@ -36,14 +36,15 @@ private static async Task<int> Main(string[] args)
.AddOption(new Option(new[] { "--dry-run" }, Resources.Format_files_but_do_not_save_changes_to_disk, new Argument<bool>()))
.AddOption(new Option(new[] { "--check" }, Resources.Terminate_with_a_non_zero_exit_code_if_any_files_were_formatted, new Argument<bool>()))
.AddOption(new Option(new[] { "--files" }, Resources.A_comma_separated_list_of_relative_file_paths_to_format_All_files_are_formatted_if_empty, new Argument<string>(() => null)))
.AddOption(new Option(new[] { "--exclude" }, "TODO", new Argument<string>(() => null)))
.AddOption(new Option(new[] { "--report" }, Resources.Accepts_a_file_path_which_if_provided_will_produce_a_format_report_json_file_in_the_given_directory, new Argument<string>(() => null)))
.UseVersionOption()
.Build();

return await parser.InvokeAsync(args).ConfigureAwait(false);
}

public static async Task<int> Run(string folder, string workspace, string verbosity, bool dryRun, bool check, string files, string report, IConsole console = null)
public static async Task<int> Run(string folder, string workspace, string verbosity, bool dryRun, bool check, string files, string exclude, string report, IConsole console = null)
{
// Setup logging.
var serviceCollection = new ServiceCollection();
Expand Down Expand Up @@ -101,7 +102,8 @@ public static async Task<int> Run(string folder, string workspace, string verbos

Environment.CurrentDirectory = workspaceDirectory;

var filesToFormat = GetFilesToFormat(files, folder);
var filesToFormat = GetFiles(files, folder);
var filesToIgnore = GetFiles(exclude, folder);

// Since we are running as a dotnet tool we should be able to find an instance of
// MSBuild in a .NET Core SDK.
Expand All @@ -122,6 +124,7 @@ public static async Task<int> Run(string folder, string workspace, string verbos
saveFormattedFiles: !dryRun,
changesAreErrors: check,
filesToFormat,
filesToIgnore,
reportPath: report);

var formatResult = await CodeFormatter.FormatWorkspaceAsync(
Expand Down Expand Up @@ -192,7 +195,7 @@ private static void ConfigureServices(ServiceCollection serviceCollection, ICons
/// <summary>
/// Converts a comma-separated list of relative file paths to a hashmap of full file paths.
/// </summary>
internal static ImmutableHashSet<string> GetFilesToFormat(string files, string folder)
internal static ImmutableHashSet<string> GetFiles(string files, string folder)
{
if (string.IsNullOrEmpty(files))
{
Expand Down
96 changes: 85 additions & 11 deletions tests/CodeFormatterTests.cs
Expand Up @@ -29,7 +29,7 @@ public class CodeFormatterTests : IClassFixture<MSBuildFixture>, IClassFixture<S
private const string FSharpProjectPath = "tests/projects/for_code_formatter/fsharp_project";
private const string FSharpProjectFilePath = FSharpProjectPath + "/fsharp_project.fsproj";

private static IEnumerable<string> EmptyFilesToFormat => Array.Empty<string>();
private static IEnumerable<string> EmptyFilesList => Array.Empty<string>();

private Regex FindFormattingLogLine => new Regex(@"((.*)\(\d+,\d+\): (.*))\r|((.*)\(\d+,\d+\): (.*))");

Expand All @@ -44,7 +44,8 @@ public async Task NoFilesFormattedInFormattedProject()
{
await TestFormatWorkspaceAsync(
FormattedProjectFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 3);
Expand All @@ -55,7 +56,8 @@ public async Task NoFilesFormattedInFormattedSolution()
{
await TestFormatWorkspaceAsync(
FormattedSolutionFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 3);
Expand All @@ -66,7 +68,8 @@ public async Task FilesFormattedInUnformattedProject()
{
await TestFormatWorkspaceAsync(
UnformattedProjectFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 2,
expectedFileCount: 5);
Expand All @@ -77,7 +80,8 @@ public async Task FilesFormattedInUnformattedSolution()
{
await TestFormatWorkspaceAsync(
UnformattedSolutionFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 2,
expectedFileCount: 5);
Expand All @@ -90,7 +94,8 @@ public async Task FilesFormattedInUnformattedProjectFolder()
// Since the code files are beneath the project folder, files are found and formatted.
await TestFormatWorkspaceAsync(
Path.GetDirectoryName(UnformattedProjectFilePath),
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 2,
expectedFileCount: 3);
Expand All @@ -102,7 +107,8 @@ public async Task NoFilesFormattedInUnformattedSolutionFolder()
// Since the code files are outside the solution folder, no files are found or formatted.
await TestFormatWorkspaceAsync(
Path.GetDirectoryName(UnformattedSolutionFilePath),
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 0);
Expand All @@ -113,7 +119,8 @@ public async Task FSharpProjectsDoNotCreateException()
{
var log = await TestFormatWorkspaceAsync(
FSharpProjectFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 1,
expectedFilesFormatted: 0,
expectedFileCount: 0);
Expand All @@ -133,6 +140,7 @@ public async Task OnlyFormatFilesFromList()
await TestFormatWorkspaceAsync(
UnformattedProjectFilePath,
filesToFormat,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 1,
expectedFileCount: 5);
Expand All @@ -146,6 +154,7 @@ public async Task NoFilesFormattedWhenNotInList()
await TestFormatWorkspaceAsync(
UnformattedProjectFilePath,
files,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 5);
Expand All @@ -159,6 +168,7 @@ public async Task OnlyLogFormattedFiles()
var log = await TestFormatWorkspaceAsync(
UnformattedSolutionFilePath,
files,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 1,
expectedFileCount: 5);
Expand All @@ -175,7 +185,8 @@ public async Task FormatLocationsLoggedInUnformattedProject()
{
var log = await TestFormatWorkspaceAsync(
UnformattedProjectFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 2,
expectedFileCount: 5);
Expand Down Expand Up @@ -224,7 +235,8 @@ public async Task FormatLocationsNotLoggedInFormattedProject()
{
var log = await TestFormatWorkspaceAsync(
FormattedProjectFilePath,
EmptyFilesToFormat,
files: EmptyFilesList,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 3);
Expand All @@ -235,7 +247,67 @@ public async Task FormatLocationsNotLoggedInFormattedProject()
Assert.Empty(formatLocations);
}

public async Task<string> TestFormatWorkspaceAsync(string workspaceFilePath, IEnumerable<string> files, int expectedExitCode, int expectedFilesFormatted, int expectedFileCount)
[Fact]
public async Task LogFilesThatDontMatchExclude()
{
var files = new[] { UnformattedProgramFilePath };
var exclude = new[] { FormattedProjectPath };

var log = await TestFormatWorkspaceAsync(
UnformattedSolutionFilePath,
files,
exclude: EmptyFilesList,
expectedExitCode: 0,
expectedFilesFormatted: 1,
expectedFileCount: 5);

var pattern = string.Format(Resources.Formatted_code_file_0, @"(.*)");
var match = new Regex(pattern, RegexOptions.Multiline).Match(log);

Assert.True(match.Success, log);
Assert.Equal("Program.cs", match.Groups[1].Value);
}

[Fact]
public async Task IgnoreFileWhenListedInExcludeList()
{
var files = new[] { UnformattedProgramFilePath };

var log = await TestFormatWorkspaceAsync(
UnformattedSolutionFilePath,
files: files,
exclude: files,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 5);

var pattern = string.Format(Resources.Formatted_code_file_0, @"(.*)");
var match = new Regex(pattern, RegexOptions.Multiline).Match(log);

Assert.False(match.Success, log);
}

[Fact]
public async Task IgnoreFileWhenContainingFolderListedInExcludeList()
{
var files = new[] { UnformattedProgramFilePath };
var exclude = new[] { UnformattedProjectPath };

var log = await TestFormatWorkspaceAsync(
UnformattedSolutionFilePath,
files: files,
exclude: exclude,
expectedExitCode: 0,
expectedFilesFormatted: 0,
expectedFileCount: 5);

var pattern = string.Format(Resources.Formatted_code_file_0, @"(.*)");
var match = new Regex(pattern, RegexOptions.Multiline).Match(log);

Assert.False(match.Success, log);
}

public async Task<string> TestFormatWorkspaceAsync(string workspaceFilePath, IEnumerable<string> files, IEnumerable<string> exclude, int expectedExitCode, int expectedFilesFormatted, int expectedFileCount)
{
var workspacePath = Path.GetFullPath(workspaceFilePath);

Expand All @@ -252,6 +324,7 @@ public async Task<string> TestFormatWorkspaceAsync(string workspaceFilePath, IEn
}

var filesToFormat = files.Select(Path.GetFullPath).ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
var filesToIgnore = exclude.Select(Path.GetFullPath).ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);

var logger = new TestLogger();
var formatOptions = new FormatOptions(
Expand All @@ -261,6 +334,7 @@ public async Task<string> TestFormatWorkspaceAsync(string workspaceFilePath, IEn
saveFormattedFiles: false,
changesAreErrors: false,
filesToFormat,
filesToIgnore,
reportPath: string.Empty);
var formatResult = await CodeFormatter.FormatWorkspaceAsync(formatOptions, logger, CancellationToken.None);

Expand Down
1 change: 1 addition & 0 deletions tests/Formatters/AbstractFormatterTests.cs
Expand Up @@ -92,6 +92,7 @@ private protected async Task<SourceText> TestAsync(string testCode, string expec
saveFormattedFiles: false,
changesAreErrors: false,
filesToFormat: ImmutableHashSet.Create(document.FilePath),
filesToIgnore: ImmutableHashSet.Create<string>(),
reportPath: string.Empty);

var filesToFormat = await GetOnlyFileToFormatAsync(solution, editorConfig);
Expand Down

0 comments on commit ddc6c12

Please sign in to comment.