File: AddPackage\AbstractAddPackageCodeFixProvider.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SymbolSearch;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.AddPackage;
 
/// <summary>
/// Values for parameters can be provided (during testing) for mocking purposes.
/// </summary> 
internal abstract partial class AbstractAddPackageCodeFixProvider : CodeFixProvider
{
    protected abstract bool IncludePrerelease { get; }
 
    public abstract override FixAllProvider? GetFixAllProvider();
 
    protected async Task<ImmutableArray<CodeAction>> GetAddPackagesCodeActionsAsync(
        CodeFixContext context, ISet<string> assemblyNames)
    {
        var document = context.Document;
        var cancellationToken = context.CancellationToken;
 
        var workspaceServices = document.Project.Solution.Services;
 
        if (workspaceServices.GetService<ISymbolSearchService>() is not { } symbolSearchService ||
            workspaceServices.GetService<IPackageInstallerService>() is not { } installerService ||
            !installerService.IsEnabled(document.Project.Id))
        {
            return [];
        }
 
        var options = await document.GetSymbolSearchOptionsAsync(cancellationToken).ConfigureAwait(false);
        if (!options.SearchNuGetPackages)
        {
            return [];
        }
 
        var codeActions = ArrayBuilder<CodeAction>.GetInstance();
        var packageSources = PackageSourceHelper.GetPackageSources(installerService.TryGetPackageSources());
 
        foreach (var (name, source) in packageSources)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var sortedPackages = await FindMatchingPackagesAsync(
                name, symbolSearchService,
                assemblyNames, cancellationToken).ConfigureAwait(false);
 
            foreach (var package in sortedPackages)
            {
                codeActions.Add(new InstallPackageParentCodeAction(
                    installerService, source,
                    package.PackageName, IncludePrerelease, document));
            }
        }
 
        return codeActions.ToImmutableAndFree();
    }
 
    private static async Task<ImmutableArray<PackageWithAssemblyResult>> FindMatchingPackagesAsync(
        string sourceName,
        ISymbolSearchService searchService,
        ISet<string> assemblyNames,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var result = new HashSet<PackageWithAssemblyResult>();
 
        foreach (var assemblyName in assemblyNames)
        {
            var packagesWithAssembly = await searchService.FindPackagesWithAssemblyAsync(
                sourceName, assemblyName, cancellationToken).ConfigureAwait(false);
 
            result.AddRange(packagesWithAssembly);
        }
 
        // Ensure the packages are sorted by rank.
        var sortedPackages = result.ToImmutableArray().Sort();
 
        return sortedPackages;
    }
}