File: Rename\Renamer.SyncNamespaceDocumentAction.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.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ChangeNamespace;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename;
 
public static partial class Renamer
{
    /// <summary>
    /// Action that will sync the namespace of the document to match the folders property 
    /// of that document, similar to if a user performed the "Sync Namespace" code refactoring.
    /// 
    /// For example, if a document is moved from "Bat/Bar/Baz" folder structure to "Bat/Bar/Baz/Bat" and contains
    /// a namespace definition of Bat.Bar.Baz in the document, then it would update that definition to 
    /// Bat.Bar.Baz.Bat and update the solution to reflect these changes. Uses <see cref="IChangeNamespaceService"/>
    /// </summary>
    internal sealed class SyncNamespaceDocumentAction : RenameDocumentAction
    {
        private readonly AnalysisResult _analysis;
 
        private SyncNamespaceDocumentAction(AnalysisResult analysis)
            : base([])
        {
            _analysis = analysis;
        }
 
        public override string GetDescription(CultureInfo? culture)
            => WorkspacesResources.ResourceManager.GetString("Sync_namespace_to_folder_structure", culture ?? WorkspacesResources.Culture)!;
 
        internal override async Task<Solution> GetModifiedSolutionAsync(Document document, DocumentRenameOptions options, CancellationToken cancellationToken)
        {
            var changeNamespaceService = document.GetRequiredLanguageService<IChangeNamespaceService>();
            var solution = await changeNamespaceService.TryChangeTopLevelNamespacesAsync(document, _analysis.TargetNamespace, cancellationToken).ConfigureAwait(false);
 
            // If the solution fails to update fail silently. The user will see no large
            // negative impact from not doing this modification, and it's possible the document
            // was too malformed to update any namespaces.
            return solution ?? document.Project.Solution;
        }
 
        public static SyncNamespaceDocumentAction? TryCreate(Document document, IReadOnlyList<string> newFolders)
        {
            var analysisResult = Analyze(document, newFolders);
 
            if (analysisResult.HasValue)
            {
                return new SyncNamespaceDocumentAction(analysisResult.Value);
            }
 
            return null;
        }
 
        private static AnalysisResult? Analyze(Document document, IReadOnlyCollection<string> newFolders)
        {
            // https://github.com/dotnet/roslyn/issues/41841
            // VB implementation is incomplete for sync namespace
            if (document.Project.Language == LanguageNames.CSharp)
            {
                var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
                var targetNamespace = PathMetadataUtilities.TryBuildNamespaceFromFolders(newFolders, syntaxFacts, document.Project.DefaultNamespace);
 
                if (targetNamespace is null)
                {
                    return null;
                }
 
                return new AnalysisResult(targetNamespace);
            }
            else
            {
                return null;
            }
        }
 
        private readonly struct AnalysisResult(string targetNamespace)
        {
            public string TargetNamespace { get; } = targetNamespace;
        }
    }
}