| File: Completion\CompletionProviders\OverrideCompletionProvider.cs | Web Access |
| Project: src\roslyn\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.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; [ExportCompletionProvider(nameof(OverrideCompletionProvider), LanguageNames.CSharp), Shared] [ExtensionOrder(After = nameof(PreprocessorCompletionProvider))] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] internal sealed partial class OverrideCompletionProvider() : AbstractOverrideCompletionProvider { internal override string Language => LanguageNames.CSharp; protected override SyntaxNode GetSyntax(SyntaxToken token) { return token.GetAncestor<EventFieldDeclarationSyntax>() ?? token.GetAncestor<EventDeclarationSyntax>() ?? token.GetAncestor<PropertyDeclarationSyntax>() ?? token.GetAncestor<IndexerDeclarationSyntax>() ?? (SyntaxNode?)token.GetAncestor<MethodDeclarationSyntax>() ?? throw ExceptionUtilities.UnexpectedValue(token); } public override bool IsInsertionTrigger(SourceText text, int characterPosition, CompletionOptions options) => CompletionUtilities.IsTriggerAfterSpaceOrStartOfWordCharacter(text, characterPosition, options); public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.SpaceTriggerCharacter; protected override SyntaxToken GetToken(CompletionItem completionItem, SyntaxTree tree, CancellationToken cancellationToken) { var tokenSpanEnd = MemberInsertionCompletionItem.GetTokenSpanEnd(completionItem); return tree.FindTokenOnLeftOfPosition(tokenSpanEnd, cancellationToken); } public override bool TryDetermineReturnType(SyntaxToken startToken, SemanticModel semanticModel, CancellationToken cancellationToken, out ITypeSymbol? returnType, out SyntaxToken nextToken) { nextToken = startToken; returnType = null; if (startToken.Parent is TypeSyntax typeSyntax) { // 'partial' is actually an identifier. If we see it just bail. This does mean // we won't handle overrides that actually return a type called 'partial'. And // not a single tear was shed. if (typeSyntax is IdentifierNameSyntax identifierName && identifierName.Identifier.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword)) { return false; } returnType = semanticModel.GetTypeInfo(typeSyntax, cancellationToken).Type; nextToken = typeSyntax.GetFirstToken().GetPreviousToken(); } return true; } public override bool TryDetermineModifiers( SyntaxToken startToken, SourceText text, int startLine, out Accessibility seenAccessibility, out DeclarationModifiers modifiers) { var token = startToken; var parentMember = token.Parent; modifiers = default; seenAccessibility = Accessibility.NotApplicable; if (parentMember is null) return false; // Keep walking backwards as long as we're still within our parent member. while (token != default) { if (token.SpanStart < parentMember.SpanStart) { // moved before the start of the member we're in. If previous member's token is on the same line, // we bail out as our replacement will delete the entire line we're on. if (IsOnStartLine(token.SpanStart, text, startLine)) return false; break; } // Ok to hit a `]` if it's the end of attributes on this member. if (token.Kind() == SyntaxKind.CloseBracketToken) { if (token.Parent is not AttributeListSyntax) return false; break; } // We only accept tokens that precede us on the same line. Splitting across multiple lines is too niche // to want to support, and more likely indicates a case of broken code that the user is in the middle of // fixing up. if (!IsOnStartLine(token.SpanStart, text, startLine)) return false; switch (token.Kind()) { // Standard modifier cases we accept. case SyntaxKind.AbstractKeyword: modifiers = modifiers.WithIsAbstract(true); break; case SyntaxKind.ExternKeyword: modifiers = modifiers.WithIsExtern(true); break; case SyntaxKind.OverrideKeyword: modifiers = modifiers.WithIsOverride(true); break; case SyntaxKind.RequiredKeyword: modifiers = modifiers.WithIsRequired(true); break; case SyntaxKind.SealedKeyword: modifiers = modifiers.WithIsSealed(true); break; case SyntaxKind.UnsafeKeyword: modifiers = modifiers.WithIsUnsafe(true); break; // Accessibility modifiers we accept. case SyntaxKind.PublicKeyword: seenAccessibility = seenAccessibility == Accessibility.NotApplicable ? Accessibility.Public : seenAccessibility; break; case SyntaxKind.PrivateKeyword: seenAccessibility = seenAccessibility switch { Accessibility.NotApplicable => Accessibility.Private, // If we see private AND protected, filter for private protected Accessibility.Protected => Accessibility.ProtectedAndInternal, _ => seenAccessibility, }; break; case SyntaxKind.InternalKeyword: // If we see internal AND protected, filter for protected internal seenAccessibility = seenAccessibility switch { Accessibility.NotApplicable => Accessibility.Internal, Accessibility.Protected => Accessibility.ProtectedOrInternal, _ => seenAccessibility, }; break; case SyntaxKind.ProtectedKeyword: seenAccessibility = seenAccessibility switch { Accessibility.NotApplicable => Accessibility.Protected, // If we see protected AND internal, filter for protected internal. Accessibility.Internal => Accessibility.ProtectedOrInternal, // Or if we see private AND protected, filter for private protected Accessibility.Private => Accessibility.ProtectedAndInternal, _ => seenAccessibility, }; break; default: // If we hit anything else, then this token is not valid for override completions, and we can just bail here. return false; } token = token.GetPreviousToken(); } // Have to at least found the override token for us to offer override-completion. return modifiers.IsOverride; } public override SyntaxToken FindStartingToken(SyntaxTree tree, int position, CancellationToken cancellationToken) { var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); return token.GetPreviousTokenIfTouchingWord(position); } public override ImmutableArray<ISymbol> FilterOverrides(ImmutableArray<ISymbol> members, ITypeSymbol? returnType) { if (returnType == null) { return members; } var filteredMembers = members.WhereAsArray(m => SymbolEquivalenceComparer.Instance.Equals(GetReturnType(m), returnType)); // Don't filter by return type if we would then have nothing to show. // This way, the user gets completion even if they speculatively typed the wrong return type return filteredMembers.Length > 0 ? filteredMembers : members; } protected override TextSpan GetTargetSelectionSpan(SyntaxNode caretTarget) { return CompletionUtilities.GetTargetSelectionSpanForInsertedMember(caretTarget); } }