File: Wrapping\AbstractWrappingCodeRefactoringProvider.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.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
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;
        }
    }
}