File: FindSymbols\FindReferences\Finders\ConstructorSymbolReferenceFinder.cs
Web Access
Project: src\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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.FindSymbols.Finders;
 
internal sealed class ConstructorSymbolReferenceFinder : AbstractReferenceFinder<IMethodSymbol>
{
    public static readonly ConstructorSymbolReferenceFinder Instance = new();
 
    private ConstructorSymbolReferenceFinder()
    {
    }
 
    protected override bool CanFind(IMethodSymbol symbol)
        => symbol.MethodKind is MethodKind.Constructor or MethodKind.StaticConstructor;
 
    protected override Task<ImmutableArray<string>> DetermineGlobalAliasesAsync(IMethodSymbol symbol, Project project, CancellationToken cancellationToken)
    {
        var containingType = symbol.ContainingType;
        return GetAllMatchingGlobalAliasNamesAsync(project, containingType.Name, containingType.Arity, cancellationToken);
    }
 
    protected override async Task DetermineDocumentsToSearchAsync<TData>(
        IMethodSymbol symbol,
        HashSet<string>? globalAliases,
        Project project,
        IImmutableSet<Document>? documents,
        Action<Document, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        var containingType = symbol.ContainingType;
        var typeName = symbol.ContainingType.Name;
 
        await AddDocumentsAsync(
            project, documents, typeName, processResult, processResultData, cancellationToken).ConfigureAwait(false);
 
        if (globalAliases != null)
        {
            foreach (var globalAlias in globalAliases)
            {
                await AddDocumentsAsync(
                    project, documents, globalAlias, processResult, processResultData, cancellationToken).ConfigureAwait(false);
            }
        }
 
        await FindDocumentsAsync(
            project, documents, containingType.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false);
 
        await FindDocumentsWithGlobalSuppressMessageAttributeAsync(
            project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false);
 
        if (symbol.MethodKind == MethodKind.Constructor)
        {
            await FindDocumentsWithImplicitObjectCreationExpressionAsync(
                project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false);
        }
    }
 
