File: src\Analyzers\CSharp\CodeFixes\AssignOutParameters\AbstractAssignOutParametersCodeFixProvider.cs
Web Access
Project: src\src\CodeStyle\CSharp\CodeFixes\Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes)
// 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;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.AssignOutParameters;
 
internal abstract class AbstractAssignOutParametersCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
    private const string CS0177 = nameof(CS0177); // The out parameter 'x' must be assigned to before control leaves the current method
 
    public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
        [CS0177];
 
    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var cancellationToken = context.CancellationToken;
        var document = context.Document;
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        var (container, location) = GetContainer(root, context.Span);
        if (container != null)
        {
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var dataFlow = semanticModel.AnalyzeDataFlow(location);
            if (dataFlow.Succeeded)
            {
                TryRegisterFix(context, document, container, location);
            }
        }
    }
 
    protected abstract void TryRegisterFix(CodeFixContext context, Document document, SyntaxNode container, SyntaxNode location);
 
    private static (SyntaxNode container, SyntaxNode exprOrStatement) GetContainer(SyntaxNode root, TextSpan span)
    {
        var location = root.FindNode(span);
        if (IsValidLocation(location))
        {
            var container = GetContainer(location);
            if (container != null)
            {
                return (container, location);
            }
        }
 
        return default;
    }
 
    private static bool IsValidLocation(SyntaxNode location)
    {
        if (location is StatementSyntax)
        {
            return location.Parent is BlockSyntax
                || location.Parent is SwitchSectionSyntax
                || location.Parent.IsEmbeddedStatementOwner();
        }
 
        if (location is ExpressionSyntax)
        {
            return location.Parent is ArrowExpressionClauseSyntax or LambdaExpressionSyntax;
        }
 
        return false;
    }
 
    private static SyntaxNode? GetContainer(SyntaxNode node)
    {
        for (var current = node; current != null; current = current.Parent)
        {
            var parameterList = current.GetParameterList();
            if (parameterList != null)
            {
                return current;
            }
        }
 
        return null;
    }
 
    private static async Task<MultiDictionary<SyntaxNode, (SyntaxNode exprOrStatement, ImmutableArray<IParameterSymbol>)>> GetUnassignedParametersAsync(
        Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
    {
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
        var containersAndLocations =
            diagnostics.SelectAsArray(d => GetContainer(root, d.Location.SourceSpan))
                       .WhereAsArray(t => t.container != null);
 
        var result = new MultiDictionary<SyntaxNode, (SyntaxNode exprOrStatement, ImmutableArray<IParameterSymbol>)>();
        foreach (var group in containersAndLocations.GroupBy(t => t.container))
        {
            var container = group.Key;
 
            var parameterList = container.GetParameterList();
            Contract.ThrowIfNull(parameterList);
 
            var outParameters = parameterList.Parameters
                .Select(p => semanticModel.GetRequiredDeclaredSymbol(p, cancellationToken))
                .Where(p => p.RefKind == RefKind.Out)
                .ToImmutableArray();
 
            var distinctExprsOrStatements = group.Select(t => t.exprOrStatement).Distinct();
            foreach (var exprOrStatement in distinctExprsOrStatements)
            {
                var dataFlow = semanticModel.AnalyzeDataFlow(exprOrStatement);
                var unassignedParameters = outParameters.WhereAsArray(
                    p => !dataFlow.DefinitelyAssignedOnExit.Contains(p));
 
                if (unassignedParameters.Length > 0)
                {
                    result.Add(container, (exprOrStatement, unassignedParameters));
                }
            }
        }
 
        return result;
    }
 
    protected sealed override async Task FixAllAsync(
        Document document, ImmutableArray<Diagnostic> diagnostics,
        SyntaxEditor editor, CancellationToken cancellationToken)
    {
        var unassignedParameters = await GetUnassignedParametersAsync(
            document, diagnostics, cancellationToken).ConfigureAwait(false);
 
        foreach (var container in unassignedParameters.Keys.OrderByDescending(n => n.Span.Start))
        {
            AssignOutParameters(
                editor, container, unassignedParameters[container], cancellationToken);
        }
    }
 
    protected abstract void AssignOutParameters(
        SyntaxEditor editor, SyntaxNode container,
        MultiDictionary<SyntaxNode, (SyntaxNode exprOrStatement, ImmutableArray<IParameterSymbol> unassignedParameters)>.ValueSet values,
        CancellationToken cancellationToken);
 
    protected static ImmutableArray<SyntaxNode> GenerateAssignmentStatements(
        SyntaxGenerator generator, ImmutableArray<IParameterSymbol> unassignedParameters)
    {
        var result = ArrayBuilder<SyntaxNode>.GetInstance();
 
        foreach (var parameter in unassignedParameters)
        {
            result.Add(generator.ExpressionStatement(generator.AssignmentStatement(
                generator.IdentifierName(parameter.Name),
                ExpressionGenerator.GenerateExpression(generator, parameter.Type, value: null, canUseFieldReference: false))));
        }
 
        return result.ToImmutableAndFree();
    }
}