File: src\Analyzers\Core\CodeFixes\UpgradeProject\AbstractUpgradeProjectCodeFixProvider.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CodeActions.CodeAction;
 
namespace Microsoft.CodeAnalysis.UpgradeProject;
 
internal abstract partial class AbstractUpgradeProjectCodeFixProvider : CodeFixProvider
{
    public abstract string SuggestedVersion(ImmutableArray<Diagnostic> diagnostics);
    public abstract Solution UpgradeProject(Project project, string version);
    public abstract bool IsUpgrade(Project project, string newVersion);
    public abstract string UpgradeThisProjectResource { get; }
    public abstract string UpgradeAllProjectsResource { get; }
 
    public override FixAllProvider? GetFixAllProvider()
    {
        // This code fix uses a dedicated action for fixing all instances in a solution
        return null;
    }
 
    public override Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var diagnostics = context.Diagnostics;
 
        context.RegisterFixes(GetUpgradeProjectCodeActions(context), diagnostics);
        return Task.CompletedTask;
    }
 
    protected ImmutableArray<CodeAction> GetUpgradeProjectCodeActions(CodeFixContext context)
    {
        var project = context.Document.Project;
        var solution = project.Solution;
        var newVersion = SuggestedVersion(context.Diagnostics);
 
        var result = new List<CodeAction>();
        var language = project.Language;
 
        var upgradeableProjects = solution.Projects.Where(p => CanUpgrade(p, language, newVersion)).AsImmutable();
 
        if (upgradeableProjects.Length == 0)
        {
            return [];
        }
 
        var fixOneProjectTitle = string.Format(UpgradeThisProjectResource, newVersion);
        var fixOneProject = ProjectOptionsChangeAction.Create(fixOneProjectTitle,
            _ => Task.FromResult(UpgradeProject(project, newVersion)));
 
        result.Add(fixOneProject);
 
        if (upgradeableProjects.Length > 1)
        {
            var fixAllProjectsTitle = string.Format(UpgradeAllProjectsResource, newVersion);
 
            var fixAllProjects = ProjectOptionsChangeAction.Create(fixAllProjectsTitle,
                ct => Task.FromResult(UpgradeAllProjects(solution, language, newVersion, ct)));
 
            result.Add(fixAllProjects);
        }
 
        return result.AsImmutable();
    }
 
    public Solution UpgradeAllProjects(Solution solution, string language, string version, CancellationToken cancellationToken)
    {
        var currentSolution = solution;
        foreach (var projectId in solution.Projects.Select(p => p.Id))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var currentProject = currentSolution.GetRequiredProject(projectId);
 
            if (CanUpgrade(currentProject, language, version))
            {
                currentSolution = UpgradeProject(currentProject, version);
            }
        }
 
        return currentSolution;
    }
 
    private bool CanUpgrade(Project project, string language, string version)
        => project.Language == language && IsUpgrade(project, version);
}
 
#if CODE_STYLE

internal sealed class ProjectOptionsChangeAction : CodeAction
{
    public override string Title { get; }
 
    private readonly Func<CancellationToken, Task<Solution>> _createChangedSolution;
 
    private ProjectOptionsChangeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution)
    {
        this.Title = title;
        _createChangedSolution = createChangedSolution;
    }
 
    public static ProjectOptionsChangeAction Create(string title, Func<CancellationToken, Task<Solution>> createChangedSolution)
        => new(title, createChangedSolution);
 
    protected override Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
        => SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
 
    protected override async Task<Solution?> GetChangedSolutionAsync(CancellationToken cancellationToken)
        => await _createChangedSolution(cancellationToken).ConfigureAwait(false);
}
 
#else
 
internal sealed class ProjectOptionsChangeAction : SolutionChangeAction
{
    public override ImmutableArray<string> Tags => RequiresNonDocumentChangeTags;
 
    private ProjectOptionsChangeAction(string title, Func<IProgress<CodeAnalysisProgress>, CancellationToken, Task<Solution>> createChangedSolution)
        : base(title, createChangedSolution, equivalenceKey: null, priority: CodeActionPriority.Default, createdFromFactoryMethod: true)
    {
    }
 
    public static ProjectOptionsChangeAction Create(string title, Func<IProgress<CodeAnalysisProgress>, CancellationToken, Task<Solution>> createChangedSolution)
        => new(title, createChangedSolution);
 
    protected override Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
        => SpecializedTasks.EmptyEnumerable<CodeActionOperation>();
}
 
#endif