// 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. #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.GeneratedCodeRecognition; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.SemanticModelWorkspaceService; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; #if DEBUG using System.Collections.Immutable; using System.Diagnostics; #endif namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static partial class DocumentExtensions { // ⚠ Verify IVTs do not use this method before removing it. public static TLanguageService? GetLanguageService(this Document? document) where TLanguageService : class, ILanguageService => document?.Project?.GetLanguageService(); public static TLanguageService GetRequiredLanguageService(this Document document) where TLanguageService : class, ILanguageService => document.Project.GetRequiredLanguageService(); public static async Task GetRequiredSemanticModelAsync(this Document document, CancellationToken cancellationToken) { var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } public static async Task GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return syntaxTree ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } #if !CODE_STYLE public static SyntaxTree GetRequiredSyntaxTreeSynchronously(this Document document, CancellationToken cancellationToken) { var syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken); return syntaxTree ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } #endif public static async Task GetRequiredSyntaxRootAsync(this Document document, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); return root ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } public static bool IsOpen(this Document document) { var workspace = document.Project.Solution.Workspace as Workspace; return workspace != null && workspace.IsDocumentOpen(document.Id); } /// /// This will return either regular semantic model or speculative semantic based on context. any feature that is /// involved in typing or run on UI thread should use this to take advantage of speculative semantic model /// whenever possible automatically. /// /// When using this API, semantic model should only be used to ask questions about nodes inside of the member /// that contains the given . If the span is not inside a member /// /// As a speculative semantic model may be returned, location based information provided by it may be innacurate. /// public static Task ReuseExistingSpeculativeModelAsync(this Document document, int position, CancellationToken cancellationToken) => GetSemanticModelForSpanAsync(document, new TextSpan(position, 0), cancellationToken); /// /// This will return either regular semantic model or speculative semantic based on context. any feature that is /// involved in typing or run on UI thread should use this to take advantage of speculative semantic model /// whenever possible automatically. /// /// When using this API, semantic model should only be used to ask questions about nodes inside of the member /// that contains the given span. If the span is not inside a member /// /// As a speculative semantic model may be returned, location based information provided by it may be innacurate. /// public static async Task GetSemanticModelForSpanAsync(this Document document, TextSpan span, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document.SupportsSemanticModel); var syntaxFactService = document.GetLanguageService(); var semanticModelService = document.Project.Solution.Workspace.Services.GetService(); if (semanticModelService == null || syntaxFactService == null) { return (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false))!; } var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(root, "We shouldn't have a null root if the document supports semantic models"); var token = root.FindToken(span.Start); if (token.Parent == null) { return (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false))!; } var node = token.Parent.AncestorsAndSelf().First(a => a.FullSpan.Contains(span)); return await GetSemanticModelForNodeAsync(semanticModelService, syntaxFactService, document, node, span, cancellationToken).ConfigureAwait(false); } /// /// this will return either regular semantic model or speculative semantic based on context. /// any feature that is involved in typing or run on UI thread should use this to take advantage of speculative semantic model /// whenever possible automatically. /// /// when using this API, semantic model should only be used to ask node inside of the given node except ones that belong to /// member signature. otherwise, it might throw if semantic model returned by this API is a speculative semantic model. /// /// also, symbols from the semantic model returned by this API might have out of date location information. /// if exact location (not relative location) is needed from symbol, regular GetSemanticModel should be used. /// public static Task GetSemanticModelForNodeAsync(this Document document, SyntaxNode? node, CancellationToken cancellationToken) { var syntaxFactService = document.GetLanguageService(); var semanticModelService = document.Project.Solution.Workspace.Services.GetService(); if (semanticModelService == null || syntaxFactService == null || node == null) { return document.GetSemanticModelAsync(cancellationToken)!; } return GetSemanticModelForNodeAsync(semanticModelService, syntaxFactService, document, node, node.FullSpan, cancellationToken); } private static Task GetSemanticModelForNodeAsync( ISemanticModelService semanticModelService, ISyntaxFactsService syntaxFactService, Document document, SyntaxNode node, TextSpan span, CancellationToken cancellationToken) { // check whether given span is a valid span to do speculative binding var speculativeBindingSpan = syntaxFactService.GetMemberBodySpanForSpeculativeBinding(node); if (!speculativeBindingSpan.Contains(span)) { return document.GetSemanticModelAsync(cancellationToken)!; } return semanticModelService.GetSemanticModelForNodeAsync(document, node, cancellationToken); } #if DEBUG public static async Task HasAnyErrorsAsync(this Document document, CancellationToken cancellationToken, List? ignoreErrorCode = null) { var errors = await GetErrorsAsync(document, cancellationToken, ignoreErrorCode).ConfigureAwait(false); return errors.Length > 0; } public static async Task> GetErrorsAsync(this Document document, CancellationToken cancellationToken, IList? ignoreErrorCode = null) { if (!document.SupportsSemanticModel) { return ImmutableArray.Empty; } ignoreErrorCode ??= SpecializedCollections.EmptyList(); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); return semanticModel!.GetDiagnostics(cancellationToken: cancellationToken).WhereAsArray( diag => diag.Severity == DiagnosticSeverity.Error && !ignoreErrorCode.Contains(diag.Id)); } /// /// Debug only extension method to verify no errors were introduced by formatting, pretty listing and other related document altering service in error-free code. /// public static async Task VerifyNoErrorsAsync(this Document newDocument, string message, CancellationToken cancellationToken, List? ignoreErrorCodes = null) { var errors = await newDocument.GetErrorsAsync(cancellationToken, ignoreErrorCodes).ConfigureAwait(false); if (errors.Length > 0) { var diagnostics = string.Join(", ", errors.Select(d => d.ToString())); Debug.Assert(false, message + ". " + diagnostics); } } #endif #if !CODE_STYLE public static bool IsGeneratedCode(this Document document, CancellationToken cancellationToken) { var generatedCodeRecognitionService = document.GetLanguageService(); return generatedCodeRecognitionService?.IsGeneratedCode(document, cancellationToken) == true; } #endif public static async Task IsGeneratedCodeAsync(this Document document, CancellationToken cancellationToken) { var generatedCodeRecognitionService = document.GetLanguageService(); return generatedCodeRecognitionService != null && await generatedCodeRecognitionService.IsGeneratedCodeAsync(document, cancellationToken).ConfigureAwait(false); } public static IEnumerable GetLinkedDocuments(this Document document) { var solution = document.Project.Solution; foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) { yield return solution.GetRequiredDocument(linkedDocumentId); } } } }