File: Wrapping\AbstractWrappingCodeRefactoringProvider.cs
Web Access
Project: src\roslyn\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.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.Wrapping;

/// <summary>
/// Base type for the C# and VB wrapping refactorings.  The only responsibility of this type is
/// to walk up the tree at the position the user is at, seeing if any node above the user can be
/// wrapped by any provided <see cref="ISyntaxWrapper"/>s.
/// 
/// Once we get any wrapping actions, we stop looking further.  This keeps the refactorings
/// scoped as closely as possible to where the user is, as well as preventing overloading of the
/// lightbulb with too many actions.
/// </summary>
internal abstract class AbstractWrappingCodeRefactoringProvider : CodeRefactoringProvider
{
    private readonly ImmutableArray<ISyntaxWrapper> _wrappers;

    protected AbstractWrappingCodeRefactoringProvider(
        ImmutableArray<ISyntaxWrapper> wrappers)
    {
        _wrappers = wrappers;
    }

    protected abstract SyntaxWrappingOptions GetWrappingOptions(IOptionsReader options);

    public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        var (document, span, cancellationToken) = context;
        if (!span.IsEmpty)
            return;

        var position = span.Start;
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var token = root.FindToken(position);

        var configOptions = await document.GetHostAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false);
        var options = GetWrappingOptions(configOptions);

        foreach (var node in token.GetRequiredParent().AncestorsAndSelf())
        {
            var containsSyntaxError = node.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error);

            // Check if any wrapper can handle this node.  If so, then we're done, otherwise
            // keep walking up.
            foreach (var wrapper in _wrappers)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var computer = await wrapper.TryCreateComputerAsync(
                    document, position, node, options, containsSyntaxError, cancellationToken).ConfigureAwait(false);

                if (computer == null)
                    continue;

                var actions = await computer.GetTopLevelCodeActionsAsync(cancellationToken).ConfigureAwait(false);
                if (actions.IsDefaultOrEmpty)
                    continue;

                context.RegisterRefactorings(actions);
                return;
            }

            // if we hit a syntax error and the computer couldn't handle it, then bail out.  Don't want to format if
            // we don't really understand what's going on.
            if (containsSyntaxError)
                return;
        }
    }
}