File: MoveToNamespace\AbstractMoveToNamespaceCodeAction.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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices;
using Microsoft.CodeAnalysis.CodeCleanup;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.MoveToNamespace;
 
internal abstract partial class AbstractMoveToNamespaceCodeAction(
    IMoveToNamespaceService moveToNamespaceService,
    MoveToNamespaceAnalysisResult analysisResult) : CodeActionWithOptions
{
    private readonly IMoveToNamespaceService _moveToNamespaceService = moveToNamespaceService;
    private readonly MoveToNamespaceAnalysisResult _moveToNamespaceAnalysisResult = analysisResult;
 
    /// <summary>
    /// This code action does notify clients about the rename it performs.  However, this is an optional part of
    /// this work, that happens after the move has happened.  As such, this does not require non document changes
    /// and can run in all our hosts.
    /// </summary>
    public sealed override ImmutableArray<string> Tags => [];
 
    public sealed override object GetOptions(CancellationToken cancellationToken)
    {
        return _moveToNamespaceService.GetChangeNamespaceOptions(
            _moveToNamespaceAnalysisResult.Document,
            _moveToNamespaceAnalysisResult.OriginalNamespace,
            _moveToNamespaceAnalysisResult.Namespaces);
    }
 
    protected sealed override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(
        object options, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        // We won't get an empty target namespace from VS, but still should handle it w/o crashing.
        if (options is MoveToNamespaceOptionsResult moveToNamespaceOptions &&
            !moveToNamespaceOptions.IsCancelled &&
            !string.IsNullOrEmpty(moveToNamespaceOptions.Namespace))
        {
            var moveToNamespaceResult = await _moveToNamespaceService.MoveToNamespaceAsync(
                _moveToNamespaceAnalysisResult,
                moveToNamespaceOptions.Namespace,
                cancellationToken).ConfigureAwait(false);
 
            if (moveToNamespaceResult.Succeeded)
            {
                return CreateRenameOperations(moveToNamespaceResult);
            }
        }
 
        return [];
    }
 
    private static ImmutableArray<CodeActionOperation> CreateRenameOperations(MoveToNamespaceResult moveToNamespaceResult)
    {
        Debug.Assert(moveToNamespaceResult.Succeeded);
 
        using var _ = PooledObjects.ArrayBuilder<CodeActionOperation>.GetInstance(out var operations);
        operations.Add(new ApplyChangesOperation(moveToNamespaceResult.UpdatedSolution));
 
        var symbolRenameCodeActionOperationFactory = moveToNamespaceResult.UpdatedSolution.Services.GetService<ISymbolRenamedCodeActionOperationFactoryWorkspaceService>();
 
        // It's possible we're not in a host context providing this service, in which case just provide a code
        // action that won't notify of the symbol rename. Without the symbol rename operation, code generators (like
        // WPF) may not know to regenerate code correctly.
        if (symbolRenameCodeActionOperationFactory != null)
        {
            foreach (var (newName, symbol) in moveToNamespaceResult.NewNameOriginalSymbolMapping)
            {
                operations.Add(symbolRenameCodeActionOperationFactory.CreateSymbolRenamedOperation(
                    symbol,
                    newName,
                    moveToNamespaceResult.OriginalSolution,
                    moveToNamespaceResult.UpdatedSolution));
            }
        }
 
        return operations.ToImmutableAndClear();
    }
 
    public static AbstractMoveToNamespaceCodeAction Generate(IMoveToNamespaceService changeNamespaceService, MoveToNamespaceAnalysisResult analysisResult)
        => analysisResult.Container switch
        {
            MoveToNamespaceAnalysisResult.ContainerType.NamedType => new MoveTypeToNamespaceCodeAction(changeNamespaceService, analysisResult),
            MoveToNamespaceAnalysisResult.ContainerType.Namespace => new MoveItemsToNamespaceCodeAction(changeNamespaceService, analysisResult),
            _ => throw ExceptionUtilities.UnexpectedValue(analysisResult.Container)
        };
}