public static async Task <WorkspaceFormatResult> FormatWorkspaceAsync( FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken, bool createBinaryLog = false) { var logWorkspaceWarnings = formatOptions.LogLevel == LogLevel.Trace; logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, formatOptions.WorkspaceFilePath)); logger.LogTrace(Resources.Loading_workspace); var workspaceStopwatch = Stopwatch.StartNew(); using var workspace = formatOptions.WorkspaceType == WorkspaceType.Folder ? OpenFolderWorkspace(formatOptions.WorkspaceFilePath, formatOptions.FileMatcher) : await OpenMSBuildWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.WorkspaceType, createBinaryLog, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false); if (workspace is null) { return(new WorkspaceFormatResult(filesFormatted: 0, fileCount: 0, exitCode: 1)); } var loadWorkspaceMS = 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, 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>(fileCount); var formattedSolution = await RunCodeFormattersAsync( solution, formatableFiles, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false); var formatterRanMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS - determineFilesMS; logger.LogTrace(Resources.Complete_in_0_ms, formatterRanMS); var documentIdsWithErrors = formattedFiles.Select(file => file.DocumentId).Distinct().ToImmutableArray(); foreach (var documentId in documentIdsWithErrors) { var documentWithError = solution.GetDocument(documentId); logger.LogInformation(Resources.Formatted_code_file_0, documentWithError !.FilePath); } var exitCode = 0; if (formatOptions.SaveFormattedFiles && !workspace.TryApplyChanges(formattedSolution)) { logger.LogError(Resources.Failed_to_save_formatting_changes); exitCode = 1; } if (exitCode == 0 && !string.IsNullOrWhiteSpace(formatOptions.ReportPath)) { ReportWriter.Write(formatOptions.ReportPath !, formattedFiles, logger); } logger.LogDebug(Resources.Formatted_0_of_1_files, documentIdsWithErrors.Length, fileCount); logger.LogInformation(Resources.Format_complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds); return(new WorkspaceFormatResult(documentIdsWithErrors.Length, fileCount, exitCode)); }
internal static async Task <(int, ImmutableArray <DocumentId>)> DetermineFormattableFilesAsync( Solution solution, string projectPath, FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken) { var totalFileCount = solution.Projects.Sum(project => project.DocumentIds.Count); var projectFileCount = 0; var documentsCoveredByEditorConfig = ImmutableArray.CreateBuilder <DocumentId>(totalFileCount); var documentsNotCoveredByEditorConfig = ImmutableArray.CreateBuilder <DocumentId>(totalFileCount); var addedFilePaths = new HashSet <string>(totalFileCount); foreach (var project in solution.Projects) { if (project?.FilePath is null) { continue; } // If a project is used as a workspace, then ignore other referenced projects. if (!string.IsNullOrEmpty(projectPath) && !project.FilePath.Equals(projectPath, StringComparison.OrdinalIgnoreCase)) { logger.LogDebug(Resources.Skipping_referenced_project_0, project.Name); continue; } // Ignore unsupported project types. if (project.Language != LanguageNames.CSharp && project.Language != LanguageNames.VisualBasic) { logger.LogWarning(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, project.FilePath); continue; } projectFileCount += project.DocumentIds.Count; foreach (var document in project.Documents) { // If we've already added this document, either via a link or multi-targeted framework, then ignore. if (document?.FilePath is null || addedFilePaths.Contains(document.FilePath)) { continue; } addedFilePaths.Add(document.FilePath); var isFileIncluded = formatOptions.WorkspaceType == WorkspaceType.Folder || (formatOptions.FileMatcher.HasMatches(document.FilePath) && File.Exists(document.FilePath)); if (!isFileIncluded || !document.SupportsSyntaxTree) { continue; } var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (syntaxTree is null) { throw new Exception($"Unable to get a syntax tree for '{document.Name}'"); } if (!formatOptions.IncludeGeneratedFiles && await GeneratedCodeUtilities.IsGeneratedCodeAsync(syntaxTree, cancellationToken).ConfigureAwait(false)) { continue; } // Track files covered by an editorconfig separately from those not covered. var analyzerConfigOptions = document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree); if (analyzerConfigOptions != null) { if (formatOptions.IncludeGeneratedFiles || GeneratedCodeUtilities.GetIsGeneratedCodeFromOptions(analyzerConfigOptions) != true) { documentsCoveredByEditorConfig.Add(document.Id); } } else { documentsNotCoveredByEditorConfig.Add(document.Id); } } } // Initially we would format all documents in a workspace, even if some files weren't covered by an // .editorconfig and would have defaults applied. This behavior was an early requested change since // users were surprised to have files not specified by the .editorconfig modified. The assumption is // that users without an .editorconfig still wanted formatting (they did run a formatter after all), // so we run on all files with defaults. // 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.ToImmutable()) : (projectFileCount, documentsCoveredByEditorConfig.ToImmutable())); }
public static async Task <WorkspaceFormatResult> FormatWorkspaceAsync( FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken, bool createBinaryLog = false) { var logWorkspaceWarnings = formatOptions.LogLevel == LogLevel.Trace; logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, formatOptions.WorkspaceFilePath)); logger.LogTrace(Resources.Loading_workspace); var workspaceStopwatch = Stopwatch.StartNew(); using var workspace = formatOptions.WorkspaceType == WorkspaceType.Folder ? await OpenFolderWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.FileMatcher, cancellationToken).ConfigureAwait(false) : await OpenMSBuildWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.WorkspaceType, createBinaryLog, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false); if (workspace is null) { return(new WorkspaceFormatResult(filesFormatted: 0, fileCount: 0, exitCode: 1)); } var loadWorkspaceMS = workspaceStopwatch.ElapsedMilliseconds; logger.LogTrace(Resources.Complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds); 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); var determineFilesMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS; logger.LogTrace(Resources.Complete_in_0_ms, determineFilesMS); logger.LogTrace(Resources.Running_formatters); var formattedFiles = new List <FormattedFile>(); var formattedSolution = await RunCodeFormattersAsync( solution, formatableFiles, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false); var formatterRanMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS - determineFilesMS; logger.LogTrace(Resources.Complete_in_0_ms, formatterRanMS); var solutionChanges = formattedSolution.GetChanges(solution); var filesFormatted = 0; foreach (var projectChanges in solutionChanges.GetProjectChanges()) { foreach (var changedDocumentId in projectChanges.GetChangedDocuments()) { var changedDocument = solution.GetDocument(changedDocumentId); if (changedDocument?.FilePath is null) { continue; } logger.LogInformation(Resources.Formatted_code_file_0, changedDocument.FilePath); filesFormatted++; } } var exitCode = 0; if (formatOptions.SaveFormattedFiles && !workspace.TryApplyChanges(formattedSolution)) { logger.LogError(Resources.Failed_to_save_formatting_changes); exitCode = 1; } 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); } logger.LogDebug(Resources.Formatted_0_of_1_files, filesFormatted, fileCount); logger.LogInformation(Resources.Format_complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds); return(new WorkspaceFormatResult(filesFormatted, fileCount, exitCode)); }
public static async Task <int> Run(string folder, string workspace, string verbosity, bool dryRun, bool check, string files, string report, IConsole console = null) { // Setup logging. var serviceCollection = new ServiceCollection(); var logLevel = GetLogLevel(verbosity); ConfigureServices(serviceCollection, console, logLevel); var serviceProvider = serviceCollection.BuildServiceProvider(); var logger = serviceProvider.GetService <ILogger <Program> >(); // Hook so we can cancel and exit when ctrl+c is pressed. var cancellationTokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cancellationTokenSource.Cancel(); }; var currentDirectory = string.Empty; try { currentDirectory = Environment.CurrentDirectory; string workspaceDirectory; string workspacePath; WorkspaceType workspaceType; if (!string.IsNullOrEmpty(folder) && !string.IsNullOrEmpty(workspace)) { logger.LogWarning(Resources.Cannot_specify_both_folder_and_workspace_options); return(1); } if (!string.IsNullOrEmpty(folder)) { folder = Path.GetFullPath(folder, Environment.CurrentDirectory); workspacePath = folder; workspaceDirectory = workspacePath; workspaceType = WorkspaceType.Folder; } else { var(isSolution, workspaceFilePath) = MSBuildWorkspaceFinder.FindWorkspace(currentDirectory, workspace); workspacePath = workspaceFilePath; workspaceType = isSolution ? WorkspaceType.Solution : WorkspaceType.Project; // To ensure we get the version of MSBuild packaged with the dotnet SDK used by the // workspace, use its directory as our working directory which will take into account // a global.json if present. workspaceDirectory = Path.GetDirectoryName(workspacePath); } Environment.CurrentDirectory = workspaceDirectory; var filesToFormat = GetFilesToFormat(files, folder); // Since we are running as a dotnet tool we should be able to find an instance of // MSBuild in a .NET Core SDK. var msBuildInstance = Build.Locator.MSBuildLocator.QueryVisualStudioInstances().First(); // Since we do not inherit msbuild.deps.json when referencing the SDK copy // of MSBuild and because the SDK no longer ships with version matched assemblies, we // register an assembly loader that will load assemblies from the msbuild path with // equal or higher version numbers than requested. LooseVersionAssemblyLoader.Register(msBuildInstance.MSBuildPath); Build.Locator.MSBuildLocator.RegisterInstance(msBuildInstance); var formatOptions = new FormatOptions( workspacePath, workspaceType, logLevel, saveFormattedFiles: !dryRun, changesAreErrors: check, filesToFormat, reportPath: report); var formatResult = await CodeFormatter.FormatWorkspaceAsync( formatOptions, logger, cancellationTokenSource.Token).ConfigureAwait(false); return(GetExitCode(formatResult, check)); } catch (FileNotFoundException fex) { logger.LogError(fex.Message); return(1); } catch (OperationCanceledException) { return(1); } finally { if (!string.IsNullOrEmpty(currentDirectory)) { Environment.CurrentDirectory = currentDirectory; } } }
public static async Task <int> Run( string?workspace, bool folder, bool fixWhitespace, string?fixStyle, string?fixAnalyzers, string?verbosity, bool check, string[] include, string[] exclude, string?report, bool includeGenerated, IConsole console = null !) { if (s_parseResult == null) { return(1); } // Setup logging. var logLevel = GetLogLevel(verbosity); var logger = SetupLogging(console, logLevel); // Hook so we can cancel and exit when ctrl+c is pressed. var cancellationTokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cancellationTokenSource.Cancel(); }; var currentDirectory = string.Empty; try { currentDirectory = Environment.CurrentDirectory; var formatVersion = GetVersion(); logger.LogDebug(Resources.The_dotnet_format_version_is_0, formatVersion); string? workspaceDirectory; string workspacePath; WorkspaceType workspaceType; // The folder option means we should treat the project path as a folder path. if (folder) { // If folder isn't populated, then use the current directory workspacePath = Path.GetFullPath(workspace ?? ".", Environment.CurrentDirectory); workspaceDirectory = workspacePath; workspaceType = WorkspaceType.Folder; } else { var(isSolution, workspaceFilePath) = MSBuildWorkspaceFinder.FindWorkspace(currentDirectory, workspace); workspacePath = workspaceFilePath; workspaceType = isSolution ? WorkspaceType.Solution : WorkspaceType.Project; // To ensure we get the version of MSBuild packaged with the dotnet SDK used by the // workspace, use its directory as our working directory which will take into account // a global.json if present. workspaceDirectory = Path.GetDirectoryName(workspacePath); if (workspaceDirectory is null) { throw new Exception($"Unable to find folder at '{workspacePath}'"); } } if (workspaceType != WorkspaceType.Folder) { var runtimeVersion = GetRuntimeVersion(); logger.LogDebug(Resources.The_dotnet_runtime_version_is_0, runtimeVersion); // Load MSBuild Environment.CurrentDirectory = workspaceDirectory; if (!TryGetDotNetCliVersion(out var dotnetVersion)) { logger.LogError(Resources.Unable_to_locate_dotnet_CLI_Ensure_that_it_is_on_the_PATH); return(UnableToLocateDotNetCliExitCode); } logger.LogTrace(Resources.The_dotnet_CLI_version_is_0, dotnetVersion); if (!TryLoadMSBuild(out var msBuildPath)) { logger.LogError(Resources.Unable_to_locate_MSBuild_Ensure_the_NET_SDK_was_installed_with_the_official_installer); return(UnableToLocateMSBuildExitCode); } logger.LogTrace(Resources.Using_msbuildexe_located_in_0, msBuildPath); } var fixType = FixCategory.None; if (s_parseResult.WasOptionUsed("--fix-style", "-s")) { fixType |= FixCategory.CodeStyle; } if (s_parseResult.WasOptionUsed("--fix-analyzers", "-a")) { fixType |= FixCategory.Analyzers; } if (fixType == FixCategory.None || fixWhitespace) { fixType |= FixCategory.Whitespace; } HandleStandardInput(logger, ref include, ref exclude); var fileMatcher = SourceFileMatcher.CreateMatcher(include, exclude); var formatOptions = new FormatOptions( workspacePath, workspaceType, logLevel, fixType, codeStyleSeverity: GetSeverity(fixStyle ?? FixSeverity.Error), analyzerSeverity: GetSeverity(fixAnalyzers ?? FixSeverity.Error), saveFormattedFiles: !check, changesAreErrors: check, fileMatcher, reportPath: report, includeGenerated); var formatResult = await CodeFormatter.FormatWorkspaceAsync( formatOptions, logger, cancellationTokenSource.Token, createBinaryLog : logLevel == LogLevel.Trace).ConfigureAwait(false); return(GetExitCode(formatResult, check)); } catch (FileNotFoundException fex) { logger.LogError(fex.Message); return(UnhandledExceptionExitCode); } catch (OperationCanceledException) { return(UnhandledExceptionExitCode); } finally { if (!string.IsNullOrEmpty(currentDirectory)) { Environment.CurrentDirectory = currentDirectory; } } }
public static async Task <int> Run( string?project, string?folder, string?workspace, string?verbosity, bool check, string[] include, string[] exclude, string?report, bool includeGenerated, IConsole console = null !) { if (s_parseResult == null) { return(1); } // Setup logging. var logLevel = GetLogLevel(verbosity); var logger = SetupLogging(console, logLevel); // Hook so we can cancel and exit when ctrl+c is pressed. var cancellationTokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cancellationTokenSource.Cancel(); }; var currentDirectory = string.Empty; try { currentDirectory = Environment.CurrentDirectory; // Check for deprecated options and assign package if specified via `-w | -f` options. if (!string.IsNullOrEmpty(workspace) && string.IsNullOrEmpty(project)) { logger.LogWarning(Resources.The_workspace_option_is_deprecated_Use_the_project_argument_instead); project = workspace; } else if (!string.IsNullOrEmpty(folder) && string.IsNullOrEmpty(project)) { logger.LogWarning(Resources.The_folder_option_is_deprecated_for_specifying_the_path_Pass_the_folder_option_but_specify_the_path_with_the_project_argument_instead); project = folder; } if (s_parseResult.WasOptionUsed("--files")) { logger.LogWarning(Resources.The_files_option_is_deprecated_Use_the_include_option_instead); } if (s_parseResult.WasOptionUsed("--dry-run")) { logger.LogWarning(Resources.The_dry_run_option_is_deprecated_Use_the_check_option_instead); } string workspaceDirectory; string workspacePath; WorkspaceType workspaceType; // The presence of the folder token means we should treat the project path as a folder path. // This will change in the following version so that the folder option is a bool flag. if (s_parseResult.WasOptionUsed("-f", "--folder")) { // If folder isn't populated, then use the current directory workspacePath = Path.GetFullPath(project ?? ".", Environment.CurrentDirectory); workspaceDirectory = workspacePath; workspaceType = WorkspaceType.Folder; } else { var(isSolution, workspaceFilePath) = MSBuildWorkspaceFinder.FindWorkspace(currentDirectory, project); workspacePath = workspaceFilePath; workspaceType = isSolution ? WorkspaceType.Solution : WorkspaceType.Project; // To ensure we get the version of MSBuild packaged with the dotnet SDK used by the // workspace, use its directory as our working directory which will take into account // a global.json if present. workspaceDirectory = Path.GetDirectoryName(workspacePath); if (workspaceDirectory is null) { throw new Exception($"Unable to find folder at '{workspacePath}'"); } } // Load MSBuild Environment.CurrentDirectory = workspaceDirectory; if (!TryGetDotNetCliVersion(out var dotnetVersion)) { logger.LogError(Resources.Unable_to_locate_dotnet_CLI_Ensure_that_it_is_on_the_PATH); return(UnableToLocateDotNetCliExitCode); } logger.LogTrace(Resources.The_dotnet_CLI_version_is_0, dotnetVersion); if (!TryLoadMSBuild(out var msBuildPath)) { logger.LogError(Resources.Unable_to_locate_MSBuild_Ensure_the_NET_SDK_was_installed_with_the_official_installer); return(UnableToLocateMSBuildExitCode); } logger.LogTrace(Resources.Using_msbuildexe_located_in_0, msBuildPath); var fileMatcher = SourceFileMatcher.CreateMatcher(include, exclude); var formatOptions = new FormatOptions( workspacePath, workspaceType, logLevel, saveFormattedFiles: !check, changesAreErrors: check, fileMatcher, reportPath: report, includeGenerated); var formatResult = await CodeFormatter.FormatWorkspaceAsync( formatOptions, logger, cancellationTokenSource.Token, createBinaryLog : logLevel == LogLevel.Trace).ConfigureAwait(false); return(GetExitCode(formatResult, check)); } catch (FileNotFoundException fex) { logger.LogError(fex.Message); return(UnhandledExceptionExitCode); } catch (OperationCanceledException) { return(UnhandledExceptionExitCode); } finally { if (!string.IsNullOrEmpty(currentDirectory)) { Environment.CurrentDirectory = currentDirectory; } } }