File: AddImport\CodeActions\AssemblyReferenceCodeAction.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.AddImport;
 
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
{
    private sealed class AssemblyReferenceCodeAction : AddImportCodeAction
    {
        /// <summary>
        /// This code action only works by adding a reference.  As such, it requires a non document change (and is
        /// thus restricted in which hosts it can run).
        /// </summary>
        public AssemblyReferenceCodeAction(
            Document originalDocument,
            AddImportFixData fixData)
            : base(originalDocument, fixData, RequiresNonDocumentChangeTags)
        {
            Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.ReferenceAssemblySymbol);
        }
 
        protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
            => await ComputeOperationsAsync(isPreview: true, cancellationToken).ConfigureAwait(false);
 
        protected override Task<ImmutableArray<CodeActionOperation>> ComputeOperationsAsync(IProgress<CodeAnalysisProgress> progress, CancellationToken cancellationToken)
            => ComputeOperationsAsync(isPreview: false, cancellationToken);
 
        private async Task<ImmutableArray<CodeActionOperation>> ComputeOperationsAsync(bool isPreview, CancellationToken cancellationToken)
        {
            var newDocument = await GetUpdatedDocumentAsync(cancellationToken).ConfigureAwait(false);
            var newProject = newDocument.Project;
 
            if (isPreview)
            {
                // If this is a preview, just return an ApplyChangesOperation for the updated document
                var operation = new ApplyChangesOperation(newProject.Solution);
                return [operation];
            }
            else
            {
                // Otherwise return an operation that can apply the text changes and add the reference
                var operation = new AddAssemblyReferenceCodeActionOperation(
                    FixData.AssemblyReferenceAssemblyName,
                    FixData.AssemblyReferenceFullyQualifiedTypeName,
                    newProject);
                return [operation];
            }
        }
 
        private sealed class AddAssemblyReferenceCodeActionOperation(
            string assemblyReferenceAssemblyName,
            string assemblyReferenceFullyQualifiedTypeName,
            Project newProject) : CodeActionOperation
        {
            private readonly string _assemblyReferenceAssemblyName = assemblyReferenceAssemblyName;
            private readonly string _assemblyReferenceFullyQualifiedTypeName = assemblyReferenceFullyQualifiedTypeName;
            private readonly Project _newProject = newProject;
 
            internal override bool ApplyDuringTests => true;
 
            public override void Apply(Workspace workspace, CancellationToken cancellationToken)
            {
                var operation = GetApplyChangesOperation(workspace);
                if (operation is null)
                    return;
 
                operation.Apply(workspace, cancellationToken);
            }
 
            internal override Task<bool> TryApplyAsync(
                Workspace workspace, Solution originalSolution, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
            {
                var operation = GetApplyChangesOperation(workspace);
                if (operation is null)
                    return SpecializedTasks.False;
 
                return operation.TryApplyAsync(workspace, originalSolution, progressTracker, cancellationToken);
            }
 
            private ApplyChangesOperation? GetApplyChangesOperation(Workspace workspace)
            {
                var resolvedPath = ResolvePath(workspace);
                if (string.IsNullOrWhiteSpace(resolvedPath))
                    return null;
 
                var service = workspace.Services.GetRequiredService<IMetadataService>();
                var reference = service.GetReference(resolvedPath, MetadataReferenceProperties.Assembly);
                var newProject = _newProject.WithMetadataReferences(
                    _newProject.MetadataReferences.Concat(reference));
 
                return new ApplyChangesOperation(newProject.Solution);
            }
 
            private string? ResolvePath(Workspace workspace)
            {
                var assemblyResolverService = workspace.Services.GetRequiredService<IFrameworkAssemblyPathResolver>();
 
                return assemblyResolverService.ResolveAssemblyPath(
                    _newProject.Id,
                    _assemblyReferenceAssemblyName,
                    _assemblyReferenceFullyQualifiedTypeName);
            }
        }
    }
}