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())); }
private static async Task <(Solution solution, int filesFormatted)> FormatFilesInProjectAsync(Project project, ICodingConventionsManager codingConventionsManager, EditorConfigOptionsApplier optionsApplier, ImmutableHashSet <string> filesToFormat, ILogger logger, CancellationToken cancellationToken) { var formattedDocuments = new List <(DocumentId documentId, Task <SourceText> formatTask)>(); foreach (var documentId in project.DocumentIds) { var document = project.Solution.GetDocument(documentId); if (!document.SupportsSyntaxTree) { continue; } if (!filesToFormat.IsEmpty && !filesToFormat.Contains(document.FilePath)) { continue; } var formatTask = Task.Run(async() => { if (await GeneratedCodeUtilities.IsGeneratedCodeAsync(document, cancellationToken)) { return(null); } logger.LogTrace(Resources.Formatting_code_file_0, Path.GetFileName(document.FilePath)); OptionSet documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var codingConventionsContext = await codingConventionsManager.GetConventionContextAsync(document.FilePath, cancellationToken).ConfigureAwait(false); if (codingConventionsContext?.CurrentConventions != null) { documentOptions = optionsApplier.ApplyConventions(documentOptions, codingConventionsContext.CurrentConventions, project.Language); } var formattedDocument = await Formatter.FormatAsync(document, documentOptions, cancellationToken).ConfigureAwait(false); var formattedSourceText = await formattedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); if (formattedSourceText.ContentEquals(await document.GetTextAsync(cancellationToken).ConfigureAwait(false))) { // Avoid touching files that didn't actually change return(null); } logger.LogInformation(Resources.Formatted_code_file_0, Path.GetFileName(document.FilePath)); return(formattedSourceText); }, cancellationToken); formattedDocuments.Add((documentId, formatTask)); } var formattedSolution = project.Solution; var filesFormatted = 0; foreach (var(documentId, formatTask) in formattedDocuments) { var text = await formatTask.ConfigureAwait(false); if (text is null) { continue; } filesFormatted++; formattedSolution = formattedSolution.WithDocumentText(documentId, text); } return(formattedSolution, filesFormatted); }