File: InlineHints\AbstractInlineTypeHintsService.cs
Web Access
Project: ..\..\..\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.InlineHints
{
    internal abstract class AbstractInlineTypeHintsService : IInlineTypeHintsService
    {
        /// <summary>
        /// Used as a tiebreaker to position coincident type and parameter hints.
        /// Type hints will always appear second.
        /// </summary>
        private const double Ranking = 1.0;
 
        protected static readonly SymbolDisplayFormat s_minimalTypeStyle = new SymbolDisplayFormat(
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
        protected abstract TypeHint? TryGetTypeHint(
            SemanticModel semanticModel, SyntaxNode node,
            bool displayAllOverride,
            bool forImplicitVariableTypes,
            bool forLambdaParameterTypes,
            bool forImplicitObjectCreation,
            CancellationToken cancellationToken);
 
        public async Task<ImmutableArray<InlineHint>> GetInlineHintsAsync(
            Document document, TextSpan textSpan, InlineTypeHintsOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken)
        {
            // TODO: https://github.com/dotnet/roslyn/issues/57283
            var globalOptions = document.Project.Solution.Services.GetRequiredService<ILegacyGlobalOptionsWorkspaceService>();
            var displayAllOverride = globalOptions.InlineHintsOptionsDisplayAllOverride;
 
            var enabledForTypes = options.EnabledForTypes;
            if (!enabledForTypes && !displayAllOverride)
                return ImmutableArray<InlineHint>.Empty;
 
            var forImplicitVariableTypes = enabledForTypes && options.ForImplicitVariableTypes;
            var forLambdaParameterTypes = enabledForTypes && options.ForLambdaParameterTypes;
            var forImplicitObjectCreation = enabledForTypes && options.ForImplicitObjectCreation;
            if (!forImplicitVariableTypes && !forLambdaParameterTypes && !forImplicitObjectCreation && !displayAllOverride)
                return ImmutableArray<InlineHint>.Empty;
 
            var anonymousTypeService = document.GetRequiredLanguageService<IStructuralTypeDisplayService>();
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
            using var _1 = ArrayBuilder<InlineHint>.GetInstance(out var result);
 
            foreach (var node in root.DescendantNodes(n => n.Span.IntersectsWith(textSpan)))
            {
                var hintOpt = TryGetTypeHint(
                    semanticModel, node,
                    displayAllOverride,
                    forImplicitVariableTypes,
                    forLambdaParameterTypes,
                    forImplicitObjectCreation,
                    cancellationToken);
                if (hintOpt == null)
                    continue;
 
                var (type, span, textChange, prefix, suffix) = hintOpt.Value;
 
                using var _2 = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var finalParts);
                finalParts.AddRange(prefix);
 
                var parts = type.ToDisplayParts(s_minimalTypeStyle);
                AddParts(anonymousTypeService, finalParts, parts, semanticModel, span.Start);
 
                // If we have nothing to show, then don't bother adding this hint.
                if (finalParts.All(p => string.IsNullOrWhiteSpace(p.ToString())))
                    continue;
 
                finalParts.AddRange(suffix);
                var taggedText = finalParts.ToTaggedText();
 
                result.Add(new InlineHint(
                    span, taggedText, textChange, ranking: Ranking,
                    InlineHintHelpers.GetDescriptionFunction(span.Start, type.GetSymbolKey(cancellationToken: cancellationToken), displayOptions)));
            }
 
            return result.ToImmutable();
        }
 
        private void AddParts(
            IStructuralTypeDisplayService anonymousTypeService,
            ArrayBuilder<SymbolDisplayPart> finalParts,
            ImmutableArray<SymbolDisplayPart> parts,
            SemanticModel semanticModel,
            int position,
            HashSet<INamedTypeSymbol>? seenSymbols = null)
        {
            seenSymbols ??= new();
 
            foreach (var part in parts)
            {
                if (part.Symbol is INamedTypeSymbol { IsAnonymousType: true } anonymousType)
                {
                    if (seenSymbols.Add(anonymousType))
                    {
                        var anonymousParts = anonymousTypeService.GetAnonymousTypeParts(anonymousType, semanticModel, position);
                        AddParts(anonymousTypeService, finalParts, anonymousParts, semanticModel, position, seenSymbols);
                        seenSymbols.Remove(anonymousType);
                    }
                    else
                    {
                        finalParts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Text, symbol: null, "..."));
                    }
                }
                else
                {
                    finalParts.Add(part);
                }
            }
        }
    }
}