File: Completion\Providers\AbstractOverrideCompletionProvider.ItemGetter.cs
Web Access
Project: src\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.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Completion.Providers;
 
internal abstract partial class AbstractOverrideCompletionProvider
{
    private sealed partial class ItemGetter
    {
        private readonly CancellationToken _cancellationToken;
        private readonly int _position;
        private readonly AbstractOverrideCompletionProvider _provider;
        private readonly SymbolDisplayFormat _overrideNameFormat = SymbolDisplayFormats.NameFormat.WithParameterOptions(
            SymbolDisplayParameterOptions.IncludeDefaultValue |
            SymbolDisplayParameterOptions.IncludeExtensionThis |
            SymbolDisplayParameterOptions.IncludeType |
            SymbolDisplayParameterOptions.IncludeName |
            SymbolDisplayParameterOptions.IncludeParamsRefOut)
            .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
 
        private readonly Document _document;
        private readonly SourceText _text;
        private readonly SyntaxTree _syntaxTree;
        private readonly int _startLineNumber;
 
        private ItemGetter(
            AbstractOverrideCompletionProvider overrideCompletionProvider,
            Document document,
            int position,
            SourceText text,
            SyntaxTree syntaxTree,
            int startLineNumber,
            CancellationToken cancellationToken)
        {
            _provider = overrideCompletionProvider;
            _document = document;
            _position = position;
            _text = text;
            _syntaxTree = syntaxTree;
            _startLineNumber = startLineNumber;
            _cancellationToken = cancellationToken;
        }
 
        public static async Task<ItemGetter> CreateAsync(
            AbstractOverrideCompletionProvider overrideCompletionProvider,
            Document document,
            int position,
            CancellationToken cancellationToken)
        {
            var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
 
            var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var startLineNumber = text.Lines.IndexOf(position);
            return new ItemGetter(overrideCompletionProvider, document, position, text, syntaxTree, startLineNumber, cancellationToken);
        }
 
        public async Task<ImmutableArray<CompletionItem>> GetItemsAsync()
        {
            // modifiers* override modifiers* type? |
            if (!TryCheckForTrailingTokens(_position))
                return default;
 
            var startToken = _provider.FindStartingToken(_syntaxTree, _position, _cancellationToken);
            if (startToken.Parent == null)
                return default;
 
            var semanticModel = await _document.ReuseExistingSpeculativeModelAsync(startToken.Parent, _cancellationToken).ConfigureAwait(false);
            if (!_provider.TryDetermineReturnType(startToken, semanticModel, _cancellationToken, out var returnType, out var tokenAfterReturnType) ||
                !_provider.TryDetermineModifiers(tokenAfterReturnType, _text, _startLineNumber, out var seenAccessibility, out var modifiers) ||
                !TryDetermineOverridableMembers(semanticModel, startToken, seenAccessibility, out var overridableMembers))
            {
                return default;
            }
 
            return _provider
                .FilterOverrides(overridableMembers, returnType)
                .SelectAsArray(m => CreateItem(m, semanticModel, startToken, modifiers));
        }
 
        private CompletionItem CreateItem(
            ISymbol symbol, SemanticModel semanticModel,
            SyntaxToken startToken, DeclarationModifiers modifiers)
        {
            var position = startToken.SpanStart;
 
            var displayString = symbol.ToMinimalDisplayString(semanticModel, position, _overrideNameFormat);
 
            return MemberInsertionCompletionItem.Create(
                displayString,
                displayTextSuffix: "",
                modifiers,
                _startLineNumber,
                symbol,
                startToken,
                position,
                rules: GetRules());
        }
 
        private bool TryDetermineOverridableMembers(
            SemanticModel semanticModel,
            SyntaxToken startToken,
            Accessibility seenAccessibility,
            out ImmutableArray<ISymbol> overridableMembers)
        {
            var containingType = semanticModel.GetEnclosingSymbol<INamedTypeSymbol>(startToken.SpanStart, _cancellationToken);
            if (containingType is null)
            {
                overridableMembers = default;
                return false;
            }
 
            var result = containingType.GetOverridableMembers(_cancellationToken);
 
            // Filter based on accessibility
            if (seenAccessibility != Accessibility.NotApplicable)
                result = result.WhereAsArray(m => MatchesAccessibility(m.DeclaredAccessibility, seenAccessibility));
 
            overridableMembers = result;
            return overridableMembers.Length > 0;
 
            static bool MatchesAccessibility(Accessibility declaredAccessibility, Accessibility seenAccessibility)
            {
                // since some accessibility modifiers take two keywords, allow filtering to those if the user has
                // only typed one of the keywords.  This makes it less onerous than having to determine the exact
                // right modifier set to specify, and follows the intuition of writing less filtering less and
                // writing more filtering out more.
                return seenAccessibility switch
                {
                    // `private`, `private protected`
                    Accessibility.Private => declaredAccessibility is Accessibility.Private or Accessibility.ProtectedAndInternal,
                    // `protected`, `private protected`, `protected internal`
                    Accessibility.Protected => declaredAccessibility is Accessibility.Protected or Accessibility.ProtectedAndInternal or Accessibility.ProtectedOrInternal,
                    // `internal`, `protected internal`
                    Accessibility.Internal => declaredAccessibility is Accessibility.Internal or Accessibility.ProtectedOrInternal,
                    // For anything else, require an exact match.
                    _ => declaredAccessibility == seenAccessibility,
                };
            }
        }
 
        private bool TryCheckForTrailingTokens(int position)
        {
            var root = _syntaxTree.GetRoot(_cancellationToken);
            var token = root.FindToken(position);
 
            // Don't want to offer Override completion if there's a token after the current
            // position.
            if (token.SpanStart > position)
            {
                return false;
            }
 
            // If the next token is also on our line then we don't want to offer completion.
            if (IsOnStartLine(token.GetNextToken().SpanStart))
            {
                return false;
            }
 
            return true;
        }
 
        private bool IsOnStartLine(int position)
            => _text.Lines.IndexOf(position) == _startLineNumber;
    }
}