    private static Task FindDocumentsWithImplicitObjectCreationExpressionAsync<TData>(Project project, IImmutableSet<Document>? documents, Action<Document, TData> processResult, TData processResultData, CancellationToken cancellationToken)
        => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, processResult, processResultData, cancellationToken);
 
    private static async Task AddDocumentsAsync<TData>(
        Project project,
        IImmutableSet<Document>? documents,
        string typeName,
        Action<Document, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, typeName).ConfigureAwait(false);
 
        if (TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService<ISyntaxFactsService>(), out var simpleName))
            await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false);
    }
 
    private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxFactsService syntaxFacts, SyntaxToken token)
        => syntaxFacts.TryGetPredefinedType(token, out var actualType) &&
           predefinedType == actualType;
 
    protected override void FindReferencesInDocument<TData>(
        IMethodSymbol methodSymbol,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        FindReferencesSearchOptions options,
        CancellationToken cancellationToken)
    {
        // First just look for this normal constructor references using the name of it's containing type.
        var containingType = methodSymbol.ContainingType;
        var containingTypeName = containingType.Name;
        AddReferencesInDocumentWorker(
            methodSymbol, containingTypeName, state, processResult, processResultData, cancellationToken);
 
        // Next, look for constructor references through a global alias to our containing type.
        foreach (var globalAlias in state.GlobalAliases)
            FindReferenceToAlias(methodSymbol, state, processResult, processResultData, containingTypeName, globalAlias, cancellationToken);
 
        foreach (var localAlias in state.Cache.SyntaxTreeIndex.GetAliases(containingTypeName, containingType.Arity))
            FindReferenceToAlias(methodSymbol, state, processResult, processResultData, containingTypeName, localAlias, cancellationToken);
 
        // Finally, look for constructor references to predefined types (like `new int()`),
        // implicit object references, and inside global suppression attributes.
        FindPredefinedTypeReferences(
            methodSymbol, state, processResult, processResultData, cancellationToken);
 
        FindReferencesInImplicitObjectCreationExpression(
            methodSymbol, state, processResult, processResultData, cancellationToken);
 
        FindReferencesInPrimaryConstructorBaseType(
            methodSymbol, state, processResult, processResultData, cancellationToken);
 
        FindReferencesInDocumentInsideGlobalSuppressions(
            methodSymbol, state, processResult, processResultData, cancellationToken);
    }
 
    private static void FindReferenceToAlias<TData>(
        IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action<FinderLocation, TData> processResult, TData processResultData, string name, string alias, CancellationToken cancellationToken)
    {
        // ignore the cases where the global alias might match the type name (i.e.
        // global alias Console = System.Console).  We'll already find those references
        // above.
        if (state.SyntaxFacts.StringComparer.Equals(name, alias))
            return;
 
        AddReferencesInDocumentWorker(
            methodSymbol, alias, state, processResult, processResultData, cancellationToken);
    }
 
    /// <summary>
    /// Finds references to <paramref name="symbol"/> in this <paramref name="state"/>, but only if it referenced
    /// though <paramref name="name"/> (which might be the actual name of the type, or a global alias to it).
    /// </summary>
    private static void AddReferencesInDocumentWorker<TData>(
        IMethodSymbol symbol,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        FindOrdinaryReferences(
            symbol, name, state, processResult, processResultData, cancellationToken);
        FindAttributeReferences(
            symbol, name, state, processResult, processResultData, cancellationToken);
    }
 
    private static void FindOrdinaryReferences<TData>(
        IMethodSymbol symbol,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        FindReferencesInDocumentUsingIdentifier(
            symbol, name, state, processResult, processResultData, cancellationToken);
    }
 
    private static void FindPredefinedTypeReferences<TData>(
        IMethodSymbol symbol,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType();
        if (predefinedType == PredefinedType.None)
            return;
 
        var tokens = state.Root
            .DescendantTokens(descendIntoTrivia: true)
            .WhereAsArray(
                static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token),
                (state, predefinedType));
 
        FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken);
    }
 
    private static void FindAttributeReferences<TData>(
        IMethodSymbol symbol,
        string name,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName))
            FindReferencesInDocumentUsingIdentifier(symbol, simpleName, state, processResult, processResultData, cancellationToken);
    }
 
    private static void FindReferencesInImplicitObjectCreationExpression<TData>(
        IMethodSymbol symbol,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        if (!state.Cache.SyntaxTreeIndex.ContainsImplicitObjectCreation)
            return;
 
        // Note: only C# supports implicit object creation.  So we don't need to do anything special around case
        // insensitive matching here.
        //
        // Search for all the `new` tokens in the file.
        var newKeywordTokens = state.Cache.GetNewKeywordTokens(cancellationToken);
        if (newKeywordTokens.IsEmpty)
            return;
 
        // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor.
        var minimumArgumentCount = symbol.Parameters.Count(static p => !p.IsOptional && !p.IsParams);
        var maximumArgumentCount = symbol.Parameters is [.., { IsParams: true }]
            ? int.MaxValue
            : symbol.Parameters.Length;
 
        var exactArgumentCount = symbol.Parameters.Any(static p => p.IsOptional || p.IsParams)
            ? -1
            : symbol.Parameters.Length;
 
        var implicitObjectKind = state.SyntaxFacts.SyntaxKinds.ImplicitObjectCreationExpression;
        foreach (var newKeywordToken in newKeywordTokens)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var node = newKeywordToken.Parent;
            if (node is null || node.RawKind != implicitObjectKind)
                continue;
 
            // if there are too few or too many arguments, then don't bother checking.
            var actualArgumentCount = state.SyntaxFacts.GetArgumentsOfObjectCreationExpression(node).Count;
            if (actualArgumentCount < minimumArgumentCount || actualArgumentCount > maximumArgumentCount)
                continue;
 
            // if we need an exact count then make sure that the count we have fits the count we need.
            if (exactArgumentCount != -1 && exactArgumentCount != actualArgumentCount)
                continue;
 
            var constructor = state.SemanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
            if (Matches(constructor, symbol))
            {
                var result = new FinderLocation(node, new ReferenceLocation(
                    state.Document,
                    alias: null,
                    newKeywordToken.GetLocation(),
                    isImplicit: true,
                    GetSymbolUsageInfo(node, state, cancellationToken),
                    GetAdditionalFindUsagesProperties(node, state), CandidateReason.None));
                processResult(result, processResultData);
            }
        }
    }
 
    private static void FindReferencesInPrimaryConstructorBaseType<TData>(
        IMethodSymbol symbol,
        FindReferencesDocumentState state,
        Action<FinderLocation, TData> processResult,
        TData processResultData,
        CancellationToken cancellationToken)
    {
        if (!state.Cache.SyntaxTreeIndex.ContainsPrimaryConstructorBaseType)
            return;
 
        var syntaxFacts = state.SyntaxFacts;
        foreach (var token in state.Cache.FindMatchingIdentifierTokens(symbol.ContainingType.Name, cancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var parent = token.GetRequiredParent();
            if (!syntaxFacts.IsSimpleName(parent))
                continue;
 
            if (syntaxFacts.IsRightOfQualifiedName(parent) || syntaxFacts.IsRightOfAliasQualifiedName(parent))
                parent = parent.GetRequiredParent();
 
            var node = parent.GetRequiredParent();
            if (!syntaxFacts.IsPrimaryConstructorBaseType(node))
                continue;
 
            var constructor = state.SemanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
            if (Matches(constructor, symbol))
            {
                var result = new FinderLocation(node, new ReferenceLocation(
                    state.Document,
                    alias: null,
                    token.GetLocation(),
                    isImplicit: false,
                    GetSymbolUsageInfo(node, state, cancellationToken),
                    GetAdditionalFindUsagesProperties(node, state), CandidateReason.None));
                processResult(result, processResultData);
            }
        }
    }
}