File: FindSymbols\FindReferences\Finders\PropertySymbolReferenceFinder.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.FindSymbols.Finders
{
    internal sealed class PropertySymbolReferenceFinder : AbstractMethodOrPropertyOrEventSymbolReferenceFinder<IPropertySymbol>
    {
        protected override bool CanFind(IPropertySymbol symbol)
            => true;
 
        protected override ValueTask<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
            IPropertySymbol symbol,
            Solution solution,
            FindReferencesSearchOptions options,
            CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
 
            CascadeToBackingFields(symbol, result);
            CascadeToAccessors(symbol, result);
            CascadeToPrimaryConstructorParameters(symbol, result, cancellationToken);
 
            return new(result.ToImmutable());
        }
 
        private static void CascadeToBackingFields(IPropertySymbol symbol, ArrayBuilder<ISymbol> result)
        {
            foreach (var member in symbol.ContainingType.GetMembers())
            {
                if (member is IFieldSymbol field &&
                    symbol.Equals(field.AssociatedSymbol))
                {
                    result.Add(field);
                }
            }
        }
 
        private static void CascadeToAccessors(IPropertySymbol symbol, ArrayBuilder<ISymbol> result)
        {
            result.AddIfNotNull(symbol.GetMethod);
            result.AddIfNotNull(symbol.SetMethod);
        }
 
        private static void CascadeToPrimaryConstructorParameters(IPropertySymbol property, ArrayBuilder<ISymbol> result, CancellationToken cancellationToken)
        {
            if (property is
                {
                    IsStatic: false,
                    DeclaringSyntaxReferences.Length: > 0,
                    ContainingType:
                    {
                        IsRecord: true,
                        DeclaringSyntaxReferences.Length: > 0,
                    } containingType,
                })
            {
                // OK, we have a property in a record.  See if we can find a primary constructor that has a parameter that synthesized this
                var containingTypeSyntaxes = containingType.DeclaringSyntaxReferences.SelectAsArray(r => r.GetSyntax(cancellationToken));
                foreach (var constructor in containingType.Constructors)
                {
                    if (constructor.DeclaringSyntaxReferences.Length > 0)
                    {
                        var constructorSyntax = constructor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
                        if (containingTypeSyntaxes.Contains(constructorSyntax))
                        {
                            // OK found the primary construct.  Try to find a parameter that corresponds to this property.
                            foreach (var parameter in constructor.Parameters)
                            {
                                if (property.Name.Equals(parameter.Name) &&
                                    property.Equals(parameter.GetAssociatedSynthesizedRecordProperty(cancellationToken)))
                                {
                                    result.Add(parameter);
                                }
                            }
                        }
                    }
                }
            }
        }
 
        protected sealed override async Task<ImmutableArray<Document>> DetermineDocumentsToSearchAsync(
            IPropertySymbol symbol,
            HashSet<string>? globalAliases,
            Project project,
            IImmutableSet<Document>? documents,
            FindReferencesSearchOptions options,
            CancellationToken cancellationToken)
        {
            var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false);
 
            var forEachDocuments = IsForEachProperty(symbol)
                ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false)
                : ImmutableArray<Document>.Empty;
 
            var elementAccessDocument = symbol.IsIndexer
                ? await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, cancellationToken).ConfigureAwait(false)
                : ImmutableArray<Document>.Empty;
 
            var indexerMemberCrefDocument = symbol.IsIndexer
                ? await FindDocumentWithIndexerMemberCrefAsync(project, documents, cancellationToken).ConfigureAwait(false)
                : ImmutableArray<Document>.Empty;
 
            var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false);
            return ordinaryDocuments.Concat(forEachDocuments, elementAccessDocument, indexerMemberCrefDocument, documentsWithGlobalAttributes);
        }
 
        private static bool IsForEachProperty(IPropertySymbol symbol)
            => symbol.Name == WellKnownMemberNames.CurrentPropertyName;
 
        protected sealed override async ValueTask<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
            IPropertySymbol symbol,
            FindReferencesDocumentState state,
            FindReferencesSearchOptions options,
            CancellationToken cancellationToken)
        {
            var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync(
                symbol, state, cancellationToken).ConfigureAwait(false);
 
            if (options.AssociatePropertyReferencesWithSpecificAccessor)
            {
                // We want to associate property references to a specific accessor (if an accessor
                // is being referenced).  Check if this reference would match an accessor. If so, do
                // not add it.  It will be added by PropertyAccessorSymbolReferenceFinder.
                nameReferences = nameReferences.WhereAsArray(loc =>
                {
                    var accessors = GetReferencedAccessorSymbols(
                        state, symbol, loc.Node, cancellationToken);
                    return accessors.IsEmpty;
                });
            }
 
            var forEachReferences = IsForEachProperty(symbol)
                ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false)
                : ImmutableArray<FinderLocation>.Empty;
 
            var indexerReferences = symbol.IsIndexer
                ? await FindIndexerReferencesAsync(symbol, state, options, cancellationToken).ConfigureAwait(false)
                : ImmutableArray<FinderLocation>.Empty;
 
            var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(
                symbol, state, cancellationToken).ConfigureAwait(false);
            return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences);
        }
 
        private static Task<ImmutableArray<Document>> FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(
            Project project, IImmutableSet<Document>? documents, CancellationToken cancellationToken)
        {
            return FindDocumentsWithPredicateAsync(
                project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, cancellationToken);
        }
 
        private static Task<ImmutableArray<Document>> FindDocumentWithIndexerMemberCrefAsync(
            Project project, IImmutableSet<Document>? documents, CancellationToken cancellationToken)
        {
            return FindDocumentsWithPredicateAsync(
                project, documents, static index => index.ContainsIndexerMemberCref, cancellationToken);
        }
 
        private static async Task<ImmutableArray<FinderLocation>> FindIndexerReferencesAsync(
            IPropertySymbol symbol,
            FindReferencesDocumentState state,
            FindReferencesSearchOptions options,
            CancellationToken cancellationToken)
        {
            if (options.AssociatePropertyReferencesWithSpecificAccessor)
            {
                // Looking for individual get/set references.  Don't find anything here. 
                // these results will be provided by the PropertyAccessorSymbolReferenceFinder
                return ImmutableArray<FinderLocation>.Empty;
            }
 
            var syntaxFacts = state.SyntaxFacts;
 
            var indexerReferenceExpressions = state.Root.DescendantNodes(descendIntoTrivia: true)
                .Where(node =>
                    syntaxFacts.IsElementAccessExpression(node) ||
                    syntaxFacts.IsImplicitElementAccess(node) ||
                    syntaxFacts.IsConditionalAccessExpression(node) ||
                    syntaxFacts.IsIndexerMemberCref(node));
            using var _ = ArrayBuilder<FinderLocation>.GetInstance(out var locations);
 
            foreach (var node in indexerReferenceExpressions)
            {
                cancellationToken.ThrowIfCancellationRequested();
 
                var (matched, candidateReason, indexerReference) = await ComputeIndexerInformationAsync(
                    symbol, state, node, cancellationToken).ConfigureAwait(false);
                if (!matched)
                    continue;
 
                var location = state.SyntaxTree.GetLocation(new TextSpan(indexerReference.SpanStart, 0));
                var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken);
 
                locations.Add(new FinderLocation(node,
                    new ReferenceLocation(
                        state.Document, alias: null, location, isImplicit: false, symbolUsageInfo,
                        GetAdditionalFindUsagesProperties(node, state),
                        candidateReason)));
            }
 
            return locations.ToImmutable();
        }
 
        private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync(
            IPropertySymbol symbol,
            FindReferencesDocumentState state,
            SyntaxNode node,
            CancellationToken cancellationToken)
        {
            var syntaxFacts = state.SyntaxFacts;
 
            if (syntaxFacts.IsElementAccessExpression(node))
            {
                // The indexerReference for an element access expression will not be null
                return ComputeElementAccessInformationAsync(symbol, node, state, cancellationToken)!;
            }
            else if (syntaxFacts.IsImplicitElementAccess(node))
            {
                return ComputeImplicitElementAccessInformationAsync(symbol, node, state, cancellationToken)!;
            }
            else if (syntaxFacts.IsConditionalAccessExpression(node))
            {
                return ComputeConditionalAccessInformationAsync(symbol, node, state, cancellationToken);
            }
            else
            {
                Debug.Assert(syntaxFacts.IsIndexerMemberCref(node));
                return ComputeIndexerMemberCRefInformationAsync(symbol, state, node, cancellationToken);
            }
        }
 
        private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerMemberCRefInformationAsync(
            IPropertySymbol symbol, FindReferencesDocumentState state, SyntaxNode node, CancellationToken cancellationToken)
        {
            var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false);
 
            // For an IndexerMemberCRef the node itself is the indexer we are looking for.
            return (matched, reason, node);
        }
 
        private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeConditionalAccessInformationAsync(
            IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken)
        {
            // For a ConditionalAccessExpression the whenNotNull component is the indexer reference we are looking for
            var syntaxFacts = state.SyntaxFacts;
            syntaxFacts.GetPartsOfConditionalAccessExpression(node, out _, out var indexerReference);
 
            if (syntaxFacts.IsInvocationExpression(indexerReference))
            {
                // call to something like: goo?.bar(1)
                //
                // this will already be handled by the existing method ref finder.
                return default;
            }
 
            var (matched, reason) = await SymbolsMatchAsync(symbol, state, indexerReference, cancellationToken).ConfigureAwait(false);
            return (matched, reason, indexerReference);
        }
 
        private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode? indexerReference)> ComputeElementAccessInformationAsync(
            IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken)
        {
            // For an ElementAccessExpression the indexer we are looking for is the argumentList component.
            state.SyntaxFacts.GetPartsOfElementAccessExpression(node, out var expression, out var indexerReference);
            if (expression != null && (await SymbolsMatchAsync(symbol, state, expression, cancellationToken).ConfigureAwait(false)).matched)
            {
                // Element access with explicit member name (allowed in VB). We will have
                // already added a reference location for the member name identifier, so skip
                // this one.
                return default;
            }
 
            var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false);
            return (matched, reason, indexerReference);
        }
 
        private static async ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeImplicitElementAccessInformationAsync(
            IPropertySymbol symbol, SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken)
        {
            var argumentList = state.SyntaxFacts.GetArgumentListOfImplicitElementAccess(node);
            var (matched, reason) = await SymbolsMatchAsync(symbol, state, node, cancellationToken).ConfigureAwait(false);
            return (matched, reason, argumentList);
        }
    }
}