File: Completion\KeywordRecommenders\AbstractSpecialTypePreselectingKeywordRecommender.cs
Web Access
Project: src\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(
    SyntaxKind keywordKind,
    bool isValidInPreprocessorContext = false,
    bool shouldFormatOnCommit = false) : AbstractSyntacticSingleKeywordRecommender(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;
    }
}