File: NameTupleElement\AbstractNameTupleElementCodeRefactoringProvider.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.NameTupleElement;
 
internal abstract class AbstractNameTupleElementCodeRefactoringProvider<TArgumentSyntax, TTupleExpressionSyntax> : CodeRefactoringProvider
    where TArgumentSyntax : SyntaxNode
    where TTupleExpressionSyntax : SyntaxNode
{
    protected abstract TArgumentSyntax WithName(TArgumentSyntax argument, string argumentName);
 
    public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        var (document, span, cancellationToken) = context;
        var (_, argument, elementName) = await TryGetArgumentInfoAsync(document, span, cancellationToken).ConfigureAwait(false);
 
        if (elementName == null)
        {
            return;
        }
 
        context.RegisterRefactoring(
            CodeAction.Create(
                string.Format(FeaturesResources.Add_tuple_element_name_0, elementName),
                c => AddNamedElementAsync(document, span, cancellationToken),
                nameof(FeaturesResources.Add_tuple_element_name_0) + "_" + elementName),
            argument.Span);
    }
 
    private static async Task<(SyntaxNode root, TArgumentSyntax argument, string argumentName)> TryGetArgumentInfoAsync(
        Document document, TextSpan span, CancellationToken cancellationToken)
    {
        if (document.Project.Solution.WorkspaceKind == WorkspaceKind.MiscellaneousFiles)
        {
            return default;
        }
 
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        var potentialArguments = await document.GetRelevantNodesAsync<TArgumentSyntax>(span, cancellationToken).ConfigureAwait(false);
        var argument = potentialArguments.FirstOrDefault(n => n?.Parent is TTupleExpressionSyntax);
        if (argument == null || !syntaxFacts.IsSimpleArgument(argument))
        {
            return default;
        }
 
        var tuple = (TTupleExpressionSyntax)argument.GetRequiredParent();
 
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        if (semanticModel.GetTypeInfo(tuple, cancellationToken).ConvertedType is not INamedTypeSymbol tupleType)
        {
            return default;
        }
 
        syntaxFacts.GetPartsOfTupleExpression<TArgumentSyntax>(tuple, out _, out var arguments, out _);
        var argumentIndex = arguments.IndexOf(argument);
        var elements = tupleType.TupleElements;
        if (elements.IsDefaultOrEmpty || argumentIndex >= elements.Length)
        {
            return default;
        }
 
        var element = elements[argumentIndex];
        if (element.Equals(element.CorrespondingTupleField))
        {
            return default;
        }
 
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        return (root, argument, element.Name);
    }
 
    private async Task<Document> AddNamedElementAsync(Document document, TextSpan span, CancellationToken cancellationToken)
    {
        var (root, argument, elementName) = await TryGetArgumentInfoAsync(document, span, cancellationToken).ConfigureAwait(false);
 
        var newArgument = WithName(argument, elementName).WithTriviaFrom(argument);
        var newRoot = root.ReplaceNode(argument, newArgument);
        return document.WithSyntaxRoot(newRoot);
    }
}