File: Completion\KeywordRecommenders\AbstractSpecialTypePreselectingKeywordRecommender.cs
Web Access
Project: ..\..\..\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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.Threading;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders
{
    internal abstract class AbstractSpecialTypePreselectingKeywordRecommender : AbstractSyntacticSingleKeywordRecommender
    {
        public AbstractSpecialTypePreselectingKeywordRecommender(
            SyntaxKind keywordKind,
            bool isValidInPreprocessorContext = false,
            bool shouldFormatOnCommit = false)
            : base(keywordKind, isValidInPreprocessorContext, shouldFormatOnCommit)
        {
        }
 
        protected abstract SpecialType SpecialType { get; }
        protected abstract bool IsValidContextWorker(int position, CSharpSyntaxContext context, CancellationToken cancellationToken);
 
        // When the keyword is the inferred type in this context, we should treat it like its corresponding type symbol
        // in terms of MatchPripority, so the selection can be determined by how well it matches the filter text instead,
        // e.g. selecting "string" over "String" when user typed "str".
        protected override int PreselectMatchPriority => SymbolMatchPriority.PreferType;
 
        protected override bool ShouldPreselect(CSharpSyntaxContext context, CancellationToken cancellationToken)
            => context.InferredTypes.Any(static (t, self) => t.SpecialType == self.SpecialType, this);
 
        protected sealed override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken)
        {
            // Filter out all special-types from locations where we think we only want something task-like.
            if (context.IsTaskLikeTypeContext)
                return false;
 
            return IsValidContextWorker(position, context, cancellationToken) ||
                IsAfterRefOrReadonlyInTopLevelOrMemberDeclaration(context, position, cancellationToken);
        }
 
        private static bool IsAfterRefOrReadonlyInTopLevelOrMemberDeclaration(CSharpSyntaxContext context, int position, CancellationToken cancellationToken)
        {
            var syntaxTree = context.SyntaxTree;
            if (!syntaxTree.IsAfterKeyword(position, SyntaxKind.RefKeyword, cancellationToken) &&
                !syntaxTree.IsAfterKeyword(position, SyntaxKind.ReadOnlyKeyword, cancellationToken))
            {
                return false;
            }
 
            var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
            token = token.GetPreviousTokenIfTouchingWord(position);
 
            // if we have `readonly` move backwards to see if we have `ref readonly`.
            if (token.Kind() is SyntaxKind.ReadOnlyKeyword)
                token = syntaxTree.FindTokenOnLeftOfPosition(token.SpanStart, cancellationToken);
 
            // if we're not after `ref` or `ref readonly` then don't offer a type-keyword here.
            if (token.Kind() != SyntaxKind.RefKeyword)
                return false;
 
            // If we're inside a type, this is always to have a ref/readonly type name.
            var containingType = token.GetAncestor<TypeDeclarationSyntax>();
            if (containingType != null)
                return true;
 
            // If not in a type, but in a namespace, this is not ok to have a ref/readonly type name.
            var containingNamespace = token.GetAncestor<BaseNamespaceDeclarationSyntax>();
            if (containingNamespace != null)
                return false;
 
            // otherwise, we're at top level.  Can have a ref/readonly top-level local/function.
            return true;
        }
    }
}