// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.Extensions; internal static partial class ProjectExtensions { public static TLanguageService? GetLanguageService<TLanguageService>(this Project? project) where TLanguageService : class, ILanguageService #if CODE_STYLE => project?.GetExtendedLanguageServices().GetService<TLanguageService>(); #else => project?.Services.GetService<TLanguageService>(); #endif public static TLanguageService GetRequiredLanguageService<TLanguageService>(this Project project) where TLanguageService : class, ILanguageService #if CODE_STYLE => project.GetExtendedLanguageServices().GetRequiredService<TLanguageService>(); #else => project.Services.GetRequiredService<TLanguageService>(); #endif #pragma warning disable RS0030 // Do not used banned API 'Project.LanguageServices', use 'GetExtendedLanguageServices' instead - allow in this helper. /// <summary> /// Gets extended host language services, which includes language services from <see cref="Project.LanguageServices"/>. /// </summary> public static HostLanguageServices GetExtendedLanguageServices(this Project project) #if !WORKSPACE => project.Solution.Workspace.Services.GetExtendedLanguageServices(project.Language); #else => project.Solution.Services.GetExtendedLanguageServices(project.Language); #endif #pragma warning restore RS0030 // Do not used banned APIs public static string? TryGetAnalyzerConfigPathForProjectConfiguration(this Project project) => TryGetAnalyzerConfigPathForProjectOrDiagnosticConfiguration(project, diagnostic: null); public static string? TryGetAnalyzerConfigPathForDiagnosticConfiguration(this Project project, Diagnostic diagnostic) { Debug.Assert(diagnostic != null); return TryGetAnalyzerConfigPathForProjectOrDiagnosticConfiguration(project, diagnostic); } private static string? TryGetAnalyzerConfigPathForProjectOrDiagnosticConfiguration(Project project, Diagnostic? diagnostic) { if (project.AnalyzerConfigDocuments.Any()) { var diagnosticFilePath = PathUtilities.GetDirectoryName(diagnostic?.Location.SourceTree?.FilePath ?? project.FilePath); if (!PathUtilities.IsAbsolute(diagnosticFilePath)) { return null; } // Currently, we use a simple heuristic to find existing .editorconfig file. // We start from the directory of the source file where the diagnostic was reported and walk up // the directory tree to find an .editorconfig file. // In future, we might change this algorithm, or allow end users to customize it based on options. var bestPath = string.Empty; AnalyzerConfigDocument? bestAnalyzerConfigDocument = null; foreach (var analyzerConfigDocument in project.AnalyzerConfigDocuments) { // Analyzer config documents always have full paths, so GetDirectoryName will not return null. var analyzerConfigDirectory = PathUtilities.GetDirectoryName(analyzerConfigDocument.FilePath)!; if (diagnosticFilePath.StartsWith(analyzerConfigDirectory) && analyzerConfigDirectory.Length > bestPath.Length) { bestPath = analyzerConfigDirectory; bestAnalyzerConfigDocument = analyzerConfigDocument; } } if (bestAnalyzerConfigDocument != null) { return bestAnalyzerConfigDocument.FilePath; } } // Did not find any existing .editorconfig, so create one at root of the solution, if one exists. // If project is not part of a solution, then use project path. var solutionOrProjectFilePath = project.Solution?.FilePath ?? project.FilePath; if (!PathUtilities.IsAbsolute(solutionOrProjectFilePath)) { return null; } var solutionOrProjectDirectoryPath = PathUtilities.GetDirectoryName(solutionOrProjectFilePath); // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636 return PathUtilities.CombineAbsoluteAndRelativePaths(solutionOrProjectDirectoryPath!, ".editorconfig"); } public static AnalyzerConfigDocument? TryGetExistingAnalyzerConfigDocumentAtPath(this Project project, string analyzerConfigPath) { Debug.Assert(analyzerConfigPath != null); Debug.Assert(PathUtilities.IsAbsolute(analyzerConfigPath)); return project.AnalyzerConfigDocuments.FirstOrDefault(d => d.FilePath == analyzerConfigPath); } public static async Task<Compilation> GetRequiredCompilationAsync(this Project project, CancellationToken cancellationToken) { var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); if (compilation == null) { throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.Compilation_is_required_to_accomplish_the_task_but_is_not_supported_by_project_0, project.Name)); } return compilation; } } |