File: ConvertToExtension\ConvertToExtensionFixAllProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ConvertToExtension;
 
using FixAllScope = CodeAnalysis.CodeFixes.FixAllScope;
 
internal sealed partial class ConvertToExtensionCodeRefactoringProvider
{
    private sealed class ConvertToExtensionFixAllProvider()
        : DocumentBasedFixAllProvider(
            [FixAllScope.Document, FixAllScope.Project, FixAllScope.Solution, FixAllScope.ContainingType])
    {
        protected override async Task<Document?> FixAllAsync(
            FixAllContext fixAllContext,
            Document document,
            Optional<ImmutableArray<TextSpan>> fixAllSpans)
        {
            var cancellationToken = fixAllContext.CancellationToken;
 
            var codeGenerationService = (CSharpCodeGenerationService)document.GetRequiredLanguageService<ICodeGenerationService>();
 
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
            var editor = new SyntaxEditor(root, document.Project.Solution.Services);
            foreach (var declaration in GetTopLevelClassDeclarations(root, fixAllSpans))
            {
                // We might hit partial parts that have no extension methods in them.  Just skip those.
                var extensionMethods = GetAllExtensionMethods(semanticModel, declaration, cancellationToken);
                if (extensionMethods.IsEmpty)
                    continue;
 
                // For each class declaration we hit that has extension methods in it, convert all the extension methods
                // to extensions and replace the old declaration with the new one.
                var newDeclaration = ConvertToExtension(
                    codeGenerationService, declaration, extensionMethods, specificExtension: null);
                editor.ReplaceNode(declaration, newDeclaration);
            }
 
            var newRoot = editor.GetChangedRoot();
            return document.WithSyntaxRoot(newRoot);
        }
 
        private static IEnumerable<ClassDeclarationSyntax> GetTopLevelClassDeclarations(
            SyntaxNode root, Optional<ImmutableArray<TextSpan>> fixAllSpans)
        {
            if (fixAllSpans.HasValue)
            {
                // User selected 'fix all in containing type'.  Core code refactoring engine will return the spans of
                // the containing class.  Process each of those individually, converting all the extension methods in
                // each partial part to extension declarations.
                return fixAllSpans.Value
                    .Select(span => root.FindNode(span) as ClassDeclarationSyntax)
                    .WhereNotNull();
            }
            else
            {
                // Processing the whole file.  Return all top level classes in the file.
                return root
                    .DescendantNodes(descendIntoChildren: n => n is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax)
                    .OfType<ClassDeclarationSyntax>();
            }
        }
    }
}